65.9K
CodeProject 正在变化。 阅读更多。
Home

CDocument::DoSave 揭秘

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (19投票s)

2002 年 9 月 19 日

CPOL

3分钟阅读

viewsIcon

192706

解释了如何在 Doc/View 应用程序中阻止文件-另存为对话框,如何将文件保存为多种格式,以及 DoSave 的实现方式。

引言

在我最近的一个项目中,我有一个相当奇怪的要求。 它基本上是一个图像转换程序,它允许您打开位图文件。 当用户单击工具栏中的保存图标,或者从文件菜单中选择保存项时,我需要以自定义格式保存图像,使用与原始位图相同的名称,但将 bmp 扩展名替换为自定义扩展名。 这意味着我应该阻止文件-另存为对话框。 最初我以为我只需要重写 OnSaveDocument 并避免调用基类,但我很快发现 OnSaveDocument 太晚了,无法阻止文件-另存为对话框。

DoSave 揭秘

我查看了 doccore.cpp,很快就弄清楚了方法在 CDocument 类中被调用的顺序。 基本上,当用户尝试保存文件时,MFC 命令路由将消息路由到 CDocument::OnFileSaveCDocument::OnFileSaveAs,具体取决于您单击的是“保存”还是“另存为”。 CDocument::OnFileSave 调用 CDocument::DoFileSave()CDocument::DoFileSave() 检查文件是否存在,如果存在,则调用 CDocument::DoSave 并传递文件的完整路径,否则它调用 CDocument::DoSave 并为文件路径传递 NULLCDocument::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 的实现非常有趣。 如果 lpszPathNameNULL,它会调用 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(请记住,这会影响打开和保存对话框)。

© . All rights reserved.