Toolbars

[Previous] [Next]

A toolbar's purpose is to provide one-click access to commonly used commands. Toolbar buttons typically serve as shortcuts for menu commands, but they can also implement commands that don't appear in a menu. MFC's CToolBar class takes a bitmap resource containing images for the faces of the toolbar buttons and an array of button IDs and creates a toolbar object that docks to the side of a frame window or floats in its own mini frame window. Toolbar buttons are assigned command IDs just as menu items are. Clicking a toolbar button produces a WM_COMMAND message just as if a menu item had been selected. If a menu item and a toolbar button are assigned the same command ID, one command handler can serve them both. With a little work, you can add combo boxes, check boxes, and other non-push-button controls to a toolbar. You can also convert ordinary push buttons into "check push buttons" that stay up or down when clicked or "radio push buttons" that work like radio buttons. MFC provides functions for hiding and displaying toolbars, saving and restoring toolbar states, and much more.

In early versions of MFC, CToolBar was a stand-alone class whose functionality came entirely from MFC. Today, CToolBar derives much of its functionality from the toolbar control in Comctl32.dll. A separate and more primitive MFC class named CToolBarCtrl provides an MFC interface to toolbar controls. That's useful to know, because if you want to do something with a CToolBar and can't find a suitable member function, CToolBarCtrl might have the member function you're looking for. You can call CToolBarCtrl functions on a CToolBar if you first call CToolBar::GetToolBarCtrl to acquire a CToolBarCtrl reference to the underlying control. Most of the time, however, CToolBar will do everything you need and then some. With that in mind, let's see what it takes to get a CToolBar up and running.

Creating and Initializing a Toolbar

You create a toolbar by constructing a CToolBar object and calling CToolBar::Create. Because a toolbar is a child of the application's main frame window and is normally created when the frame window is created, the usual practice is to add a CToolBar member to the frame window class and call Create from the frame window's OnCreate handler. If m_wndToolBar is a CToolBar data member, the statement

 m_wndToolBar.Create (this); 

creates a toolbar that is a child of this. Two parameters are implicit in the call: the toolbar's style and its child-window ID. The default style is WS_CHILD ¦ WS_VISIBLE ¦ CBRS_TOP. You can change the toolbar style by adding a second parameter to Create or by calling the SetBarStyle function that a toolbar inherits from its base class, CControlBar, after the toolbar is created. For example, to replace CBRS_TOP with CBRS_BOTTOM so that the toolbar aligns itself along the bottom of its parent, you could create it like this:

 m_wndToolBar.Create (this, WS_CHILD ¦ WS_VISIBLE ¦ CBRS_BOTTOM); 

Or you could create it like this:

 m_wndToolBar.Create (this); m_wndToolBar.SetBarStyle ((m_wndToolBar.GetBarStyle () &     ~CBRS_TOP) ¦ CBRS_BOTTOM); 

CToolBar::Create also accepts an optional third parameter specifying the toolbar ID. The default is AFX_IDW_TOOLBAR. There's no need to change the toolbar ID unless you write an application that contains two or more toolbars. In a multitoolbar application, you should assign each toolbar a unique ID.

A freshly created toolbar is empty, so the next step is to add buttons to it. One way to add buttons is to call CToolBar::LoadBitmap to load a bitmap resource containing images for the button faces and CToolBar::SetButtons to tell the toolbar how many buttons it will have and what the buttons' command IDs are. The following statements create a toolbar and initialize it with the images stored in the bitmap resource IDR_TOOLBAR and the IDs in the array nButtonIDs. The special ID_SEPARATOR value places a small gap a few pixels wide between buttons.

// In the RC file IDR_TOOLBAR BITMAP Toolbar.bmp // In the CPP file static UINT nButtonIDs[] = {     ID_FILE_NEW,     ID_FILE_OPEN,     ID_FILE_SAVE,     ID_SEPARATOR,     ID_EDIT_CUT,     ID_EDIT_COPY,     ID_EDIT_PASTE,     ID_EDIT_UNDO,     ID_SEPARATOR,     ID_FILE_PRINT }; m_wndToolBar.Create (this); m_wndToolBar.LoadBitmap (IDR_TOOLBAR); m_wndToolBar.SetButtons (nButtonIDs, 10); 

The bitmap resource contains all of the toolbar button images, positioned end to end like frames in a filmstrip, as shown in Figure 12-1. By default, each image is 16 pixels wide and 15 pixels high. The button itself measures 24 pixels by 22 pixels. You can change both the image size and the button size with CToolBar::SetSizes. Drawing professional-looking toolbar buttons requires a little artistic flair, but for standard items such as New, Open, Save, Cut, Copy, Paste, and Print, you can borrow images from the Toolbar.bmp bitmap supplied with Visual C++.

click to view at full size.

Figure 12-1. Toolbar images and a toolbar created from them.

