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

WTL for MFC Programmers, Part III - Toolbars and Status Bars

0.00/5 (No votes)
22 Dec 2005 4  
The basics of using toolbars and status bars in WTL applications.

Contents

Introduction to Part III

Ever since they were made into common controls in Windows 95, toolbars and status bars have become commonplace. MFC's support for multiple floating toolbars also helped their popularity. In later common controls updates, rebars (or coolbars as they were originally called) added another way to present toolbars. In Part III, I will cover how WTL supports these kinds of control bars and how to use them in your own apps.

Remember, if you encounter any problems installing WTL or compiling the demo code, read the readme section of Part I before posting questions here.

Toolbars and Status Bars in a Frame

CFrameWindowImpl has three HWND members that are set when the frame window is created. We've already seen m_hWndClient, which is the handle of the "view" window in the frame's client area. Now we'll encounter two others:

  • m_hWndToolBar: HWND of the toolbar or rebar
  • m_hWndStatusBar: HWND of the status bar

CFrameWindowImpl only supports one toolbar, and there is no equivalent to the MFC system of multiple dockable toolbars. If you need more than one toolbar, and don't want to hack around in CFrameWindowImpl internals, then you will need to use a rebar. I will cover both of these options and show how to select from them in the AppWizard.

The CFrameWindowImpl::OnSize() handler calls UpdateLayout(), which does two things: position the bars, and resize the view window to fill the client area. UpdateLayout() calls UpdateBarsPosition() which does the actual work. The code is quite simple, it just sends a WM_SIZE message to the toolbar and status bar, if those bars have been created. The bars' default window procedures take care of moving the bars to the top or bottom of the frame window.

When you tell the AppWizard to give your frame a toolbar and status bar, the wizard puts code to create the bars in CMainFrame::OnCreate(). Let's take a closer look at that code now, as we write yet another clock app.

AppWizard Code for Toolbars and Status Bars

We'll start a new project and have the wizard create a toolbar and status bar for our frame. Start a new WTL project called WTLClock2. On the first AppWizard page, choose an SDI app and check Generate CPP files:

 [AppWizard pg 1 - 22K]

 [VC7 AppWizard pg 1 - 24K]

On the next page, uncheck Rebar so the wizard creates a normal toolbar:

 [AppWizard pg 2 - 21K]

 [VC7 AppWizard pg 2 - 22K]

After copying the clock code from the app in Part II, the new app looks like this:

 [default toobar - 5K]

How CMainFrame creates the bars

The AppWizard puts more code in CMainFrame::OnCreate() in this project. Its job is to create the bars and tell CUpdateUI to update the toolbar buttons.

LRESULT CMainFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, 
                             LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    CreateSimpleToolBar();
    CreateSimpleStatusBar();

    m_hWndClient = m_view.Create(...);

// ...


    // register object for message filtering and idle updates

    CMessageLoop* pLoop = _Module.GetMessageLoop();
    ATLASSERT(pLoop != NULL);
    pLoop->AddMessageFilter(this);
    pLoop->AddIdleHandler(this);

    return 0;
}

The new code here is at the beginning. CFrameWindowImpl::CreateSimpleToolBar() creates a new toolbar using the toolbar resource IDR_MAINFRAME and stores its handle in m_hWndToolBar. Here is the code for CreateSimpleToolBar():

BOOL CFrameWindowImpl::CreateSimpleToolBar(
    UINT nResourceID = 0, 
    DWORD dwStyle = ATL_SIMPLE_TOOLBAR_STYLE, 
    UINT nID = ATL_IDW_TOOLBAR)
{
    ATLASSERT(!::IsWindow(m_hWndToolBar));
 
    if(nResourceID == 0)
        nResourceID = T::GetWndClassInfo().m_uCommonResourceID;
 
    m_hWndToolBar = T::CreateSimpleToolBarCtrl(m_hWnd, 
                     nResourceID, TRUE, dwStyle, nID);
    return (m_hWndToolBar != NULL);
}

The parameters are:

