*
MSDN*
Results by Bing
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > May 1998
May 1998

Microsoft Systems Journal Homepage

C++ Q&A

Code for this article: May98Cqa.exe (22KB)
Paul DiLascia is the author of Windows ++: Writing Reusable Code in C++ (Addison-Wesley, 1992) and a freelance consultant and writer-at-large. He can be reached at askpd@pobox.com or http://pobox.com/~askpd.

Q What is the difference between CToolBar and CToolBarCtrl, and when should I use them?
Norm Busalt
A This can be a bit confusing and the answer amounts to a kludge. CToolBar was written before ToolbarWindow32 was available in comctl32.dll. First, MFC provided its own CToolBar. When comctl32.dll came out with ToolbarWindow32, MFC added a new class, CToolBarCtrl, to encapsulate it the same way CEdit encapsulates an edit control and CTreeCtrl and CListCtrl encapsulate the tree and list controls. At the same time, the MFC developers modified the CToolBar implementation to use ToolbarWindow32. So CToolBarCtrl is the "bare" encapsulation of ToolbarWindow32, with functions like EnableButton that correspond to each toolbar message such as TB_ENABLEBUTTON. CToolBar is the MFC class that implements functionality above and beyond ToolbarWindow32.
For example, CToolBar implements floating and docking toolbars. Actually, the docking stuff is implemented in CControlBar, so other kinds of bars like CStatusBar and CDialogBar can inherit the same docking features. And herein lies the problem. Logically, an enhanced toolbar should be derived from CToolBarCtrl—after all, CToolBar is a toolbar, right? But it's already derived from CControlBar. Changing the derivation would require rewriting a ton of code. So instead, MFC has a CToolBar::GetToolBarCtrl function that lets you get the "pure" toolbar associated with the CToolBar.

 CToolBarCtrl& tbc = m_wndToolBar.GetToolBarCtrl();
Similarly, CStatusBar has a GetStatusBarCtrl. But how do these functions work? The implementation may amaze you. GetToolBarCtrl just returns the CToolBar itself, cast to a CToolBarCtrl.

 inline CToolBarCtrl& CToolBar::GetToolBarCtrl() const{
     return *(CToolBarCtrl*)this;
 }
This should completely surprise you because it shouldn't have a chance of working. How can you just cast a CToolBar to a CToolBarCtrl? It only works because CToolBarCtrl is what you might call a pure wrapper: it has no data members and no virtual functions. All it has are nonvirtual functions, most of them brainless inline message wrappers that convert C++-"speak" to Windows®-"speak."

 inline int CToolBarCtrl::GetState(int nID) const
 {
    ASSERT(::IsWindow(m_hWnd));
    return
    (int)::SendMessage(m_hWnd,
                      TB_GETSTATE, nID, 0L);
 }
This is a wrapper function because all it does is wrap the TB_GETSTATE message into a C++ function with a similar name. Only because CToolBarCtrl has no data members and no virtual functions can you cast a CToolBar to a CToolBarCtrl. Of course, what I really mean is that CToolBarCtrl has no data members or virtual functions beyond what it inherits from CWnd—from which CToolBar is also derived. Figure 1 shows the derivations for CToolBarCtrl and CToolBar, and Figure 2 shows their memory representations.
Figure 1  Class Hierarchy
Figure 1  Class Hierarchy


As you can see, up to the CWnd part CToolBarCtrl and CToolBar are identical, so the cast works. Of course, it also works because the m_hWnd of a CToolBar is indeed the HWND of a ToolbarWindow32. Please note you cannot go the other way! You can't cast from CToolBarCtrl to CToolBar, since a CToolBarCtrl object does not have the CControlBar and CToolBar parts of a CToolBar. For this reason, you should always use CToolBar and not CToolBarCtrl unless you know for certain that you have no need for any of the extra features in CToolBar. The only time you need to use CToolBarCtrl is when you want to access the wrapper functions that have no implementation in CToolBar.