A second method for creating the toolbar buttons is to add a TOOLBAR resource describing the button IDs and image sizes to the application's RC file and call CToolBar::LoadToolBar with the resource ID. The following statements create and initialize a toolbar that is identical to the one in the previous paragraph:

 // In the RC file IDR_TOOLBAR BITMAP Toolbar.bmp IDR_TOOLBAR TOOLBAR 16, 15 BEGIN     BUTTON ID_FILE_NEW     BUTTON ID_FILE_OPEN     BUTTON ID_FILE_SAVE     SEPARATOR     BUTTON ID_EDIT_CUT     BUTTON ID_EDIT_COPY     BUTTON ID_EDIT_PASTE     BUTTON ID_EDIT_UNDO     SEPARATOR     BUTTON ID_FILE_PRINT END // In the CPP file m_wndToolBar.Create (this); m_wndToolBar.LoadToolBar (IDR_TOOLBAR); 

When you use a TOOLBAR resource, you can change the image size simply by changing the numbers in the resource statement. LoadToolBar loads the toolbar images, sets the button IDs, and sets the button sizes all in one step. When you ask AppWizard to include a toolbar in an application, it uses this method to define the toolbar.

Fortunately, you don't have to create and edit TOOLBAR resources by hand. When AppWizard adds a toolbar to an application, it creates a TOOLBAR resource and a bitmap to go with it. You can also add TOOLBAR resources to a project with Visual C++'s Insert-Resource command. Once it's added, a TOOLBAR resource and its button bitmaps can be edited visually in Visual C++'s resource editor.

By default, toolbar buttons contain images but not text. You can add text strings to the faces of the buttons with CToolBar::SetButtonText. After you've specified the text of each button, use CToolBar::SetSizes to adjust the button sizes to accommodate the text strings. The following statements create a toolbar from IDR_TOOLBAR and add descriptive text to each button face:

 // In the RC file IDR_TOOLBAR BITMAP Toolbar.bmp IDR_TOOLBAR TOOLBAR 40, 19      // In the CPP file m_wndToolBar.Create (this); m_wndToolBar.LoadToolBar (IDR_TOOLBAR); m_wndToolBar.SetButtonText (0, _T ("New")); m_wndToolBar.SetButtonText (1, _T ("Open")); m_wndToolBar.SetButtonText (2, _T ("Save")); m_wndToolBar.SetButtonText (4, _T ("Cut")); m_wndToolBar.SetButtonText (5, _T ("Copy")); m_wndToolBar.SetButtonText (6, _T ("Paste")); m_wndToolBar.SetButtonText (7, _T ("Undo")); m_wndToolBar.SetButtonText (9, _T ("Print")); m_wndToolBar.SetSizes (CSize (48, 42), CSize (40, 19)); 

The resulting toolbar is shown in Figure 12-2. The first parameter passed to SetButtonText specifies the button's index, with 0 representing the leftmost button on the toolbar, 1 representing the button to its right, and so on. SetSizes must be called after the button text is added, not before, or the button sizes won't stick. Also, the width of the button bitmaps must be expanded to make room for the button text. In this example, the button bitmaps in Toolbar.bmp were expanded to a width of 40 pixels each, and the height was changed to 19 pixels to make the resulting buttons roughly square.

Figure 12-2. Toolbar buttons with text.

Unless you take steps to have them do otherwise, toolbar buttons behave like standard push buttons: they go down when clicked and pop back up when released. You can use MFC's CToolBar::SetButtonStyle function to create check push buttons that stay down until they're clicked again and radio push buttons that stay down until another toolbar button is clicked. The following statements create a text formatting toolbar that contains check push buttons for selecting bold, italic, and underlined text and radio push buttons for selecting left aligned, centered, or right aligned text.

// In the RC file IDR_TOOLBAR BITMAP Toolbar.bmp IDR_TOOLBAR TOOLBAR 16, 15 BEGIN     BUTTON ID_CHAR_BOLD     BUTTON ID_CHAR_ITALIC     BUTTON ID_CHAR_UNDERLINE     SEPARATOR     BUTTON ID_PARA_LEFT     BUTTON ID_PARA_CENTER     BUTTON ID_PARA_RIGHT END // In the CPP file m_wndToolBar.Create (this); m_wndToolBar.LoadToolBar (IDR_TOOLBAR); m_wndToolBar.SetButtonStyle (0, TBBS_CHECKBOX); m_wndToolBar.SetButtonStyle (1, TBBS_CHECKBOX); m_wndToolBar.SetButtonStyle (2, TBBS_CHECKBOX); m_wndToolBar.SetButtonStyle (4, TBBS_CHECKGROUP); m_wndToolBar.SetButtonStyle (5, TBBS_CHECKGROUP); m_wndToolBar.SetButtonStyle (6, TBBS_CHECKGROUP); 

The TBBS_CHECKBOX style creates a check push button. TBBS_CHECKGROUP, which is equivalent to TBBS_CHECKBOX ¦ TBBS_GROUP, creates a radio push button. Because buttons 4, 5, and 6 share the TBBS_CHECKGROUP style, clicking any one of them "checks" that button and unchecks the others. Buttons 0, 1, and 2, however, operate independently of each other and toggle up and down only when clicked. Other toolbar button styles that you can specify through SetButtonStyle include TBBS_BUTTON, which creates a standard push button, and TBBS_SEPARATOR, which creates a button separator. The complementary CToolBar::GetButtonStyle function retrieves button styles.

