Introduction
For one of my recent projects I had a rather strange requirement. It was an
image conversion program basically, and it allowed you to open bitmap files.
When the user clicked on the save icon in the toolbar or if he took the
save-item from the File-menu, I was to save the image in a custom format using
the same name as the original bitmap, but replacing the bmp extension with a
custom extension. This meant that I should suppress the File-Save-As dialog.
Initially I thought I simply had to override OnSaveDocument
and refrain from
calling the base class, but I quickly discovered that OnSaveDocument
was too
late to suppress the File-Save-As dialog.
DoSave revealed
I took a look at doccore.cpp and soon figured out the order in which
methods got called inside the CDocument
class. Essentially when the
user tries to save a file, MFC command routing routes the message to
CDocument::OnFileSave
or CDocument::OnFileSaveAs
depending
on whether you clicked on Save or on Save-As. CDocument::OnFileSave
calls
CDocument::DoFileSave()
. CDocument::DoFileSave()
checks to see if the file
exists and if it does, it proceeds to call CDocument::DoSave
passing the full
path of the file, else it calls CDocument::DoSave
passing NULL
for the file
path. CDocument::OnFileSaveAs
simply calls CDocument::DoSave
passing
NULL
for
the file path. Thus eventually we end up in CDocument::DoSave
. So I decided
that this was the method to override. CDocument::DoSave
is declared thus :-
BOOL CDocument::DoSave(LPCTSTR lpszPathName, BOOL bReplace);
lpszPathName
:- This is the full path of the file to save. If
this is NULL
the default implementation will prompt the user for
a filename and path using the File-Save-As common dialog.
bReplace
:- If TRUE
it will replace an existing
file, if FALSE
it won't.
In my particular case I was least bothered with the working of the
DoSave
method. My intention was to get rid of this method totally. So
this is what I did - I overrode this member function and did not call the base
class implementation.
BOOL CBmpToXyzDoc::DoSave(LPCTSTR lpszPathName, BOOL bReplace)
{
CString DestPath = SrcPath;
DestPath.Replace("bmp","xyz");
OnSaveDocument(DestPath);
return TRUE;
}
That was just what I had wanted to accomplish. The user never gets prompted
and the file is saved using the same name as the original except for the change
in extension.
Other plausible applications
While I did not specifically require it for my project, I figured that
DoSave
can be used for some other purposes too. At least one nifty usage
came to my mind. Assume that I wanted to do different things based on some flag.
For example assume that I want to show a Save-As dialog with JPG filter if the current file
is a GIF and might want to show a Save-As dialog with GIF filter if the current
file is a JPG. If so, I could show my own CFileDialog
after setting
the corresponding OPENFILENAME
members.
BOOL CBmpToXyzDoc::DoSave(LPCTSTR lpszPathName, BOOL bReplace)
{
CFileDialog fd(false);
if(m_bgif)
{
fd.m_ofn.lpstrFilter="JPG Files(*.jpg)\0*.jpg\0\0";
fd.m_ofn.lpstrDefExt="jpg";
fd.m_ofn.lpstrTitle ="Save as JPG";
}
else
{
fd.m_ofn.lpstrFilter="GIF Files(*.gif)\0*.gif\0\0";
fd.m_ofn.lpstrDefExt="gif";
fd.m_ofn.lpstrTitle ="Save as GIF";
}
if(fd.DoModal()==IDOK)
{
if(m_bgif)
OnSaveJpgDocument(fd.GetPathName());
else
OnSaveGifDocument(fd.GetPathName());
}
return TRUE;
}
Tech notes
The CDocument::DoSave
implementation is very interesting. If
lpszPathName
is
NULL
, it calls CWinApp::DoPromptFileName
:-
if (!AfxGetApp()->DoPromptFileName(newName,
bReplace ? AFX_IDS_SAVEFILE : AFX_IDS_SAVEFILECOPY,
OFN_HIDEREADONLY | OFN_PATHMUSTEXIST, FALSE, pTemplate))
{
return FALSE;
}
CWinApp::DoPromptFileName
itself calls
CDocManager::DoPromptFileName
.
BOOL CWinApp::DoPromptFileName(CString& fileName,
UINT nIDSTitle,
DWORD lFlags,
BOOL bOpenFileDialog,
CDocTemplate* pTemplate)
{
ASSERT(m_pDocManager != NULL);
return m_pDocManager->DoPromptFileName(fileName,
nIDSTitle, lFlags, bOpenFileDialog, pTemplate);
}
CDocManager::DoPromptFileName
simply uses CFileDialog
to prompt for a
filename.
BOOL CDocManager::DoPromptFileName(CString& fileName,
UINT nIDSTitle,
DWORD lFlags,
BOOL bOpenFileDialog,
CDocTemplate* pTemplate)
{
CFileDialog dlgFile(bOpenFileDialog, NULL, NULL,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, NULL, NULL, 0);
CString title;
VERIFY(title.LoadString(nIDSTitle));
dlgFile.m_ofn.Flags |= lFlags;
INT_PTR nResult = dlgFile.DoModal();
Of course it does a lot of stuff in addition to just showing the file dialog.
For example it will append a *.* filter to your File dialogs, which is why in
addition to your document filter, you'll also see a *.* filter in the file type
drop-down combo-box. Knowing how the flow proceeds is handy in the sense that if
you want to customize it without hooking the window, you might simply override
CWinApp::DoPromptFileName
and call your own CFileDialog
there (remember that this will affect both Open and Save dialogs).