只允许在 MFC MDI 应用程序中打开一个 CDocument

Allow only one CDocument to be opened in an MFC MDI app

如问题所述,我试图在 MFC MDI 应用程序中一次只允许一个文档。由于许多(不相关的)原因,我没有使用 SDI 文档模板。很多地方评论说这样做,但我不知道如何做。最接近的是 link: http://computer-programming-forum.com/82-mfc/06d5cebffaeefeae.htm 中的建议,但它不适用于 CWinAppEx - 文档已关闭,即使用户取消了“文件打开”对话框。此外,使用 MRU 列表或工具栏按钮会绕过此建议。

非常感谢任何帮助!


BOOL CMyDoc::closeDocument()
{
    if (!SaveModified())
    {
        // User has vetoed the close, return error
        return TRUE;
    }
    else
    {
        // OK to close
        OnCloseDocument();
        return FALSE;
    }
}

在 CMyApp 中:

void CMyApp::OnFileOpen()
{
    CMyDoc* pDoc = CMyDoc::GetDoc();
    if (pDoc != NULL && pDoc->closeDocument())
        // user has vetoed the close - can't create new one
        return;
    // no document currently open, or we succesfully closed it    

    CWinAppEx::OnFileOpen();
}


void CMyApp::OnFileNew()
{
    CMyDoc* pDoc = CVATDoc::GetDoc();
    if (pDoc != NULL && pDoc->closeDocument())
        // user has vetoed the close - can't create new one
        return;
    // no document currently open, or we succesfully closed it
    CWinAppEx::OnFileOpen();
}

虽然这在旧版本的 MFC 中可能有效,但现在似乎无效 (VS2013)。在用户选择(或取消)新文档之前关闭文档。

所以我有一个有效的解决方案,我认为解释它很有用。我希望这对某人有用。我花了很长时间研究 MFC(这是一件好事),现在明白为什么上面的解决方案 link 不再是 MFC9 中的解决方案。

文件打开命令

来自最近使用列表

调用栈是这样的:

CWinApp::OnOpenRecentFile(UINT nID) // Non-virtual – cannot override
CWinApp::OpenDocumentFile(LPCTSTR lpszFileName) // Virtual – can override in CMyApp
CDocManager::OpenDocumentFile(LPCTSTR lpszFileName)
CDocManager::OpenDocumentFile(LPCTSTR lpszFileName, BOOL bAddToMRU)
CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName, BOOL bAddToMRU, BOOL bMakeVisible); // here is the actual creation of the new frame etc

所以唯一可以挂钩的地方是覆盖 CMyApp class.

中的 OpenDocumentFile

来自对话框

这更容易 - 有多个地方可以挂接,但在开始时挂接将不允许用户显示文件打开对话框并取消它,同时保持现有文档打开。

CMyApp::OnFileOpen(); // override
CWinAppEx::OnFileOpen(); // call base class
CDocManager::OnFileOpen(); // This one is key – in here the File Open dialog is shown
CMyApp::OpenDocumentFile(LPCTSTR lpszFileName) // And here we can hook into the call stack
CWinAppEx::OpenDocumentFile(lpszFileName);
CDocManager::OpenDocumentFile(LPCTSTR lpszFileName)
CDocManager::OpenDocumentFile(LPCTSTR lpszFileName, BOOL bAddToMRU)
CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName, BOOL bAddToMRU, BOOL bMakeVisible) // here is the actual creation of the new frame etc

文件新命令

这个比较棘手,因为一旦 OnFileNew 被调用就没有钩子了。

CMyApp::OnFileNew(); // virtual override
CWinAppEx::OnFileNew(); // base class
CDocManager::OnFileNew(); // this calls the doc template without another hook back into our derived classes
CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName, BOOL bAddToMRU, BOOL bMakeVisible)

解决方案

一个解决方案是派生我们自己的 DocTemplate class 并重写一些功能,但这是一项艰巨的工作。 'hackier'方法如下

正在打开一个文件

此挂钩适用于 MRU 和对话命令 - 请参阅上面的调用堆栈。文件打开对话框已经显示并选择了文件。如果文件打开对话框被取消,我们就不会到达这里。

CDocument* CMyApp::OpenDocumentFile(LPCTSTR lpszFileName)
{
    CMyDoc* pDoc = CMyDoc::GetDoc(); // static func that gets pointer to MyDoc
    if (pDoc != NULL && pDoc->closeDocument())
        // user has vetoed the close - can't create new one
        return NULL; // here we prevent opening by by-passing base class call
    return CWinAppEx::OpenDocumentFile(lpszFileName);
}

新文件

这里因为没有要取消的对话框,我们可以在开始时挂钩,如果用户不允许关闭现有文档,我们可以阻止创建新文件。

void CMyApp::OnFileNew()
{
    CMyDoc* pDoc = CMyDoc::GetDoc();
    if (pDoc != NULL && pDoc->closeDocument())
        // user has vetoed the close - can't create new one
        return;
    // no document currently open, or we succesfully closed it
    CWinAppEx::OnFileNew();
}

最后,closeDocument()函数按照上面的link:

BOOL CMyDoc::closeDocument()
{
    if (!SaveModified())
    {
        // User has vetoed the close, return error
        return TRUE;
    }
    else
    {
        // OK to close
        OnCloseDocument();
        return FALSE;
    }
}

有一种更简单的方法,不需要挂钩所有这些函数。将一些 ON_UPDATE_COMMAND_UI() 消息映射处理程序添加到您的应用程序对象。具体来说,ID_FILE_NEW 和 ID_FILE_OPEN。当您有一个打开的文档时,请禁用该命令。这是一些代码。不能保证有效,但应该可以帮助您。

ON_UPDATE_COMMAND_UI(ID_FILE_NEW, SomeUpdateHandler)
ON_UPDATE_COMMAND_UI(ID_FILE_OPEN, SomeUpdateHandler)

void CMyApp::SomeUpdateHandler(CCmdUI* pCmdUI)
{
    POSITION pos = GetFirstDocTemplatePosition();

    CDocTemplate* pTemplate = GetNextDocTemplate(pos);

    POSITION posDoc = pTemplate->GetFirstDocPosition();
    pCmdUI->Enable(posDoc != NULL);
}

如果您有打开的文档,则文件-新建和文件-打开将被禁用。关闭文档后,您可以打开或创建一个新文档。