When you add radio push buttons to a toolbar, you should also check one member of each group to identify the default selection. The following code expands on the example in the previous paragraph by checking the ID_PARA_LEFT button:

 int nState =     m_wndToolBar.GetToolBarCtrl ().GetState (ID_PARA_LEFT); m_wndToolBar.GetToolBarCtrl ().SetState (ID_PARA_LEFT, nState ¦     TBSTATE_CHECKED); 

As described earlier in the chapter, CToolBar::GetToolBarCtrl returns a reference to the CToolBarCtrl that provides the basic functionality for a CToolBar. CToolBarCtrl::GetState returns the state of a toolbar button, and CToolBarCtrl::SetState changes the button state. Setting the TBSTATE_CHECKED flag in the parameter passed to SetState checks the button.

In practice, you might never need SetButtonStyle because in an MFC program you can convert standard push buttons into check push buttons and radio push buttons by providing update handlers that use CCmdUI::SetCheck to do the checking and unchecking. I'll have more to say about this aspect of toolbar buttons in just a moment.

Docking and Floating

One feature that CToolBar provides for free is the ability for the user to grab a toolbar with the mouse, detach it from its frame window, and either dock it to another side of the window or allow it to float free in a mini frame window of its own. You can control which (if any) sides of the frame window a toolbar can be docked to and other docking and floating characteristics. You can also create highly configurable toolbars that can be docked, floated, and resized at the user's behest and static tool palettes that permanently float and retain rigid row and column configurations.

When a toolbar is first created, it's affixed to the side of its frame window and can't be detached. Floating and docking are enabled by calling the toolbar's EnableDocking function ( CControlBar::EnableDocking) with bit flags specifying which sides of the frame window the toolbar will allow itself to be docked to and by calling the frame window's EnableDocking function ( CFrameWnd::EnableDocking) with bit flags specifying which sides of the window are valid docking targets. The following values can be ORed together and passed to either EnableDocking function:

Bit Flag Description
CBRS_ALIGN_LEFT Permit docking to the left side of the frame window
CBRS_ALIGN_RIGHT Permit docking to the right side of the frame window
CBRS_ALIGN_TOP Permit docking to the top of the frame window
CBRS_ALIGN_BOTTOM Permit docking to the bottom of the frame window
CBRS_ALIGN_ANY Permit docking to any side of the frame window

Called from a member function of a frame window class, the statements

 m_wndToolBar.EnableDocking (CBRS_ALIGN_ANY); EnableDocking (CBRS_ALIGN_ANY); 

enable the toolbar represented by m_wndToolBar to be docked to any side of its parent. The statements

 m_wndToolBar.EnableDocking (CBRS_ALIGN_TOP ¦ CBRS_ALIGN_BOTTOM); EnableDocking (CBRS_ALIGN_ANY); 

restrict docking to the top and bottom of the frame window. It might seem redundant for both the toolbar and the frame window to specify docking targets, but the freedom to configure the toolbar's docking parameters and the frame window's docking parameters independently comes in handy when a frame window contains more than one toolbar and each has different docking requirements. For example, if m_wndToolBar1 and m_wndToolBar2 belong to the same frame window, the statements

 m_wndToolBar1.EnableDocking (CBRS_ALIGN_TOP ¦ CBRS_ALIGN_BOTTOM); m_wndToolBar2.EnableDocking (CBRS_ALIGN_LEFT ¦ CBRS_ALIGN_RIGHT); EnableDocking (CBRS_ALIGN_ANY); 

enable m_wndToolBar1 to be docked top and bottom and m_wndToolBar2 to be docked left and right.

Toolbars are docked and undocked programmatically with the CFrameWnd member functions DockControlBar and FloatControlBar. DockControlBar docks a toolbar to its parent frame. The statement

 DockControlBar (&m_wndToolBar); 

docks m_wndToolBar in its default location—the inside top of the frame window. The statement

 DockControlBar (&m_wndToolBar, AFX_IDW_DOCKBAR_RIGHT); 

docks the toolbar to the right edge of the frame window. To exercise even finer control over a toolbar's placement, you can pass DockControlBar a CRect object or a pointer to a RECT structure containing a docking position. Until DockControlBar is called, a toolbar can't be detached from its parent, even if docking has been enabled with CControlBar::EnableDocking and CFrameWnd::EnableDocking.

FloatControlBar is the opposite of DockControlBar. It's called to detach a toolbar from its frame window and tell it to begin floating. The framework calls this function when the user drags a docked toolbar and releases it in an undocked position, but you can float a toolbar yourself by calling FloatControlBar and passing in a CPoint parameter specifying the position of the toolbar's upper left corner in screen coordinates:

 FloatControlBar (&m_wndToolBar, CPoint (x, y)); 

