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.
Transfering Data between CView and CDoc
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. Separating 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 CView class.
class CMainFrame : public CFrame
{
public:
CMainFrame();
virtual ~CMainFrame();
CDoc& GetDoc() { return m_view.GetDoc(); }
void LoadFile(LPCTSTR str);
LRESULT OnDropFile(WPARAM wparam);
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 msg, WPARAM wparam, LPARAM lparam);
private:
CView m_view;
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()
{
return m_doc;
}
std::vector& CView::GetAllPoints()
{
return GetDoc().GetAllPoints();
}
Saving Data
To save the data we perform the following steps:
- Use CFileDialog 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 isFileSaved = FALSE;
try
{
CArchive ar(szFilename, CArchive::store);
ar << *this;
isFileSaved = TRUE;
}
catch (const CFileException &e)
{
// An exception occurred. Display the relevant information.
::MessageBox(NULL, e.GetText(), _T("Failed to Save File"), MB_ICONWARNING);
}
return isFileSaved;
}
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
number of PlotPoints, and then we store an ArchiveObject that 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 points = GetPoints().size();
ar << points;
// 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 points;
PlotPoint pp = {0};
GetPoints().clear();
// Load the number of points.
ar >> points;
// Load the PlotPoint data.
for (UINT u = 0; u < points; ++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 that 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 fileName)
{
GetPoints().clear();
BOOL isFileOpened = FALSE;
try
{
CArchive ar(fileName, CArchive::load);
ar >> *this;
isFileOpened = 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 isFileOpened;
}
void CMainFrame::LoadFile(LPCTSTR fileName)
{
// Retrieve the PlotPoint data.
if (GetDoc().FileOpen(fileName))
{
// Save the filename.
m_fileName = fileName;
}
else
m_filehName=_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 msg, WPARAM wparam, LPARAM lparam)
{
switch (uMsg)
{
case WM_DROPFILES: return OnDropFiles(msg, wparam, lparam);
case WM_LBUTTONDOWN: return OnLButtonDown(msg, wparam, lparam);
case WM_MOUSEMOVE: return OnMouseMove(msg, wparam, lparam);
case WM_LBUTTONUP: return OnLButtonUp(msg, wparam, lparam);
}
// Use the default message handling for remaining messages.
return WndProcDefault(msg, wparam, lparam);
}
LRESULT CView::OnDropFiles(UINT msg, WPARAM wparam, LPARAM lparam)
{
UNREFERENCED_PARAMETER(msg);
UNREFERENCED_PARAMETER(lparam);
HDROP drop = (HDROP)wparam;
UINT length = DragQueryFile(drop, 0, 0, 0);
if (length > 0)
{
CString fileName;
DragQueryFile(drop, 0, fileName.GetBuffer(length), length+1);
fileName.ReleaseBuffer();
// Send a user defined message to the frame window.
GetParent().SendMessage(UWM_DROPFILE, (WPARAM)fileName.c_str(), 0);
DragFinish(drop);
}
return 0;
}