You'll make the CanClose function check whether the drawing in the window has changed before the drawing is discarded. If the drawing has changed, the user is given a chance to either save the file, continue without saving the file, or abort the close operation entirely.
Also, to implement the CmFileOpen function, the CmFileSave function, and the CmFileSaveAs function, you need to add two more protected functions, OpenFile and SaveFile, to the window class. These functions are discussed a little later in this step.
FileData is initialized in the TDrawWindow constructor to a newed TOpenSaveDialog::TData object. Because FileData is a pointer to an object, a delete statement must be added to the TDrawWindow destructor to ensure that the object is removed from memory when the application terminates.
Outside of the constructor, the IsDirty flag is set in a number of functions:
Outside the constructor, the IsNewFile flag is set in a number of functions:
Checking the IsDirty flag tells the CanClose function whether it's even necessary to prompt the user for approval of the closing operation. If the drawing isn't dirty, there's no need to ask whether it's OK to close. The user can simply reload the file.
If the file is dirty, then the CanClose function pops up a message box. Using the MB_YESNOCANCEL flag in the message box call gives the user three possible choices instead of two:
bool TDrawWindow::CanClose() { if (IsDirty) switch(MessageBox("Do you want to save?", "Drawing has changed", MB_YESNOCANCEL | MB_ICONQUESTION)) { case IDCANCEL: // Choosing Cancel means to abort the close -- return false. return false; case IDYES: // Choosing Yes means to save the drawing. CmFileSave(); } return true; }
Note that the CmFileNew function is modified in this step to take advantage of the new CanClose function.
CmFileSave function
The CmFileSave function is relatively simple. It checks whether the drawing is new by testing IsNewFile. If IsNewFile is true, CmFileSave calls CmFileSaveAs, which prompts the user for a file in which to save the drawing. Otherwise, it calls SaveFile, which does the actual work of saving the drawing.
void TDrawWindow::CmFileSave() { if (IsNewFile) CmFileSaveAs(); else SaveFile(); }
After ensuring that it's OK to proceed, CmFileOpen creates a TFileOpenDialog object. The TFileOpenDialog constructor can take up to five parameters, but for this application you need to use only two. The last three parameters all have default values. The two parameters you need to provide are a pointer to the parent window and a reference to a TOpenSaveDialog::TData object. In this case, the pointer to the parent window is the this pointer. The TOpenSaveDialog::TData object is provided by FileData.
Once the dialog box object is constructed, it is executed by calling the TFileOpenDialog::Execute function. There are only two possible return values for the TFileOpenDialog, IDOK and IDCANCEL. The value that is returned depends on whether the user presses the OK or Cancel button in the File Open dialog box.
If the return value is IDOK, CmFileOpen then calls the OpenFile function, which does the actual work of opening the file. The Execute function also stores the name of the file the user selected into the FileName member of FileData. If the return value is not IDOK (that is, if the return value is IDCANCEL), no further action is taken and the function returns.
The CmFileOpen function should look something like this:
void TDrawWindow::CmFileOpen() { if (CanClose()) if (TFileOpenDialog(this, *FileData).Execute() == IDOK) OpenFile(); }
To determine which of these the user is doing, CmFileSaveAs first checks the IsNewFile flag. If the file is new, CmFileSaveAs copies a null string into the FileName member of FileData. If the file is not new, FileName is left as it is.
The distinction between these two is quite important. If FileName contains a null string, the default name in the File Name box of the File Open dialog box is set to the name filter found in the FileData object, in this case, *.pts. But if FileName already contains a name, that name plus its directory path is inserted in the File Name box.
Once this has been done, TFileSaveDialog is created and executed. This works exactly the same as TFileOpenDialog does in the CmFileOpen function. If the Execute function returns IDOK, CmFileSaveAs then calls the SaveFile function.
The CmFileSaveAs function should look something like this:
void TDrawWindow::CmFileSaveAs() { if (IsNewFile) strcpy(FileData->FileName, ""); if ((new TFileSaveDialog(this, *FileData))->Execute() == IDOK) SaveFile(); }
Once the file is successfully opened, the Line array is flushed. OpenFile then reads in the number of points saved in the file, which is the first data item stored in the file. It then sets up a for loop that reads each point into a temporary TPoint object. That object is then added to the Line array.
Once all the points have been read in, OpenFile calls Invalidate. This invalidates the window region, causing a WM_PAINT message to be sent and the new drawing to be painted in the window.
Lastly, OpenFile sets IsDirty and IsNewFile both to false. The OpenFile function should look something like this:
void TDrawWindow::OpenFile() { ifstream is(FileData->FileName); if (!is) MessageBox("Unable to open file", "File Error", MB_OK | MB_ICONEXCLAMATION); else { Line->Flush(); unsigned numPoints; is >> numPoints; while (numPoints--) { TPoint point; is >> point; Line->Add(point); } } IsNewFile = IsDirty = false; Invalidate(); }
Once the file has been opened, the function Line->GetItemsInContainer is called. The result is inserted into the file. This number is read in by the OpenFile function to determine how many points are stored in the file.
After that, SaveFile sets up an iterator called i from Line. This iterator goes through all the points contained in the Line array. Each point is then inserted into the stream until there are no points left.
Lastly, IsNewFile and IsDirty are set to false. Here is how the SaveFile function should look:
void TDrawWindow::SaveFile() { ofstream os(FileData->FileName); if (!os) MessageBox("Unable to open file", "File Error", MB_OK | MB_ICONEXCLAMATION); else { os << Line->GetItemsInContainer(); TPointsIterator i(*Line); while (i) os << i++; IsNewFile = IsDirty = false; } }
TDialog can take up to three parameters:
The code for CmAbout should look like this:
void TDrawWindow::CmAbout() { TDialog(this, IDD_ABOUT).Execute(); }