You can also pass FloatControlBar a third parameter equal to CBRS_ALIGN_TOP to orient the toolbar horizontally or CBRS_ALIGN_LEFT to orient it vertically. Call FloatControlBar instead of DockControlBar to create a toolbar that's initially floating instead of docked. If you call EnableDocking with a 0 and then call FloatControlBar, you get a floating toolbar that can't be docked to the side of a frame window. MFC programmers sometimes use this technique to create stand-alone tool palette windows. You can determine whether a toolbar is docked or floating at any given moment by calling CControlBar::IsFloating. You can also add a title to the mini frame window that surrounds a floating toolbar by calling the toolbar's SetWindowText function.

By default, a floating toolbar aligns itself horizontally when docked to the top or bottom of a frame window and vertically when it's docked on the left or right, but it can't be realigned while it's floating. You can give the user the ability to resize a floating toolbar by adding a CBRS_SIZE_DYNAMIC flag to the toolbar style. Conversely, you can make sure that a toolbar's size and shape remain fixed (even when the toolbar is docked) by using CBRS_SIZE_FIXED. One use for CBRS_SIZE_FIXED is to create floating tool palette windows with permanent row and column configurations. You can create static tool palettes containing multiple rows of buttons by using the TBBS_WRAPPED style to tell CToolBar where the line breaks are. A toolbar button with the style TBBS_WRAPPED is analogous to a carriage return/line feed pair in a text file: what comes after it begins on a new line. Assuming IDR_TOOLBAR represents a toolbar containing nine buttons, the following sample code creates a fixed tool palette window containing three rows of three buttons each:

 m_wndToolBar.Create (this); m_wndToolBar.LoadToolBar (IDR_TOOLBAR); m_wndToolBar.SetBarStyle (m_wndToolBar.GetBarStyle () ¦     CBRS_SIZE_FIXED); m_wndToolBar.SetButtonStyle (2,     m_wndToolBar.GetButtonStyle (0) ¦ TBBS_WRAPPED); m_wndToolBar.SetButtonStyle (5,     m_wndToolBar.GetButtonStyle (0) ¦ TBBS_WRAPPED); EnableDocking (CBRS_ALIGN_ANY); m_wndToolBar.EnableDocking (0); FloatControlBar (&m_wndToolBar, CPoint (x, y)); 

Adding TBBS_WRAPPED bits to the buttons whose indexes are 2 and 5 creates a line break every third button. And because the tool palette's EnableDocking function is called with a 0, the tool palette floats indefinitely and can't be docked to a frame window.

If an application uses two or more toolbars, you can include a CBRS_FLOAT_MULTI flag in the toolbars' EnableDocking functions and allow the user to dock floating toolbars together to form composite toolbars that share a common mini frame window. Unfortunately, the CBRS_FLOAT_MULTI and CBRS_SIZE_DYNAMIC styles are incompatible with each other, so you can't use both in the same toolbar.

Controlling a Toolbar's Visibility

Most applications that incorporate toolbars feature commands for hiding and displaying them. An MFC application can use the CFrameWnd member function OnBarCheck to toggle a toolbar on or off. Called with a toolbar ID, OnBarCheck hides the toolbar if it's visible or displays it if it's hidden. A related member function named OnUpdateControlBarMenu updates the menu containing the command that toggles a toolbar on or off by checking or unchecking the menu item whose ID matches the toolbar ID. OnBarCheck and OnUpdateControlBarMenu work with status bars, too; all you have to do is pass a status bar ID instead of a toolbar ID.

If your application has only one toolbar and that toolbar is assigned the default ID AFX_IDW_TOOLBAR, you can create a menu item that toggles the toolbar on and off by assigning the menu item the special ID value ID_VIEW_TOOLBAR. For a status bar, use ID_VIEW_STATUS_BAR instead. No message mapping is necessary because CFrameWnd's message map contains entries mapping these "magic" menu item IDs to the appropriate CFrameWnd member functions:

 ON_UPDATE_COMMAND_UI (ID_VIEW_STATUS_BAR, OnUpdateControlBarMenu) ON_COMMAND_EX (ID_VIEW_STATUS_BAR, OnBarCheck) ON_UPDATE_COMMAND_UI (ID_VIEW_TOOLBAR, OnUpdateControlBarMenu) ON_COMMAND_EX (ID_VIEW_TOOLBAR, OnBarCheck) 

ON_COMMAND_EX is similar to ON_COMMAND, but an ON_COMMAND_EX handler, unlike an ON_COMMAND handler, receives a UINT parameter containing the ID of the UI object that generated the message. OnBarCheck assumes that the toolbar ID and the menu item ID are the same and uses that ID to hide or display the toolbar.

