CDocument::DoSave 揭秘






4.80/5 (19投票s)
解释了如何在 Doc/View 应用程序中阻止文件-另存为对话框,如何将文件保存为多种格式,以及 DoSave 的实现方式。
引言
在我最近的一个项目中,我有一个相当奇怪的要求。 它基本上是一个图像转换程序,它允许您打开位图文件。 当用户单击工具栏中的保存图标,或者从文件菜单中选择保存项时,我需要以自定义格式保存图像,使用与原始位图相同的名称,但将 bmp 扩展名替换为自定义扩展名。 这意味着我应该阻止文件-另存为对话框。 最初我以为我只需要重写 OnSaveDocument
并避免调用基类,但我很快发现 OnSaveDocument
太晚了,无法阻止文件-另存为对话框。
DoSave 揭秘
我查看了 doccore.cpp,很快就弄清楚了方法在 CDocument
类中被调用的顺序。 基本上,当用户尝试保存文件时,MFC 命令路由将消息路由到 CDocument::OnFileSave
或 CDocument::OnFileSaveAs
,具体取决于您单击的是“保存”还是“另存为”。 CDocument::OnFileSave
调用 CDocument::DoFileSave()
。 CDocument::DoFileSave()
检查文件是否存在,如果存在,则调用 CDocument::DoSave
并传递文件的完整路径,否则它调用 CDocument::DoSave
并为文件路径传递 NULL
。 CDocument::OnFileSaveAs
只是调用 CDocument::DoSave
并为文件路径传递 NULL
。 因此,最终我们会进入 CDocument::DoSave
。 所以我决定这就是要重写的方法。 CDocument::DoSave
的声明如下:-
BOOL CDocument::DoSave(LPCTSTR lpszPathName, BOOL bReplace);
lpszPathName
:- 这是要保存的文件的完整路径。 如果这是NULL
,则默认实现将提示用户使用文件-另存为通用对话框输入文件名和路径。bReplace
:- 如果TRUE
,它将替换现有文件,如果FALSE
,它不会。
在我的特定情况下,我最不关心 DoSave
方法的工作方式。 我的意图是完全摆脱这个方法。 所以这就是我所做的 - 我重写了这个成员函数,并且没有调用基类实现。
BOOL CBmpToXyzDoc::DoSave(LPCTSTR lpszPathName, BOOL bReplace) { //SrcPath is the full path of the current file CString DestPath = SrcPath; //I replace the extension with the custom one DestPath.Replace("bmp","xyz"); //Now I simply call OnSaveDocument OnSaveDocument(DestPath); //File saved successfully return TRUE; }
这正是我想要完成的。 用户永远不会收到提示,并且文件以与原始文件相同的名称保存,除了扩展名中的更改。
其他可能的应用
虽然我的项目没有特别需要它,但我认为 DoSave
也可以用于其他目的。 至少我想到了一种巧妙的用法。 假设我想根据一些标志来做不同的事情。 例如,假设如果当前文件是 GIF,我想显示一个带有 JPG 过滤器的“另存为”对话框,如果当前文件是 JPG,可能想显示一个带有 GIF 过滤器的“另存为”对话框。 如果是这样,我可以在设置相应的 OPENFILENAME
成员后显示我自己的 CFileDialog
。
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; }
技术说明
CDocument::DoSave
的实现非常有趣。 如果 lpszPathName
为 NULL
,它会调用 CWinApp::DoPromptFileName
:-
if (!AfxGetApp()->DoPromptFileName(newName, bReplace ? AFX_IDS_SAVEFILE : AFX_IDS_SAVEFILECOPY, OFN_HIDEREADONLY | OFN_PATHMUSTEXIST, FALSE, pTemplate)) { return FALSE; }
CWinApp::DoPromptFileName
本身会调用 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
只是使用 CFileDialog
来提示输入文件名。
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(); //...
当然,除了显示文件对话框之外,它还做了很多事情。 例如,它会在您的文件对话框中附加一个 *.* 过滤器,这就是为什么除了您的文档过滤器之外,您还会在文件类型下拉组合框中看到一个 *.* 过滤器。 了解流程的进行方式非常有用,因为如果您想在不挂钩窗口的情况下对其进行自定义,您可以简单地重写 CWinApp::DoPromptFileName
并在那里调用您自己的 CFileDialog
(请记住,这会影响打开和保存对话框)。