MDI应用中的工具栏的自动选择(MDI 工具栏 切换)

 /*****************    http://blog.csdn.net/elysium    *************************/

有感于MDI架构的菜单工具栏的切换实现的问题,从codeproject搜到了该篇文章,甚合我意,由此产生了翻译成中文的念头,以使流传。附原贴地址,以供E文爱好者以及读者参考:http://www.codeproject.com/docview/afautotoolbar.asp

该文在作者的website也有收录: www.softwaresphere.com/articles

MDI应用中的工具栏的自动选择(Alessandro Forcella)

摘要:MDI应用中,当前子框架改变时,如何改变工具栏。

 

 

  • 绪论

    In this article you can find a good way to change the toolbar when the current active child frame changes in a MDI application. 在这篇文章中你能发现一个好方法来在MDI应用程序当前活动子框架改变时改变工具栏。

    背景

    The MDI, Multiple Document Interface, application style is the best choice when the application must be not so small. In this kind of application the user interacts with many different views of the data. The true MDI framework allows the child frame windows to be overlapped, anyway only one child frame is active at any time. 当应用程序很可能不小时,MDI(Multiple Document Interface)程序风格是最好的选择。在这种应用中,用户组织许多不同的数据视图。MDI架构允许子框架窗口重叠,无论如何,在同一时刻只能有一个子框架是活动的。

    Each child frame of the main frame can have a different menu bar associated to it. The MFC classes provide enough functionalities for changing the main frame menu bar accordingly to the current active child frame. 主框架中的每一个子框架都可以有一个不同的菜单栏与之关联。为了依照当前活动子框架而改变主框架菜单栏,MFC类提供了充足的函数功能。

  • Usually the commands listed in the menus are replicated also in the toolbar. The toolbar is a more graphical way to show the commands. If you want to display a different toolbar for each child frame you have to write the code because MFC doesn't provide it. Sometimes a toolbar can be placed in the child frame instead of the main frame. This is a good solution when you use the MDI framework with the child frames always in the maximized state but it looks ugly when the child frames are smaller and overlapped each other. Too many toolbars have a negative effect on the user's understanding of the application. 通常菜单中列出的命令可以被工具栏复制。工具栏以更为图形化的方式显示命令。如果你想为每一个子框架显示一个不同的工具栏,你必须自己写代码,因为MFC不提供这种实现。有时候工具栏可以放到子框架中以替代主框架,当使用MDI平台并且子框架总是最大化时这是一个好的解决方法,但是当这些子框架较小并且互相重叠时,看起来却是很难看。太多的工具栏使用户对应用程序的理解会产生负面影响。

  • The best way is to have at most one toolbar, docked in the main frame, but this requires a mechanism to modify the toolbar when the active child changes. 最好的办法是在主框架上最多停靠一个工具栏,这就需要一种机制用于当活动子框架改变时来修改工具栏。

    I searched a lot in the MFC framework and also in the Win32 API about MDI child frames, and I tried many other solutions before arriving at the one explained in this article. 我曾就MDI子框架在MFC平台以及Win32 API方面查找过许多资料,并且在解释这篇文章之前尝试了多种其它的解决方案。

    A possible way is to override the ActivateFrame member function of CMDIChildWnd and from there to send a message to the main frame for selecting the required toolbar. If we use a CMDIChildWnd derived class as base class for our child frames we only need a way to identify the toolbar. A child frame is attached to a document object thus to a doc template object. The child frame can get the doctemplate identifier and send it to the main frame in a custom message. When you implement this procedure you can see that the toolbar is not changed as we expect, sometimes two toolbars remain visible at the same time. 一个可能的方法是重载CMDIChildWnd类的ActivateFrame 成员函数,从中为选择所需的工具栏向主框架发送一个消息。如果为我们的子框架使用CMDIChildWnd继承类作为基类,我们只需要标识工具栏即可。子框架附于文档对象以至于文档模板对象。子框架能够得到文档模板标识符,并且将它通过自定义消息发送到主框架。当你执行这个过程时,你会看到工具栏没有像我们预期的那样被改变,有时候两个工具栏同时保持可见。

    I got the basis for a better solution seeing how the MDI menu bar is replaced when the active child frame changes in the MFC classes. The main frame will select the toolbar to show each time it selects the menu bar. In MFC this happens in the idle time processing. Remember that the idle time processing is executed only after a message has been pumped. This is enough for a GUI because the active child frame can change only for a user action like a mouse click.通过了解在MFC类中,当活动子框架改变时,MDI菜单栏是如何被替换的,我为更好的解决方案找到了依据。主框架每次选择菜单栏时也会选择工具栏来显示。在MFC中,它发生在空闲时间的过程中。请记住空闲时间过程仅在一个消息被输送(泵)后才被执行。这对GUI来说足够了,因为活动子框架的改变仅缘于类似鼠标单击这样的用户行为。

    Using the code

    The solution is to override the OnIdleUpdateCmdUI member function of CFrameWnd relatively to the main frame. This function is really a message handler, not a virtual function. The corresponding message is WM_IDLEUPDATECMDUI which is a message defined in the MFC framework. To handle it we need to include the <afxpriv.h> header. It's better to place the header in the StdAfx.h header of the project so it will be precompiled together with the other MFC headers we are using. So in our CMDIFrameWnd derived class we write: 该解决方案是继承对应于主框架的CFrameWnd的OnIdleUpdateCmdUI成员函数。这个函数是一个真正的消息句柄,不是虚函数。相应的消息时WM_IDLEUPDATECMDUI,这是一个在MFC架构中定义的消息。为了掌控它,我们需要包含<afxpriv.h>头文件,将这个头文件放到StdAfx.h头文件里更好一些,这样它将和我们所用的其他MFC头文件一起被编译。接下来在我们的CMDIFrameWnd继承类中,我们这样写:

     Collapse
    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(); }

    In this member function the main frame gets the active child, then the active document and then the doctemplate of the document. Then it obtains the doctemplate resource identifer from the doctemplate. This is done with a little helper class because the base class CDocTemplate does not have a public member to get the value. The technique is the same used in another article of mine, "How to find a doctemplate given the resource identifier". Once obtained the resource identifier the mainframe chooses the toolbar to show. For performance reasons all the toolbars should have been already created, and the SelectToolbar member function maps the resource identifier to a toolbar object. In this example this is made by a simple switch statement but we could use a structure containing the toolbars, like a map or other similar structure. 在此成员函数中,主框架得到活动子框架,然后是活动文档以及该文档的文档模板。接下来从文档模板中得到文档模板资源标识符。这由一个小的帮助类来完成,因为这个CDocTemplate基类没有一个公共成员来获取该值。这个技巧同样的被用在我的另一篇文章中-"How to find a doctemplate given the resource identifier"。一旦取得资源标识符,主框架选择工具栏来显示。基于演示,所有的工具栏已经被创建,并且SelectToolbar成员函数映射资源标识符到工具栏对象。在本例中,由一个简单的switch结构来完成,但是我们可以使用一个结构体来包含工具栏,象map或者其他类似的结构。

    It is important that the toolbar will not be hidden and the showed if it is not necessary so the main frame needs a member variable which indicates the current toolbar, and if the new toolbar is the current toolbar, nothing will be done. Another little instruction is required to ensure that the command which shows or hides the toolbar continues to work. 重要的是工具栏还不能隐藏和显示,即使它是不必要的(???),因此主框架需要一个成员变量用来指示当前工具栏,并且如果新工具栏就是当前工具栏,那么就什么也不用做。另一点需要说明的是要确保显示或隐藏工具栏的命令能够持续运行。

     Collapse
    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;

    }

    Conclusion(结束语)

    In this solution the responsibility for toolbar selection is entirely of the main frame. The main frame creates all the toolbars at the beginning of the program, i.e. in the OnCreate message handler, and the toolbars are children of the main frame. Every time it is needed the main frame will select one toolbar to show, basing upon the active document template. Let's notice that the same toolbar can be reused for more doctemplates. In the MFC framework the doctemplates are identified by a single resource identifier which selects the string, the menu bar and the icon resource at the same time. This means you can't share a menu bar among a set of doctemplates because the resource identifiers of the doctemplates must be different or they will have also the same string and icon. A better solution is to give separate identifiers for string, icon, menu and also toolbar resource to the doctemplate. Such a derived class would have a constructor like this: 此篇中主框架肩负了工具栏选择的全部职责。在程序开始主框架创建所有的工具栏,例如在OnCreate消息句柄中,并且工具栏是主框架的子控件。每次需要由主框架根据活动文档模板选择一个工具栏。我们注意到相同的工具栏可以被多个文档模板复用。在MFC框架中,文档模板被同时选择的字符串、菜单栏以及图标资源作为唯一标识符所标识。这就意味着你不能在一个文档模板集中共享一个菜单栏,因为资源标识符必须是不同的,否则他们将也有相同的字符串和图标。一个更好的解决方案是将字符串、图标、菜单以及工具栏资源作为单独的标识符给文档模板。这样的一个继承类有如下结构:

    class CMyDocTemplate : public CMultiDocTemplate
    {
    public:
    CMyDocTemplate(
    UINT stringId,
    UINT menuId,
    UINT iconId,
    UINT toolbarId,
    CRuntimeClass* pDocClass,
    CRuntimeClass* pFrameClass,
    CRuntimeClass* pViewClass);
    };

    and would override the LoadTemplate member function. The new doctemplate class would be introduced in the application namespace so the main frame can get the toolbar identifier directly, without using the trick above. Furthermore these doctemplates could also store a pointer to their toolbar if they need one so the main frame doesn't need to map toolbar identifiers to toolbar objects. In my middle-size applications I prefer to avoid the introduction of this new base class and I put all the toolbar stuff in the main frame class. How would you do this instead ? 若重载LoadTemplate成员函数,新的文档模板类将被引入应用程序的命名空间,因此主框架能够直接获取工具栏的ID,无须使用上述诀窍,此外那些文档模板也能够存储一个指针到他们的工具栏(如果需要一个指针的话),那么主框架不再需要映射工具栏标识到工具栏对象。在我的中型的应用程序中,我更喜欢避免这种新基类的入门,我会将所有工具栏素材安插进主框架类。换了你该怎么做呢?

  • Alessandro Forcella


  • 点击 这里  了解 Alessandro Forcella 在线信息

Elysium's BLOG:  http://blog.csdn.net/elysium

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值