If your application uses a toolbar whose ID isn't AFX_IDW_TOOLBAR, you can connect the toolbar to command and update handlers that control its visibility in two ways. The simplest method is to assign the toolbar and the corresponding menu item the same ID and to map that ID to OnBarCheck and OnUpdateControlBarMenu in the main frame window's message map. If the menu item ID is ID_VIEW_TOOLBAR2, here's what the message-map entries will look like:

 ON_UPDATE_COMMAND_UI (ID_VIEW_TOOLBAR2, OnUpdateControlBarMenu) ON_COMMAND_EX (ID_VIEW_TOOLBAR2, OnBarCheck) 

Don't forget that for this method to work, the toolbar must be assigned the same ID as the menu item.

The second approach is to provide your own command and update handlers and use CFrameWnd::ShowControlBar to hide and display the toolbar. You can determine whether a toolbar is currently visible or invisible by checking the WS_VISIBLE bit of the value returned by GetStyle:

 // In CMainFrame's message map ON_COMMAND (ID_VIEW_TOOLBAR2, OnViewToolbar2) ON_UPDATE_COMMAND_UI (ID_VIEW_TOOLBAR2, OnUpdateViewToolbar2UI)      void CMainFrame::OnViewToolbar2 () {     ShowControlBar (&m_wndToolBar2, (m_wndToolBar2.GetStyle() &         WS_VISIBLE) == 0, FALSE); } void CMainFrame::OnUpdateViewToolbar2UI (CCmdUI* pCmdUI) {     pCmdUI->SetCheck ((m_wndToolBar2.GetStyle () &         WS_VISIBLE) ? 1 : 0); } 

Don't try to toggle a toolbar's visibility by turning the WS_VISIBLE flag on or off, because there's more to hiding and displaying a toolbar than flipping a style bit. When a toolbar is toggled on or off (or docked or undocked), for example, MFC resizes the view to compensate for the change in the visible area of the frame window's client area. ShowControlBar takes these and other factors into account when it hides or displays a toolbar. For details, see the code for CFrameWnd::ShowControlBar in the MFC source code file Winfrm.cpp.

Keeping Toolbar Buttons in Sync with Your Application

Toolbar buttons are connected to command handlers in your source code the same way menu items are connected: through message maps. You can assign toolbar buttons update handlers just as you can menu items. That's one reason MFC passes an update handler a pointer to a CCmdUI object instead of a pointer to a CMenu or a CButton: the same CCmdUI functions that update menu items are equally capable of updating toolbar buttons. Calling CCmdUI::SetCheck during a menu update checks or unchecks the menu item. Calling the same function during a toolbar update checks or unchecks a toolbar button by pushing it down or popping it back up. Because CCmdUI abstracts the physical nature of UI objects, one update handler can do the updating for a toolbar button and a menu item as long as both objects share the same ID.

Suppose your application has an Edit menu with a Paste command that's enabled when there's text on the clipboard and disabled when there isn't. Furthermore, suppose that the application has a Paste toolbar button that performs the same action as Edit-Paste. Both the menu item and the toolbar button are assigned the predefined command ID ID_EDIT_PASTE, and ID_EDIT_PASTE is mapped to a handler named OnEditPaste with the following message-map entry.

 ON_COMMAND (ID_EDIT_PASTE, OnEditPaste) 

To update the Paste menu item each time the Edit menu is displayed, you also map ID_EDIT_PASTE to an update handler named OnUpdateEditPasteUI:

 ON_UPDATE_COMMAND_UI (ID_EDIT_PASTE, OnUpdateEditPasteUI) 

OnUpdateEditPasteUI uses CCmdUI::Enable to enable or disable the Paste command based on the value returned by ::IsClipboardFormatAvailable:

 void CMyClass::OnUpdateEditPasteUI (CCmdUI* pCmdUI) {     pCmdUI->Enable (::IsClipboardFormatAvailable (CF_TEXT)); } 

With this infrastructure in place, a paste operation can be performed by selecting Paste from the Edit menu or by clicking the Paste button in the toolbar. In addition, the handler that keeps the menu item in sync with the clipboard state also updates the toolbar button. The only difference between menu item updates and toolbar updates is the timing of calls to the update handler. For a menu item, the framework calls the update handler in response to WM_INITMENUPOPUP messages. For a toolbar button, the framework calls the update handler during idle periods in which there are no messages for the application to process. Thus, although menu updates are deferred until just before a menu is displayed, toolbar buttons are updated almost immediately when a state change occurs. It's a good thing, too, because toolbar buttons, unlike menu items, are visible at all times. The physical calling mechanism is transparent to the application, which simply provides an update handler and then trusts the framework to call it as needed.

