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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章