Figure 2  Memory Representations
Figure 2  Memory Representations

 CToolBarCtrl& tbc = m_wndToolBar.GetToolBarCtrl();
 tbc.EnableButton(ID_FOO, TRUE);
In practice, however, it is quite common (particularly in MFC itself) to see code like this:

 pToolBar->SendMessage(TB_ENABLEBUTTON, ID_FOO, TRUE);
In other words, a lot of programmers don't bother with GetToolBarCtrl and simply use SendMessage. There's nothing wrong with this, but you should never call DefWindowProc to send a message! Unfortunately, the Redmontonians do it all the time, no doubt because they want to save a few CPU cycles. The implementation of CToolBar is littered with calls like this:

 DefWindowProc(TB_ADDBUTTONS, ...);
This is slightly faster than calling SendMessage because the message goes directly to the ToolbarWindow32, without going through normal MFC channels. But the architectural cost is enormous! Calling DefWindowProc instead of SendMessage defeats the whole object-oriented purpose of Windows because now no one else can see the message.
I was once writing some code that needed to do something whenever a button was added to the toolbar—so I installed my own message proc in the toolbar to look for TB_ADDBUTTON messages. Even though TB_ADDBUTTON was clearly being sent to my toolbar, my window proc never saw it! Why? Because MFC was sending TB_ADDBUTTON with DefWindowProc instead of SendMessage. Go directly to toolbar, do not pass window proc. This is the kind of thing that makes you want to go ballistic. The only time you should ever use DefWindowProc is when you have subclassed a window and your window proc has processed a particular message, and then you want to let the original window proc handle it. It's like calling the base class version of some handler function; when CToolBar calls DefWindowProc(WM_FOO), it's like calling CWnd::OnFoo directly.
While I'm beating on MFC and CToolBar, I may as well tell you about another buggy function that can get you easily, and that is CToolBar::GetItemRect. You'll discover that CToolBar does in fact implement some of the wrappers found in CToolBarCtrl. For example, both classes have a CommandToIndex function that does no more than SendMessage(TB_COMMANDTOINDEX), and they both have a GetItemRect that gets the rectangle of a toolbar button. But while the version in CToolBarCtrl is a normal wrapper that sends TB_GETITEMRECT to the window, the implementation in CToolBar has all sorts of side effects before finally sending the TB_GETITEMRECT message (improperly, via DefWindowProc). There's this bogus business of "delayed button layout," so CToolBar's version of GetItemRect may recompute the layout before sending TB_GETITEMRECT (via DefWindowProc!) to the window. There's even a lengthy REVEW (sic) comment in the source code about why it's OK to cast away const-ness. (Perhaps no one reviewed the code as they searched for "review" because it's misspelled.)
In any case, I have found this function to be a major source of woe, particularly if you call it during custom paint operations. You think it's an innocent const function—all it does is retrieve a rectangle—when in reality it resizes your toolbar. The moral is, instead of calling CToolBar::GetItemRect, you should call SendMessage(TB_GETITEMRECT) or GetToolBarCtrl.GetItemRect. And let it be a lesson to you: the number one rule of any wrapper function is that it should have no side effects. None. Just call SendMessage (and not DefWindowProc) with the wrapped message. If you find yourself attempting to justify adding extraneous code to a wrapper function, it's a sure sign of a problem with your overall design.
OK, enough code bashing. I also want to point out that the situation with GetToolBarCtrl is analogous to the situation I described in my September 1997 column. There, I explained why you can cast a CWnd* to a CEdit* (or pointer to any other kind of control) if the attached HWND is in fact an edit control: because CEdit is, like CToolBarCtrl, a pure wrapper class—that is, one with no data members or virtual functions.

 CEdit* pEdit = (CEdit*)GetDlgItem(IDC_MYEDIT); 
 pEdit->Paste();
This works even though the return type from GetDlgItem is CWnd*, and "downcasting" is generally not valid. It seems an unwritten rule in MFC that control wrapper classes have no data members or virtual functions. This is true of the basic controls CEdit, CButton, CListBox, CComboBox, and CStatic—as well as the newfangled controls like CListCtrl and CTreeCtrl in afxcmn.h (see Figure 3).
MFC uses this clever rule to implement a class, CCtrlView, that lets you turn any control class into a view. MFC uses CCtrlView to implement CListView and CTreeView. To use CCtrlView, all you have to do is specify the (Windows) window class name and default style flags in the constructor. For example, the CListView constructor looks like this:

 CListView::CListView() :
 CCtrlView(WC_LISTVIEW, AFX_WS_DEFAULT_VIEW)
 {
 }
WC_LISTVIEW is SysListView32 (defined in commctrl.h), the name of the system list view control class, and AFX_WS_ DEFAULT_VIEW specifies default style flags (WS_ VISIBLE | WS_CHILD | WS_BORDER). CListView also has a function GetListCtrl to get the underlying CListCtrl.

 CListCtrl& CListView::GetListCtrl() const
 {
     return *(CListCtrl*)this;
 }
This should look familiar. It's just like CToolBar::GetToolBarCtrl; only the names are changed. It's totally gauche but it works. You could use CCtrlView to implement your own CRichEditView or CAnimateView based on CRichEditCtrl or CAnimateCtrl. Just mimic the code for CListView.
So, after this long-winded and winding treatise, the short answer to your question is: you should generally use CToolBar to implement a toolbar unless you know you don't want any of the CControlBar features such as docking and command routing, in which case you can use CToolBarCtrl. If you use CToolBar, you can still access the underlying CToolBarCtrl by calling CToolBar::GetToolBarCtrl. Many people don't bother and simply call SendMessage on their CToolBar. If you want to be really cool, you could implement your frame with a CToolBarCtrl

 CMainFrame : public CFrameWnd {
     CToolBar      m_wndToolBar;
     CToolBarCtrl& m_wndToolBarCtrl;
 •
 •
 •
 };
and then initialize it in your constructor:

 CMainFrame::CMainFrame() :
 m_wndToolBarCtrl(m_wndToolBar.GetToolBarCtrl())
 {
 •
 •
 •
 }
Remember that a reference must always be initialized; it can never be set.
All this reveals some of the deep limitations of C++ and MFC. Ideally, CToolBar would be multiply derived from CControlBar and CToolBarCtrl, but we all know how troublesome multiple inheritance is in C++. In MFC, it simply isn't allowed (for CObjects). (In Java, CControlBar and CToolBarCtrl would be interfaces, both of which CToolBar would implement—in fact, you can think of CToolBar::GetToolBarCtrl as getting the CToolBarCtrl interface for a CToolBar.) So instead of using multiple inheritance to solve the problem with CToolBar, the MFC hackers came up with this clever kludgy cast thing that works fine as long as the control wrapper class has no data or virtual functions.

Q How can I implement dropdown buttons like the undo buttons in most commercial apps? I set the TBSTYLE_DROPDOWN,
Figure 4
Figure 4
but it doesn't seem to do anything except cause the toolbar to send the TBN_DROPDOWN notification. How do I get the toolbar to draw the down arrow to the side of the button (as shown in Figure 4)?

Melody C. Araya
A Funny , isn't it. You find a button style with a promising name like TBSTYLE_DROPDOWN that looks just like what you need—but when you try it, it proves useless. As you discovered, TBN_DROPDOWN only causes the button to send TBN_DROPDOWN messages to your parent frame. No arrow next to the button. To get that, you also have to set the special style TBSTYLE_EX_DRAWDDARROWS.

 m_wndToolBar.SendMessage(TB_SETEXTENDEDSTYLE, 0,
                          TBSTYLE_EX_DRAWDDARROWS);
Do this in CMainFrame::OnCreate. Then you must also set the individual button's style to TBSTYLE_DROPDOWN.

 int iButton = tb.SendMessage(TB_COMMANDTOINDEX,
                              ID_FILE_OPEN);
 DWORD dwStyle = tb.GetButtonStyle(iButton);
 dwStyle |= TBSTYLE_DROPDOWN;
 tb.SetButtonStyle(iButton, dwStyle);
Here I've chosen to first get the index of the button with command ID ID_FILE_OPEN, rather than just passing 1 as the index because I don't know where the Open button will be in the case of a customizable toolbar. So it's safer to use TB_COMMANDTOINDEX.
Of course, there's just one little problem here: neither TB_SETEXTENDEDSTYLE nor TBSTYLE_EX_DRAWDDARROWS is defined anywhere unless you have the most recent SDK installed. (See? Sometimes there is indeed reason to install all the stuff on those MSDN
CDs you keep getting.) But even if you don't have the SDK, the version of ToolbarWindow32 that's in comctl32.dll version 4.71 (the one that ships with Microsoft® Internet Explorer 4.0) does in fact respond to these messages.
If you haven't installed the latest commctrl.h, or if you can't because it breaks something else in your code, all you have to do is add the magic symbols to your program:

 #ifndef TB_SETEXTENDEDSTYLE
 #define TB_SETEXTENDEDSTYLE (WM_USER + 84)  
         //For TBSTYLE_EX_*
 #define TBSTYLE_EX_DRAWDDARROWS 0x00000001
 #endif