Earlier I mentioned that you can use update handlers to create check push buttons and radio push buttons without changing the button styles. It's easy: just provide an update handler for each button and use CCmdUI::SetCheck or CCmdUI::SetRadio to do the checking and unchecking. If a button's command handler toggles a Boolean variable between TRUE and FALSE, and if its update handler checks or unchecks the button based on the value of the variable, the button acts like a check push button. If the command handler sets the variable value to TRUE and sets the values of other buttons in the group to FALSE, the button acts like a radio push button. The following message-map entries, command handlers, and update handlers make a group of three toolbar buttons behave like radio push buttons:

 // In CMyClass's message map ON_COMMAND (ID_BUTTON1, OnButton1) ON_COMMAND (ID_BUTTON2, OnButton2) ON_COMMAND (ID_BUTTON3, OnButton3) ON_UPDATE_COMMAND_UI (ID_BUTTON1, OnUpdateButton1) ON_UPDATE_COMMAND_UI (ID_BUTTON2, OnUpdateButton2) ON_UPDATE_COMMAND_UI (ID_BUTTON3, OnUpdateButton3)      void CMyClass::OnButton1 () {     m_bButton1Down = TRUE;     m_bButton2Down = FALSE;     m_bButton3Down = FALSE; } void CMyClass::OnButton2 () {     m_bButton1Down = FALSE;     m_bButton2Down = TRUE;     m_bButton3Down = FALSE; } void CMyClass::OnButton3 () {     m_bButton1Down = FALSE;     m_bButton2Down = FALSE;     m_bButton3Down = TRUE; } void CMyClass::OnUpdateButton1 (CCmdUI* pCmdUI) {     pCmdUI->SetCheck (m_bButton1Down); } void CMyClass::OnUpdateButton2 (CCmdUI* pCmdUI) {     pCmdUI->SetCheck (m_bButton2Down); } void CMyClass::OnUpdateButton3 (CCmdUI* pCmdUI) {     pCmdUI->SetCheck (m_bButton3Down); } 

With these command and update handlers in place, it's irrelevant whether the toolbar buttons are TBBS_CHECKGROUP buttons or ordinary TBBS_BUTTON buttons. Clicking any one of the buttons sets the other button-state variables to FALSE, and the update handlers respond by drawing the buttons in their new states.

Adding ToolTips and Flyby Text

When toolbars first began appearing in Microsoft Windows applications, they were sometimes more hindrance than help because the meanings of the buttons weren't always clear from the pictures on the buttons' faces. Some UI designers sought to alleviate this problem by adding text to the buttons. Others went one step further and invented ToolTips—small windows with descriptive text such as "Open" and "Paste" that appear on the screen when the cursor pauses over a toolbar button for a half second or so. (See Figure 12-3.) Today, ToolTips are commonplace in Windows applications, and they offer a unique solution to the problem of button ambiguity because they make context-sensitive help for toolbar buttons readily available without requiring a commensurate increase in button size.

Figure 12-3. A floating toolbar with a ToolTip displayed.

Adding ToolTips to an MFC toolbar is easy. Simply add CBRS_TOOLTIPS to the toolbar style and create a string table resource containing ToolTip text. The string IDs match the ToolTips to the toolbar buttons. If you use standard MFC command IDs such as ID_FILE_OPEN and ID_EDIT_PASTE and include Afxres.h in your application's RC file, the framework provides the ToolTip text for you. For other command IDs, you provide the ToolTip text by supplying string resources with IDs that match the toolbar button IDs. The following code sample creates a toolbar with buttons for performing common text-formatting operations and ToolTips to go with the buttons:

 // In the RC file IDR_TOOLBAR BITMAP Toolbar.bmp IDR_TOOLBAR TOOLBAR 16, 15 BEGIN     BUTTON ID_CHAR_BOLD     BUTTON ID_CHAR_ITALIC     BUTTON ID_CHAR_UNDERLINE     SEPARATOR     BUTTON ID_PARA_LEFT     BUTTON ID_PARA_CENTER     BUTTON ID_PARA_RIGHT END STRINGTABLE BEGIN     ID_CHAR_BOLD        "\nBold"     ID_CHAR_ITALIC      "\nItalic"     ID_CHAR_UNDERLINE   "\nUnderline"     ID_PARA_LEFT        "\nAlign Left"     ID_PARA_CENTER      "\nAlign Center"     ID_PARA_RIGHT       "\nAlign Right" END // In the CPP file m_wndToolBar.Create (this, WS_CHILD ¦ WS_VISIBLE ¦     CBRS_TOP ¦ CBRS_TOOLTIPS); m_wndToolBar.LoadToolBar (IDR_TOOLBAR); 

When the cursor pauses over a toolbar button and there's a string resource whose ID matches the button ID, the framework displays the text following the newline character in a ToolTip window. The ToolTip disappears when the cursor moves. In the old days, you had to set timers, monitor mouse movements, and subclass windows to make ToolTips work. Nowadays, that functionality is provided for you.

If your application features a status bar as well as a toolbar, you can configure the toolbar to display "flyby" text in addition to (or in lieu of) ToolTips by setting the CBRS_FLYBY bit in the toolbar style. Flyby text is descriptive text displayed in the status bar when the cursor pauses over a toolbar button. ToolTip text should be short and to the point, but flyby text can be lengthier. Did you wonder why the string resources in the previous paragraph began with "\n" characters? That's because the same string resource identifies flyby text and ToolTip text. Flyby text comes before the newline character, and ToolTip text comes after. Here's what the previous code sample would look like if it were modified to include flyby text as well as ToolTips:

 // In the RC file IDR_TOOLBAR BITMAP Toolbar.bmp IDR_TOOLBAR TOOLBAR 16, 15 BEGIN     BUTTON ID_CHAR_BOLD     BUTTON ID_CHAR_ITALIC     BUTTON ID_CHAR_UNDERLINE     SEPARATOR     BUTTON ID_PARA_LEFT     BUTTON ID_PARA_CENTER     BUTTON ID_PARA_RIGHT END STRINGTABLE BEGIN     ID_CHAR_BOLD        "Toggle boldface on or off\nBold"     ID_CHAR_ITALIC      "Toggle italic on or off\nItalic"      ID_CHAR_UNDERLINE   "Toggle underline on or off\nUnderline"     ID_PARA_LEFT        "Align text flush left\nAlign Left"     ID_PARA_CENTER      "Center text between margins\nAlign Center"     ID_PARA_RIGHT       "Align text flush right\nAlign Right" END // In the CPP file m_wndToolBar.Create (this, WS_CHILD ¦ WS_VISIBLE ¦     CBRS_TOP ¦ CBRS_TOOLTIPS ¦ CBRS_FLYBY); m_wndToolBar.LoadToolBar (IDR_TOOLBAR); 

If menu items share the same IDs as the toolbar buttons, the text preceding the newline character in the corresponding string resource is also displayed when a menu item is highlighted. We'll discuss this and other features of status bars shortly.

You can assign ToolTips and flyby text to toolbar buttons visually using the resource editor in Visual C++. With a toolbar resource open for editing, double-clicking a toolbar button displays the button's property sheet. You can then type a string into the Prompt box to assign flyby text, ToolTip text, or both to the button, as shown in Figure 12-4.

Figure 12-4. Assigning ToolTip text and flyby text to a toolbar button.

Adding Non-Push-Button Controls to a Toolbar

Push buttons far outnumber the other types of controls found on toolbars, but CToolBars can also include non-push-button controls such as combo boxes and check boxes. Suppose you'd like to add a combo box to a toolbar so that the user can select a typeface or a font size or something else from a drop-down list. Here's how to do it.

The first step is to include either a button separator or a dummy push button—a push button with an arbitrary command ID and button image—in the TOOLBAR resource where you want the combo box to appear. The following TOOLBAR resource definition uses a separator as a placeholder for a combo box that appears to the right of the final push button:

 IDR_TOOLBAR TOOLBAR 16, 15 BEGIN     BUTTON ID_CHAR_BOLD     BUTTON ID_CHAR_ITALIC     BUTTON ID_CHAR_UNDERLINE     SEPARATOR     BUTTON ID_PARA_LEFT     BUTTON ID_PARA_CENTER     BUTTON ID_PARA_RIGHT     SEPARATOR       // Space between button and combo box     SEPARATOR       // Placeholder for combo box END 

The second step is to use CToolBar::SetButtonInfo to increase the width of the placeholder to make room for the combo box and then to create a combo box in that space. Assuming that the toolbar is represented by a toolbar class derived from CToolBar, that m_wndComboBox is a CComboBox data member in the derived class, that IDC_COMBOBOX is the combo box's control ID, and that nWidth and nHeight hold the desired combo box dimensions, here's an excerpt from the derived class's OnCreate handler demonstrating how to create the combo box:

 SetButtonInfo (8, IDC_COMBOBOX, TBBS_SEPARATOR, nWidth); CRect rect; GetItemRect (8, &rect); rect.bottom = rect.top + nHeight; m_wndComboBox.Create (WS_CHILD ¦ WS_VISIBLE ¦ WS_VSCROLL ¦     CBS_SORT ¦ CBS_DROPDOWNLIST, rect, this, IDC_COMBOBOX); 

The call to CToolBar::SetButtonInfo assigns the placeholder the same ID as the combo box and expands the placeholder horizontally so that its width equals the desired width of the combo box. Before CComboBox::Create is called to create the combo box, CToolBar::GetItemRect is called to retrieve the placeholder's control rectangle. That rectangle is then heightened to make room for the list box part of the combo box, and the combo box is created over the top of the placeholder. The combo box is parented to the toolbar so that it will move when the toolbar moves. The toolbar also receives the combo box's WM_COMMAND messages, but thanks to command routing, the notifications that the combo box sends to its parent can be processed by the frame window, the view, and other standard command targets.

What about ToolTips and flyby text for non-push-button controls? As far as the framework is concerned, the combo box is just another control on the toolbar and can include ToolTips and flyby text just as push button controls can. All you have to do to add ToolTip and flyby text to the combo box is define a string resource whose ID is IDC_COMBOBOX. A ToolTip window will automatically appear when the cursor pauses over the combo box, and the flyby text will appear in the status bar.

Updating Non-Push-Button Controls

It wouldn't make sense to assign an update handler to a combo box in a toolbar because CCmdUI isn't designed to handle combo boxes. But MFC provides an alternative update mechanism that's ideal for non-push-button controls. CControlBar::OnUpdateCmdUI is a virtual function the framework calls as part of its idle-processing regimen. A derived toolbar class can override OnUpdateCmdUI and take the opportunity to update controls that don't have UI update handlers. OnUpdateCmdUI is the perfect solution for keeping custom toolbar controls in sync with other parts of the application, and doing it in a passive way that closely mimics the update mechanism used for toolbar buttons and menu items.

Let's say you've derived a toolbar class named CStyleBar from CToolBar that includes a combo box with a list of all the fonts installed in the system. As the user moves the caret through a document, you want to update the combo box so that the item selected in it is the name of the typeface at the current caret position. Rather than respond to each change in the caret position by updating the combo box selection directly, you can override OnUpdateCmdUI as shown here:

 void CStyleBar::OnUpdateCmdUI (CFrameWnd* pTarget,     BOOL bDisableIfNoHndler) {     CToolBar::OnUpdateCmdUI (pTarget, bDisableIfNoHndler);     CString string = GetTypefaceAtCaret ();     if (m_wndComboBox.SelectString (-1, string) == CB_ERR)         m_wndComboBox.SetCurSel (-1); } 

GetTypefaceAtCaret is a CStyleBar helper function that retrieves font information from the document or from the view and returns a CString with the typeface name. After GetTypefaceAtCaret returns, CComboBox::SelectString is called to select the corresponding combo box item, and CComboBox::SetCurSel is called with a -1 to blank the visible portion of the combo box if SelectString fails. With this simple update handler in place, the combo box selection will stay in sync with the caret as the user cursors through the document. The MyWord application presented later in this chapter uses a similar OnUpdateCmdUI handler to keep a pair of combo boxes—one for typefaces and one for font sizes—in sync with the caret position.

Generally speaking, you can ignore the pTarget and bDisableIfNoHndler parameters passed to OnUpdateCmdUI. But be sure to call CToolBar::OnUpdateCmdUI from the derived class's OnUpdateCmdUI function to avoid short-circuiting the update handlers for conventional toolbar buttons.

Making Toolbar Settings Persistent

MFC provides two convenient functions that you can use to preserve toolbar settings across sessions: CFrameWnd::SaveBarState and CFrameWnd::LoadBarState. SaveBarState writes information about each toolbar's docked or floating state, position, orientation, and visibility to the registry or a private INI file. (In Windows 95 and Windows 98 and in all versions of Windows NT, you should call CWinApp::SetRegistryKey from the application class's InitInstance function so that SaveBarState will use the registry.) If your application includes a status bar, SaveBarState records information about the status bar, too. Calling LoadBarState when the application restarts reads the settings back from the registry and restores each toolbar and status bar to its previous state. Normally, LoadBarState is called from the main frame window's OnCreate handler after the toolbars and status bars are created, and SaveBarState is called from the frame window's OnClose handler. If you'd also like to save control bar settings if Windows is shut down while your application is running, call SaveBarState from an OnEndSession handler, too.

You shouldn't call SaveBarState from the frame window's OnDestroy handler if you want to preserve the states of floating toolbars as well as docked toolbars. A docked toolbar is a child of the frame window it's docked to, but a floating toolbar is a child of the mini frame window that surrounds it. The mini frame window is a popup window owned by the frame window, but it's not a child of the frame window. (A popup window is a window with the style WS_POPUP; a child window has the WS_CHILD style instead.) The distinction is important because popup windows owned by a frame window are destroyed before the frame window is destroyed. Child windows, on the other hand, are destroyed after their parents are destroyed. A floating toolbar no longer exists when the frame window's OnDestroy function is called. Consequently, if it's called from OnDestroy, SaveBarState will fail to save state information for toolbars that aren't docked to the frame window.

Toolbar Support in AppWizard

You can use AppWizard to add a basic toolbar to an application. Checking the Docking Toolbar box in AppWizard's Step 4 dialog box (shown in Figure 12-5) adds a simple toolbar containing push buttons for File-Open, File-Save, and other commonly used commands. Besides creating the TOOLBAR resource and button bitmap, AppWizard adds a CToolBar data member named m_wndToolBar to the main frame window class and includes in the frame window's OnCreate handler code to create the toolbar and to enable docking.

click to view at full size.

Figure 12-5. Using AppWizard to add a toolbar.

AppWizard's toolbar-creation code uses CToolBar::CreateEx rather than CToolBar::Create to create a toolbar, and it passes CBRS_GRIPPER and TBSTYLE_FLAT flags to CreateEx. CBRS_GRIPPER draws a thin vertical bar, or "gripper," down the left edge of the toolbar. TBSTYLE_FLAT creates a "flat" toolbar—one with flat buttons whose edges are visible only when the cursor is over them—like the ones in Visual C++. Flat toolbars are only supported on systems that have Internet Explorer installed. Fortunately, they degrade gracefully on older systems by assuming the visage of ordinary toolbars.



Programming Windows with MFC
Programming Windows with MFC, Second Edition
ISBN: 1572316950
EAN: 2147483647
Year: 1999
Pages: 101
Authors: Jeff Prosise

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net