nResourceID
ID of the toolbar resource to use. The default of 0 means to use the ID specified in the DECLARE_FRAME_WND_CLASS macro. This is IDR_MAINFRAME in the wizard-generated code.
dwStyle
Styles for the toolbar. The default value ATL_SIMPLE_TOOLBAR_STYLE is defined as TBSTYLE_TOOLTIPS plus the usual child and visible styles. This makes the toolbar create a tooltip control for use when the cursor hovers over a button.
nID
Window ID for the toolbar, you will usually use the default value.

CreateSimpleToolBar() checks that a toolbar hasn't been created yet, then calls CreateSimpleToolBarCtrl() to actually create the control. The handle returned by CreateSimpleToolBarCtrl() is saved in m_hWndToolBar. CreateSimpleToolBarCtrl() reads the resource and creates toolbar buttons accordingly, then returns the toolbar's window handle. The code to do that is rather long, so I won't cover it here. You can find it in atlframe.h if you're interested.

The next call in OnCreate() is CFrameWindowImpl::CreateSimpleStatusBar(). This creates a status bar and stores its handle in m_hWndStatusBar. Here is the code:

BOOL CFrameWindowImpl::CreateSimpleStatusBar(
    UINT nTextID = ATL_IDS_IDLEMESSAGE, 
    DWORD dwStyle = ... SBARS_SIZEGRIP, 
    UINT nID = ATL_IDW_STATUS_BAR)
{
    TCHAR szText[128];    // max text lentgth is 127 for status bars

    szText[0] = 0;
    ::LoadString(_Module.GetResourceInstance(), nTextID, szText, 128);
    return CreateSimpleStatusBar(szText, dwStyle, nID);
}

This loads a string from the string table, which will be shown in the status bar. The parameters are:

nTextID
Resource ID of the string to be initially shown in the status bar. The AppWizard generates the string "Ready" with ID ATL_IDS_IDLEMESSAGE.
dwStyle
Styles for the status bar. The default includes SBARS_SIZEGRIP to have a resizing gripper added to the bottom-right corner.
nID
Window ID for the status bar, you will usually use the default value.

CreateSimpleStatusBar() calls the other overload to do the work:

BOOL CFrameWindowImpl::CreateSimpleStatusBar(
    LPCTSTR lpstrText,
    DWORD dwStyle = ... SBARS_SIZEGRIP,
    UINT nID = ATL_IDW_STATUS_BAR)
{
    ATLASSERT(!::IsWindow(m_hWndStatusBar));
    m_hWndStatusBar = ::CreateStatusWindow(dwStyle, lpstrText, m_hWnd, nID);
    return (m_hWndStatusBar != NULL);
}

This version checks that a status bar has not been created yet, then calls CreateStatusWindow() to create the status bar. The status bar's handle is then stored in m_hWndStatusBar.

Showing and hiding the bars

CMainFrame also has a View menu with two commands to show/hide the toolbar and status bar. These commands have IDs ID_VIEW_TOOLBAR and ID_VIEW_STATUS_BAR. CMainFrame has handlers for both commands that show or hide the corresponding bar. Here is the OnViewToolBar() handler:

LRESULT CMainFrame::OnViewToolBar(WORD /*wNotifyCode*/, WORD /*wID*/, 
                                  HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    BOOL bVisible = !::IsWindowVisible(m_hWndToolBar);
    ::ShowWindow(m_hWndToolBar, bVisible ? SW_SHOWNOACTIVATE : SW_HIDE);
    UISetCheck(ID_VIEW_TOOLBAR, bVisible);
    UpdateLayout();
    return 0;
}

This toggles the visible state of the bar, toggles the check mark next to the View|Toolbar menu item, then calls UpdateLayout() to position the bar (if it is becoming visible) and resize the view window.

Built-in features of the bars

MFC's framework provides some nice features in its toolbars and status bars, such as tooltips for toolbar buttons and flyby help for menu items. WTL has comparable features implemented in CFrameWindowImpl. Below are screen shots showing both the tooltip and flyby help.

 [Toolbar button tooltip - 4K]  [Status bar flyby help - 5K]