This is what I did in my sample program, MyEdit. It's a simple text editor with a dropdown Open button that lets you select recently
Figure 5 My Edit
Figure 5 My Edit
opened files (see Figure 5). Once you know about the extended styles, the rest is pretty straightforward, so I won't bother explaining it. The code for CMainFrame, the only class of any interest, is in Figure 6.
So what other magic new features do toolbars support? Figure 7 shows some of the new message codes defined in commctrl.h. A full explanation is beyond the scope of this column, but I want to bring to your attention that there's a lot of new stuff—not just for toolbars, but for all the controls in comctl32.dll.
Also, a warning: some programs really will break if you install the new SDK because some structures have changed sizes—for example, the REBARBANDINFO struct used with the Rebar control. Remember how you have to set the first member, cbSize, to sizeof(REBARBANDINFO)? Well, now the size is bigger. If you blithely install the new SDK with the new commctrl.h and recompile your code, then sizeof(REBARBANDINFO) will be larger than it was. If you then attempt to run your program with older versions of comctl32.dll, the rebar will barf at your too-big struct. I discovered this the hard way. Fortunately, as I mentioned last month, Microsoft finallyhas a policy for redistributing comctl32.dll, which is described at http://www.microsoft.com/msdn/downloads/files/ 40comupd.htm. The situation with toolbars and comctl32.dll is one of the messier situations in all of Windows right now, and quite the cause for consternation.
As for the implementation of the menu itself (when your window gets the TBN_DROPDOWN notification), it's pretty straightforward. All you have to do is send the toolbar a TB_GETRECT message, which gets the button rectangle from a command ID instead of zero-index (as TB_GETITEMRECT does). Then use the coordinates to display your popup menu in the right place. Since TrackPopupMenu generates all the right WM_INITPOPUPMENU messages, which MFC interprets if the command ID is ID_FILE_MRU, I used the following popup menu in MyEdit:

 IDR_FILEDROPDOWN MENU PRELOAD DISCARDABLE
 BEGIN    
   POPUP "&File"    
   BEGIN
     MENUITEM "Recent File", ID_FILE_MRU_FILE1, GRAYED     
   END
 END
Now when the user invokes the menu, MFC replaces Recent- File with a series of menu items, one for each recent file opened, as saved in the program's registry setting. Neat.

Have a question about programming in C or C++? Send it to askpd@pobox.com

From the May 1998 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1998 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2014 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft