Tutorials

 

Home
About
How it Works
Documentation
Tutorials

Examples

Why not MFC
Links
Contact

 

 

Menu of tutorials

Tutorial 1:   The Simplest Window
Tutorial 2:   Using Classes and Inheritance
Tutorial 3:   Using Messages to Create a Scribble Window
Tutorial 4:   Repainting the Window
Tutorial 5:   Wrapping a Frame around our Scribble Window
Tutorial 6:   Customising Window Creation
Tutorial 7:   Customising the ToolBar
Tutorial 8:   Loading and Saving Files
Tutorial 9:   Printing
Tutorial 10: Finishing Touches

Tutorial 8:  Loading and Saving Files

In this tutorial we will demonstrate how to save our data to a file, and load it back again.

Saving Data

In the previous tutorial our plotpoint data was stored in a vector member variable of CView. In this tutorial we move this data to the CDoc class. The code to load and save our data to a disc file is added to CDoc as well. Seperating the data from the view like this is often referred to as a document/view architecture.

Our CDoc class is made a member variable of the CMainFrame class like this.

class CMainFrame : public CFrame
{
public:
  CMainFrame();
  virtual ~CMainFrame();

  CDoc& GetDoc() { return m_Doc; }
  void LoadFile(LPCTSTR str);

  void OnFileExit();
  void OnFileNew();
  void OnFileOpen();
  void OnFilePrint();
  void OnFileSave();
  void OnFileSaveAs();
  void OnPenColor();

protected:
  virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
  virtual void SetupToolBar();
  virtual LRESULT WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam);

private:
  CView m_View;
  CDoc m_Doc;
  CString m_PathName;

};

Our CView class stores the PlotPoint data in CDoc. It uses the following functions to access CDoc. CView::GetDoc returns a reference to CDoc, and CView::GetPoints returns a reference to the vector containing the PlotPoints.

CDoc& CView::GetDoc()
{
  CMainFrame& Frame = GetScribbleApp().GetMainFrame();
  return Frame.GetDoc();
}

std::vector& CView::GetPoints()
{ 
  return GetDoc().GetPoints(); 
}

To save the data we perform the following steps:

  • Use CFile::SaveFileDialog to open a dialog and choose the filename to save the data to.
  • Use CArchive to write the data to the file.

This code segment shows how to use the SaveFileDialog function to choose the file to write to.

void CMainFrame::OnFileSaveAs()
{
  CFileDialog FileDlg(FALSE, _T("dat"), 0, OFN_OVERWRITEPROMPT, _T("Scribble Files (*.dat)\0*.dat\0\0"));
  FileDlg.SetTitle(_T("Save File"));

  // Bring up the file open dialog retrieve the selected filename
  if (FileDlg.DoModal(*this) == IDOK)
  {
    CString str = FileDlg.GetPathName();

    // Save the file
    if (GetDoc().FileSave(str))
    {
      // Save the file name
      m_PathName = str;
      AddMRUEntry(m_PathName);
    }
  }
}

This next code segment demonstrates how to write the contents of CDoc to an archive. Note that a failure to write to the archive throws a FileException. Here we use a catch to display the relevant information if writing to the archive fails.

BOOL CDoc::FileSave(LPCTSTR szFilename)
{
  BOOL bResult = FALSE;

  try
  {
    CArchive ar(szFilename, CArchive::store);
    ar << *this;
    bResult = TRUE;
  }
  catch (const CFileException &e)
  {
    // An exception occurred. Display the relevant information.
    ::MessageBox(NULL, e.GetText(), _T("Failed to Save File"), MB_ICONWARNING);
  }

  return bResult;
}

CDoc inherits from CObject. This provides CDoc with a virtual Serialize function we can override to define how the CDoc class stores itself in the archive. Within the Serialize function we define, CArchive's IsStoring function is used to test if the archive is loading or storing so we can take the appropriate actions.

This is the Serialize function for CDoc. When storing, we first store the numer of PlotPoints, and then we store an ArchiveObject which contains each PlotPoint. When loading, we read the number of PlotPoints, and then read an ArchiveObject containing each PlotPoint. The PlotPoints are then pushed into the our vector.  

