MDI 应用程序中的自动工具栏选择






4.56/5 (11投票s)
2004年2月26日
5分钟阅读

85046

1730
在 MDI 应用程序中,
引言
在本文中,您可以找到一种在 MDI 应用程序中更改当前活动子窗口框架时切换工具栏的有效方法。
背景
MDI(Multiple Document Interface,多文档界面)应用程序风格是在应用程序规模较大时最适合的选择。在这种应用程序中,用户可以与数据的多种不同视图进行交互。真正的 MDI 框架允许子窗口框架重叠,但任何时候只有一个子窗口框架是活动的。
主窗口框架的每个子窗口框架都可以关联一个不同的菜单栏。MFC 类提供了足够的功能来根据当前活动的子窗口框架更改主窗口框架的菜单栏。
通常,菜单中列出的命令也会复制到工具栏上。工具栏是一种更直观地显示命令的方式。如果您想为每个子窗口框架显示不同的工具栏,您需要编写代码,因为 MFC 并不提供此功能。有时,工具栏可以放在子窗口框架而不是主窗口框架中。当您使用 MDI 框架并始终最大化子窗口框架时,这是一个不错的选择,但当子窗口框架较小且相互重叠时,它看起来会很丑陋。过多的工具栏会对用户对应用程序的理解产生负面影响。
最好的方法是至多拥有一个工具栏,并将其停靠在主窗口框架中,但这需要一种机制来在活动子窗口框架更改时修改工具栏。
我在 MFC 框架和 Win32 API 中对 MDI 子窗口框架进行了大量研究,并在提出本文所述的解决方案之前尝试了许多其他方法。
一种可能的方法是覆盖 `CMDIChildWnd` 的 `ActivateFrame` 成员函数,并从那里向主窗口框架发送一条消息来选择所需的工具栏。如果我们使用一个派生自 `CMDIChildWnd` 的类作为我们子窗口框架的基础类,我们只需要一种方法来标识工具栏。子窗口框架与文档对象相关联,从而与文档模板对象相关联。子窗口框架可以获取文档模板标识符,并通过自定义消息将其发送到主窗口框架。当您实现此过程时,您会发现工具栏并未按预期更改,有时两个工具栏会同时可见。
在 MFC 类中,我看到了 MDI 菜单栏在活动子窗口框架更改时被替换的方式,从中我获得了更好的解决方案的基础。主窗口框架会在每次选择菜单栏时选择要显示的工具栏。在 MFC 中,这发生在空闲时间处理中。请记住,空闲时间处理仅在消息处理完毕后执行。这对于 GUI 来说已经足够了,因为活动子窗口框架的更改只能通过用户操作(如鼠标单击)来触发。
使用代码
解决方案是覆盖 `CFrameWnd` 的 `OnIdleUpdateCmdUI` 成员函数,该函数相对于主窗口框架。这个函数实际上是一个消息处理程序,而不是一个虚函数。相应的消息是 `WM_IDLEUPDATECMDUI`,这是 MFC 框架中定义的消息。要处理它,我们需要包含 `
class CMainFrame : public CMDIFrameWnd { . . . afx_msg void OnIdleUpdateCmdUI(); }; BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd) . ON_MESSAGE_VOID(WM_IDLEUPDATECMDUI, OnIdleUpdateCmdUI) END_MESSAGE_MAP() void CMainFrame::OnIdleUpdateCmdUI() { CMDIChildWnd* pChild = MDIGetActive(); if ( pChild ) { CView *pView = pChild->GetActiveView(); if ( pView ) { CDocument* pDoc = pView->GetDocument(); if ( pDoc ) { CDocTemplate* pDocTemplate = pDoc->GetDocTemplate(); if ( pDocTemplate ) { // Class to extract the m_nIDResource member class CHelperDocTemplate : public CDocTemplate { public: CHelperDocTemplate():CDocTemplate(0, NULL, NULL, NULL){} UINT GetResourceId(){return m_nIDResource;} }; CHelperDocTemplate* pHelper = ( CHelperDocTemplate*) pDocTemplate; UINT n = pHelper->GetResourceId(); if ( SelectToolBar(n) ) return; } } } } // No active view SelectToolBar(IDR_MAINFRAME); CMDIFrameWnd::OnIdleUpdateCmdUI(); }
在此成员函数中,主窗口框架获取活动子窗口框架,然后获取活动文档,接着获取文档的文档模板。然后,它从文档模板中获取文档模板资源标识符。这是通过一个小的辅助类实现的,因为基类 `CDocTemplate` 没有公共成员来获取该值。该技术与我的另一篇文章 “如何根据资源标识符查找文档模板” 中使用的技术相同。一旦获得资源标识符,主窗口框架就会选择要显示的工具栏。出于性能考虑,所有工具栏都应该已经创建,并且 `SelectToolbar` 成员函数将资源标识符映射到工具栏对象。在此示例中,这是通过一个简单的 `switch` 语句实现的,但我们可以使用包含工具栏的结构,如映射或其他类似结构。
重要的是,工具栏不会在不必要时被隐藏和显示,因此主窗口框架需要一个指示当前工具栏的成员变量,如果新工具栏是当前工具栏,则不执行任何操作。还需要另一个小指令来确保显示或隐藏工具栏的命令继续正常工作。
void CMainFrame::SelectToolBar(UINT nTemplateId) { UINT id = 0; switch(nResourceID) { case IDR_MAINFRAME: id = IDR_MAINFRAME; break; case IDR_DOC1: id = IDR_DOC1; break; case IDR_DOC2: id = IDR_DOC2; break; case IDR_DOC3: id = IDR_DOC3; break; } if ( id == 0 ) return FALSE; if ( id == m_CurrentToolBar ) return TRUE; CControlBar* pNewToolBar = GetControlBar(id); if ( ! pNewToolBar ) return FALSE; CControlBar* pCurToolBar = GetControlBar(AFX_IDW_TOOLBAR); if ( ! pCurToolBar ) return FALSE; // Change the toolbar dialog id pCurToolBar->SetDlgCtrlID(m_CurrentToolBar); pNewToolBar->SetDlgCtrlID(AFX_IDW_TOOLBAR); m_CurrentToolBar = id; // If the previous toolbar is not visible also the new toolbar // will not be visible BOOL bVisible = pCurToolBar->IsWindowVisible(); if ( bVisible ) { ShowControlBar(pCurToolBar, FALSE, TRUE); ShowControlBar(pNewToolBar, TRUE, FALSE); } return TRUE; }
结论
在此解决方案中,工具栏选择的责任完全由主窗口框架承担。主窗口框架在程序开始时(即在 `OnCreate` 消息处理程序中)创建所有工具栏,并且这些工具栏是主窗口框架的子项。每次需要时,主窗口框架将根据活动文档模板选择要显示的工具栏。请注意,同一个工具栏可以被多个文档模板重用。在 MFC 框架中,文档模板由一个资源标识符标识,该标识符同时选择字符串、菜单栏和图标资源。这意味着您不能在一组文档模板之间共享菜单栏,因为文档模板的资源标识符必须不同,否则它们将具有相同的字符串和图标。更好的解决方案是为文档模板提供单独的字符串、图标、菜单和工具栏资源标识符。这样的派生类将有一个如下的构造函数
class CMyDocTemplate : public CMultiDocTemplate { public: CMyDocTemplate( UINT stringId, UINT menuId, UINT iconId, UINT toolbarId, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass); };
并会覆盖 `LoadTemplate` 成员函数。新的文档模板类将被引入应用程序命名空间,以便主窗口框架可以直接获取工具栏标识符,而无需使用上述技巧。此外,这些文档模板还可以存储指向其工具栏的指针(如果需要),这样主窗口框架就不需要将工具栏标识符映射到工具栏对象。在我中等规模的应用程序中,我倾向于避免引入这个新的基类,并将所有工具栏相关的事务都放在主窗口框架类中。您会如何处理呢?