Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Automatic toolbar selection in MDI applications

0.00/5 (No votes)
2 Mar 2004 2  
How to change the toolbar when the current active child frame changes in a MDI application

Introduction

In this article you can find a good way to change the toolbar when the current active child frame changes in a MDI application.

Background

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.

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.

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.

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.

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.

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.

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:

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.

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.

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:

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 ?

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here