CFrameWindowImplBase has two message handlers that are used for these features. OnMenuSelect() handles WM_MENUSELECT, and it finds the flyby help string just like MFC does - it loads a string resource with the same ID as the currently-selected menu item, looks for a \n character in the string, and uses the text preceding the \n as the flyby help. OnToolTipTextA() and OnToolTipTextW() handle TTN_GETDISPINFOA and TTN_GETDISPINFOW respectively to provide tooltip text for toolbar buttons. Those handlers load the same string as OnMenuSelect(), but use the text following the \n. (As a side note, in WTL 7.0 and 7.1, OnMenuSelect() and OnToolTipTextA() are not DBCS-safe, as they do not check for DBCS lead/trail bytes when looking for the \n.) Here's an example of a toolbar button and its associated help string:

 [Toolbar button and help string - 9K]

Creating a toolbar with a different style

You can change the style for the toolbar by passing the style bits as the second parameter to CreateSimpleToolBar(). For example, to use 3D buttons and make the toolbar resemble the ones in Internet Explorer, use this code in CMainFrame::OnCreate():

    CreateSimpleToolBar ( 0, ATL_SIMPLE_TOOLBAR_STYLE | 
                               TBSTYLE_FLAT | TBSTYLE_LIST );

Note that if you use a common controls v6 manifest in your app, you don't have a choice with the buttons when running on Windows XP or later - toolbars always use flat buttons even if you don't specify the TBSTYLE_FLAT style when the toolbar is created.

The Toolbar Editor

The AppWizard creates several default buttons as we saw earlier. Only the About button is hooked up, however. You can edit the toolbar just as you do with an MFC project; the editor modifies the toolbar resource that CreateSimpleToolBarCtrl() uses to build the bar. Here's how the AppWizard-generated toolbar looks in the editor:

 [default toolbar in the editor - 7K]

For our clock app, we'll add two buttons to change the colors in the view window, and two buttons that show/hide the toolbar and status bar. Here's our new toolbar:

 [New toolbar buttons - 5K]

The buttons are:

  • IDC_CP_COLORS: Change the view to CodeProject colors
  • IDC_BW_COLORS: Change the view to black and white
  • ID_VIEW_STATUS_BAR: Show or hide the status bar
  • ID_VIEW_TOOLBAR: Show or hide the toolbar

The first two buttons also have items on the View menu. They call a new function in the view class, SetColors(). Each one passes a foreground and background color that the view will use for its clock display. Handling these two buttons is no different from handling menu items with the COMMAND_ID_HANDLER_EX macro; check out the sample project if you want to see the details of the message handlers. In the next section, I'll cover UI updating the View Status Bar and View Toolbar buttons so they reflect the current state of the bars.

UI Updating Toolbar Buttons

The AppWizard-generated CMainFrame already has UI update handlers that check and uncheck the View|Toolbar and View|Status Bar menu items. This is done just as the app in Part II - there are UI update macros for each command in CMainFrame:

BEGIN_UPDATE_UI_MAP(CMainFrame)
    UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP)
    UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP)
END_UPDATE_UI_MAP()

Our clock app has toolbar buttons with those same IDs, so the first step is to add the UPDUI_TOOLBAR flag to each macro:

BEGIN_UPDATE_UI_MAP(CMainFrame)
    UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
    UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
END_UPDATE_UI_MAP()

There are two other functions to call to hook up toolbar button updating, but the wizard-generated code calls them, so if you build the project at this point, the menu items and toolbar buttons will both be updated.

Enabling toolbar UI updating

If you look in CMainFrame::OnCreate(), you'll see a new block of code that sets up the initial state of the two View menu items:

LRESULT CMainFrame::OnCreate( ... )
{
// ...

    m_hWndClient = m_view.Create(...);
 
    UIAddToolBar(m_hWndToolBar);
    UISetCheck(ID_VIEW_TOOLBAR, 1);
    UISetCheck(ID_VIEW_STATUS_BAR, 1);
// ...

}

UIAddToolBar() tells CUpdateUI the HWND of our toolbar, so it knows what window to send messages to when it needs to update the button states. The other important call is in OnIdle():

BOOL CMainFrame::OnIdle()
{
    UIUpdateToolBar();
    return FALSE;
}

Recall that OnIdle() is called by CMessageLoop::Run() when it has no messages waiting in the message queue. UIUpdateToolBar() goes through the update UI map, looks for elements with the UPDUI_TOOLBAR flag that have been changed with calls such as UISetCheck(), and changes the state of the buttons accordingly. Note that we didn't need these two steps when we were just updating popup menu items, because CUpdateUI handles WM_INITMENUPOPUP and updates the menu when that message is sent.