void CDoc::Serialize(CArchive& ar)
// Uses CArchive to stream data to or from a file
{

  if (ar.IsStoring())
  {
    // Store the number of points
    UINT nPoints = GetPoints().size();
    ar << nPoints;
		
    // Store the PlotPoint data
    std::vector::iterator iter;
    for (iter = GetPoints().begin(); iter < GetPoints().end(); ++iter)
    {
      ArchiveObject ao( &(*iter), sizeof(PlotPoint) );
      ar << ao;
    }
  }
  else
  {
    UINT nPoints;
    PlotPoint pp = {0};
    GetPoints().clear();

    // Load the number of points
    ar >> nPoints;

    // Load the PlotPoint data
    for (UINT u = 0; u < nPoints; ++u)
    {
      ArchiveObject ao( &pp, sizeof(pp) );
      ar >> ao;
      GetPoints().push_back(pp);
    }
  }

}

Loading Data

Reading data back from a file follows a rather similar process. The steps involved are as follows:

  • Use CFile::OpenFileDialog to open a dialog and choose the file to load.
  • Use CArchive to retrieve the data from the file.

The following code segment demonstrates how to use OpenFileDialog to choose the file to load.

void CMainFrame::OnFileOpen()
{
  CFileDialog FileDlg(TRUE, _T("dat"), 0, OFN_FILEMUSTEXIST, _T("Scribble Files (*.dat)\0*.dat\0\0"));
  FileDlg.SetTitle(_T("Open File"));

  // Bring up the file open dialog retrieve the selected filename
  if (FileDlg.DoModal(*this) == IDOK)
  {
    // Load the file
    LoadFile(FileDlg.GetPathName());
  }
}

This is the code which loads our PlotPoint vector from the contents of the chosen file. Once again we catch the exception and display the result if the archive fails to write to the file. 

BOOL CDoc::FileOpen(LPCTSTR szFilename)
{
  GetPoints().clear();
  BOOL bResult = FALSE;

  try
  {
    CArchive ar(szFilename, CArchive::load);
    ar >> *this;
    bResult = TRUE;
  }

  catch (const CWinException& e)
  {
    // An exception occurred. Display the relevant information.
    ::MessageBox(NULL, e.GetText(), _T("Failed to Load File"), MB_ICONWARNING);
		
    GetPoints().clear();
  }

  return bResult;
}
void CMainFrame::LoadFile(LPCTSTR str)
{
  // Retrieve the PlotPoint data
  if (GetDoc().FileOpen(str))
  {
    // Save the filename
    m_PathName = str;
  }
  else
    m_PathName=_T("");

  GetView().Invalidate();
}

Supporting Drag and Drop

An alternative way to load a file is drop it on the view window. This is known as drag and drop. The steps required to support drag and drop are as follows:

  • Add DragAcceptFiles to CView::OnCreate
  • Add a handler for the WM_DROPFILES message to CView::WndProc
  • Add a CView::OnDropFiles member function.
int CView::OnCreate(CREATESTRUCT&)
{
  // Support Drag and Drop on this window
  DragAcceptFiles(TRUE);
  return 0;
}
LRESULT CView::WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uMsg)
  {
    case WM_DROPFILES:	return OnDropFiles(uMsg, wParam, lParam);
    case WM_LBUTTONDOWN:	return OnLButtonDown(uMsg, wParam, lParam);
    case WM_MOUSEMOVE:	return OnMouseMove(uMsg, wParam, lParam);
    case WM_LBUTTONUP:	return OnLButtonUp(uMsg, wParam, lParam);
  }

  //Use the default message handling for remaining messages
  return WndProcDefault(uMsg, wParam, lParam);
}
LRESULT CView::OnDropFiles(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  UNREFERENCED_PARAMETER(uMsg);
  UNREFERENCED_PARAMETER(lParam);

  HDROP hDrop = (HDROP)wParam;
  UINT nLength = DragQueryFile(hDrop, 0, 0, 0);

  if (nLength > 0)
  {
    CString FileName;
    DragQueryFile(hDrop, 0, FileName.GetBuffer(nLength), nLength+1);
    FileName.ReleaseBuffer();

    // Send a user defined message to the frame window
    GetParent().SendMessage(UWM_DROPFILE, (WPARAM)FileName.c_str(), 0);

    DragFinish(hDrop);
  }

  return 0L;
}

The source code for this tutorial is located within the Tutorial folder of the software available from SourceForge at http://sourceforge.net/projects/win32-framework.