If you check out the sample project, it also shows how to UI update top-level menu items in the frame's menu bar. There is an item that executes the Start and Stop commands to start and stop the clock. While this is not a common (or even recommended) thing to do -- items in the menu bar should always be popups -- I included it for the sake of completeness in covering CUpdateUI. Look for the calls to UIAddMenuBar() and UIUpdateMenuBar().

Using a Rebar Instead of a Plain Toolbar

CFrameWindowImpl also supports using a rebar to give your app a look similar to Internet Explorer. Using a rebar is also the way to go if you need multiple toolbars. To use a rebar, check the Rebar box in the second page of the AppWizard, as shown here:

 [AppWizard pg 2 with rebar checked - 21K]

 [VC7 AppWizard pg 2 with rebar checked - 22K]

The second sample project, WTLClock3, was made with this option checked. If you're following along in the sample code, open WTLClock3 now.

The first thing you'll notice is that the code to create the toolbar is different. This makes sense since we're using a rebar in this app. Here is the relevant code:

LRESULT CMainFrame::OnCreate(...)
{
    HWND hWndToolBar = CreateSimpleToolBarCtrl ( m_hWnd, 
                           IDR_MAINFRAME, FALSE, 
                           ATL_SIMPLE_TOOLBAR_PANE_STYLE );
 
    CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE);
    AddSimpleReBarBand(hWndToolBar);
// ...

}

It begins by creating a toolbar, but using a different style, ATL_SIMPLE_TOOLBAR_PANE_STYLE. This is a #define in atlframe.h that is similar to ATL_SIMPLE_TOOLBAR_STYLE, but has additional styles such as CCS_NOPARENTALIGN that are necessary for the toolbar to work properly as the child of the rebar.

The next line calls CreateSimpleReBar(), which creates a rebar and stores its HWND in m_hWndToolBar. Next, AddSimpleReBarBand() adds a band to the rebar, and tells the rebar that the toolbar will be contained in that band.

 [App with rebar - 4K]

CMainFrame::OnViewToolBar() is also different. Instead of hiding m_hWndToolBar (that would hide the entire rebar, not just the one toolbar), it hides the band that contains the toolbar.

If you want to have multiple toolbars, you can create them and call AddSimpleReBarBand() in OnCreate() just like the wizard-generated code does for the first toolbar. Since CFrameWindowImpl uses the standard rebar control, there is no support for dockable toolbars like in MFC; all the user can do is rearrange the positions of the toolbars within the rebar.

Multi-Pane Status Bars

WTL has another status bar class that implements a bar with multiple panes, similar to the default MFC status bar that has CAPS LOCK and NUM LOCK indicators. The class is CMultiPaneStatusBarCtrl, and is demonstrated in the WTLClock3 sample project. This class supports limited UI updating, as well as a "default" pane that stretches to the full width of the bar to show flyby help when a popup menu is displayed.

The first step is to declare a CMultiPaneStatusBarCtrl member variable in CMainFrame:

class CMainFrame : public ...
{
//...

protected:
    CMultiPaneStatusBarCtrl m_wndStatusBar;
};

Then in OnCreate(), we create the bar and set it up for UI updating:

    m_hWndStatusBar = m_wndStatusBar.Create ( *this );
    UIAddStatusBar ( m_hWndStatusBar );

Notice that we store the status bar handle in m_hWndStatusBar, just like CreateSimpleStatusBar() would.

The next step is to set up the panes by calling CMultiPaneStatusBarCtrl::SetPanes():

    BOOL SetPanes(int* pPanes, int nPanes, bool bSetText = true);

The parameters are:

pPanes
An array of pane IDs
nPanes
The number of elements in pPanes
bSetText
If true, all panes have their text set immediately. This is explained below.

The pane IDs can either be ID_DEFAULT_PANE to create the flyby help pane, or IDs of strings in the string table. For the non-default panes, WTL loads the string with the matching ID and calculates its width, then sets the corresponding pane to the same width. This is the same logic that MFC uses.

bSetText controls whether the panes show the strings immediately. If it is set to true, SetPanes() shows the strings in each pane, otherwise the panes are left blank.

Here's our call to SetPanes():

    // Create the status bar panes.

int anPanes[] = { ID_DEFAULT_PANE, IDPANE_STATUS, 
                  IDPANE_CAPS_INDICATOR };
 
    m_wndStatusBar.SetPanes ( anPanes, 3, false );

The string IDPANE_STATUS is "@@@@", which should (hopefully) be enough space to show the two clock status strings "Running" and "Stopped". Just as with MFC, you have to approximate how much space you'll need for the pane. The string IDPANE_CAPS_INDICATOR is "CAPS".

UI updating the panes

In order for us to update the pane text, we'll need entries in the update UI map:

    BEGIN_UPDATE_UI_MAP(CMainFrame)
        //...

        UPDATE_ELEMENT(1, UPDUI_STATUSBAR)  // clock status

        UPDATE_ELEMENT(2, UPDUI_STATUSBAR)  // CAPS indicator

    END_UPDATE_UI_MAP()

The first parameter in the macro is the index of the pane, not its ID. Watch out for this if you rearrange the panes - if not every pane has an entry in this map, you will need to update the numbers in the UPDATE_ELEMENT macros to match the new order.

Since we pass false as the third parameter to SetPanes(), the panes are initially empty. Our next step is to set the initial text of the clock status pane to "Running".

    // Set the initial text for the clock status pane.

    UISetText ( 1, _T("Running") );

Again, the first parameter is the index of the pane. UISetText() is the only UI update call that works on status bars.

Finally, we need to add a call to UIUpdateStatusBar() in CMainFrame::OnIdle() so that the status bar panes are updated at idle time:

BOOL CMainFrame::OnIdle()
{
    UIUpdateToolBar();
    UIUpdateStatusBar();
    return FALSE;
}

There is a problem in CUpdateUI that appears when you use UIUpdateStatusBar() - text in menu items is not updated after you use UISetText()! If you look at the WTLClock3 project, the clock start/stop menu item has been moved to a Clock menu, and the handler for that command sets the menu item's text. However, if the call to UIUpdateStatusBar() is present, the UISetText() call does not take effect. This bug is still present in WTL 7.1; some folks have investigated and come up with fixes - see the discussion forum at the end of the article for more details.

Finally, we need to check the state of the CAPS LOCK key and update pane 2 accordingly. This code can go right in OnIdle(), so the state will get checked every time the app goes idle.

BOOL CMainFrame::OnIdle()
{
    // Check the current Caps Lock state, and if it is on, show the

    // CAPS indicator in pane 2 of the status bar.

    if ( GetKeyState(VK_CAPITAL) & 1 )
        UISetText ( 2, CString(LPCTSTR(IDPANE_CAPS_INDICATOR)) );
    else
        UISetText ( 2, _T("") );
 
    UIUpdateToolBar();
    UIUpdateStatusBar();
    return FALSE;
}

The first UISetText() call loads the "CAPS" string from the string table using a neat (but fully documented) trick in the CString constructor.

So after all that code, here's what the status bar looks like:

 [Multipane status bar - 4K]

Up Next: All About Dialogs

In Part IV, I'll cover dialogs (both the ATL classes and WTL enhancements), control wrappers, and more WTL message handling improvements that relate to dialogs and controls.

References

"How to use the WTL multipane status bar control" by Ed Gadziemski goes into more detail on the CMultiPaneStatusBarCtrl class.

Copyright and license

This article is copyrighted material, (c)2003-2005 by Michael Dunn. I realize this isn't going to stop people from copying it all around the 'net, but I have to say it anyway. If you are interested in doing a translation of this article, please email me to let me know. I don't foresee denying anyone permission to do a translation, I would just like to be aware of the translation so I can post a link to it here.

The demo code that accompanies this article is released to the public domain. I release it this way so that the code can benefit everyone. (I don't make the article itself public domain because having the article available only on CodeProject helps both my own visibility and the CodeProject site.) If you use the demo code in your own application, an email letting me know would be appreciated (just to satisfy my curiosity about whether folks are benefiting from my code) but is not required. Attribution in your own source code is also appreciated but not required.

Revision History

  • April 11, 2003: Article first published.
  • December 20, 2005: Updated to cover changes in VC 7.1.

Series Navigation: � Part II (WTL GUI Base Classes) | � Part IV (Dialogs and Controls)

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