A Flat ToolBar that does not require Internet Explorer






4.38/5 (7 votes)
Feb 25, 2000

99543

4831
A flat toolbar implementation that does not require the updated common controls library from Internet Explorer.
For those of you, who have already read this article CodeGuru, the major enhancements start here. I'm looking for someone who has enough time to help me! .
Some time ago, I saw Roger Onslow's flat toolbar implementation. The fact, that I need
a special product (MSIE) (or even DLL -> comctl32.dll) was somewhat inconvenient
to me. So I started to develop my own version of a flat looking toolbar without such
requirements. The result is a class called CToolBarEx.
With CToolBarEx
one can toggle between flat- and "classic"-mode. The
appropriate look will be shown immediataly.
Don't wonder if some parts of the code seem to be well known to you. The drawing of separators and the gripper was (more or less) stolen from Roger's toolbar (why should I do all of the hard bits again ;-)
In flat-mode CToolBarEx
makes all of the drawings by itself; in classic-mode,
MFC does the work.
Since VC++ >= 4.2 provides custom-draw abilities, this feature will be emulated in flat mode by a local helper class, so one can use this feature regardless of the current mode (flat or classic). To get some further informations on owner-drawing, have a look at the implementation file ToolBarEx.cpp. The MainFrm.cpp in the sample application may provide even more informations, if you're not familiar with owner-drawing on toolbars.
However, CToolBarEx
should be able to work with older versions of VC++ too...
CToolBarEx
consists of two files:
- ToolBarEx.h
- ToolBarEx.cpp
To use CToolBarEx
in an MFC application, you have to perform the following
steps (I assume you use App-/Class-Wizard):
#include "ToolBarEx.h"
in either StdAfx.h or MainFrm.h- Change the type of
CMainFrame::m_wndToolBar
fromCToolBar
toCToolBarEx
The CToolBarEx
class provides the following public methods (in addition to its
ancestor-classes):
// Set the mode of the toolbar. The mode changes immediately // on the screen. void SetFlatLook( BOOL bFlat = TRUE ); // determine wether the current mode is "flat" BOOL IsFlatLook() const; // This function I've personally missed in CToolBar // for more than one time ... void GetSizes( CSize &sizeButton, CSize &sizeImage ) const; // Get the window to which the toolbar initially was docked. This // is not necessarily the window returned by CWnd::GetParent() or // CControlBar::GetDockingFrame(). Both these functions don't // return the expected window, if the toolbar is floating ... CWnd * GetParentFrame() const;
Update: The code has now been enhanced.
- texts on buttons now work
- gripper improved for a closer look like Office97
- disabled images now look embossed
- (thanks to Victor Vogelpoel)
(Zafir: Also to Oscar who sent in the same fix) - a separator is drawn only if it has no WRAP state set
Major Enhanced Version
First note that this enhanced version ofCToolBarEx
currently does not
run properly on NT < 4.0, so if you plan to use the CToolBarEx
on
such a system, please use the standard version above!
Many thanks to Jonathan Chin, Victor Vogelpoel and Patrick Liechty (the three guinea-pigs :-) for their help in testing and fixing this version of the CToolBarEx class !
The enhanced version covers two major features:
- The ALT-drag feature is now implemented. That means that one can drag'n'drop buttons and
controls from one toolbar to another by pressing the ALT-key and the left mouse button
simultanously.
CToolBarEx
uses the same technique to allow customization asCToolBarCtrl
do, so if you're not familiar with adjustable toolbars, please refer to the online help and/or have a look at the enhanced sample. - Easy, generalized way to add custom controls to a toolbar. The
CToolBarEx
class allows you to add/replace/reposition controls onto the toolbar and takes care of these controls.
This (zoomed) image gives you a feeling of the new features.
Things that have to be done later:
- It is still impossible to save/restore a customized toolbar. The methods from the
underlaying
CToolBarCtrl
class are not usable, if one uses custom controls and/or moved buttons/controls from one bar to another ... - There is still no (Office97- or DevStudio-like) customization dialog
Limitations:
- It is generally a bad idea to use toolbars one with texts on its buttons and another without. Either all bars have buttons with text or no bar has it. Otherwise you will get the user confused, if he/she moves a button from a bar without text to one with text or vice versa. See the sample for what happens.
New methods:
// determine wether this toolbar supports buttons with text BOOL HasButtonText() const; // invalidate the button with the given index (force a repaint // of this button) void InvalidateButton(int nIndex); // Check wether the button with index <idx> is a "real" separator. // Thus it is *not* a control. BOOL IsSeparator(int idx) const; // Check wether the button with the given index is a control in // real life BOOL; IsControl(int idx) >const; // Get the control that is associated with the given index // The CWnd * returned may be temporary (if you don't use // MFC to add a control to the toolbar) // Set <IdxIsID> to TRUE, if <idx> represents the ID of the control CWnd * GetControl(int idx, BOOL IdxIsID = FALSE) const; // Retrieve the bitmap associated with the button with ID <ID>. // The background of the bitmap is the current setting for // COLOR_BTNFACE. // Don't forget to destroy the bitmap, if it is not longer needed! HBITMAP GetBitmap(int ID); // Replace the button with ID <id> with a custom control // of type <pClass>. The custom control gets the ID <id>. // <rc> gives width & height of the control. // The stylebits WS_CHILD and WS_VISIBLE will be added automatically. // to <dwStyle>. // The control shall support the DECLARE_DYNCREATE! (except // CComboBox and CEdit which are handled separatly) // You must not "delete" the return-value ! CWnd * CtrlReplace( CRuntimeClass * pClass, CRect &rc, UINT id, DWORD dwStyle = 0 ); // Insert a custom control before the button with index <before>. // if <before> == -1, then the control is appended behind the last // For more information see CtrlReplace() above. CWnd * CtrlInsert( CRuntimeClass * pClass, CRect &rc, UINT id, int before = 0, DWORD dwStyle = 0 ); // call "RepositionControls()"; if you have added one or more controls // and want to add more buttons/controls. // This function will automatically be called for controls when calling // CtrlReplace() and CtrlInsert(). void RepositionControls(); // Recalculate the size of the toolbar and recalculate the // layout of its parent. // There should be no need to call this method from outside // the toolbar. void RecalcLayout();
There is no must to add controls with either CtrlInsert()
or
CtrlReplace()
. You can do it in a more traditional fashion too and the
toolbar still works (i.e. it takes care of all its children, regardless of the way of
insertion). So there is no need to change your existing code. You should call
RepositionControls()
by yourself, if you add more controls in another
way than the prefered one.
You might add a combo-box in the following way (assuming your toolbar resource includes
a button with the id IDC_COMBOBOX
, that is to be replaced by the control):
// ... // replace a button by a CComboBox-control on the toolbar DWORD dwComboStyle = WS_VSCROLL|CBS_AUTOHSCROLL|CBS_DROPDOWN| CBS_HASSTRINGS; CComboBox * pBox = (CComboBox*) m_wndToolBar.CtrlReplace( RUNTIME_CLASS(CComboBox), // control type IDC_COMBOBOX, // id of the button dwComboStyle // style bits ); if ( pBox ) { pBox->AddString(TEXT("Line 1")); pBox->AddString(TEXT("Line 2")); pBox->AddString(TEXT("Line 3")); } // ...
As you can see, adding a control is an easy task.
If you plan to use other types of controls (other than CComboBox
or
CEdit
), you have to derive a class from this type and to implement the
DECLARE_DYNCREATE
macro and to overload the following methods:
virtual BOOL Create( LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT &rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext = NULL);and
virtual void PostNcDestroy();
Lets say you plan to insert a static text into the toolbar:
- Create a new class using class-wizard to create a class derived from CStatic.
- Use class-wizard or class-view to overload the 2 virtual methods described above.
- The result should look like this (assuming you have named the class
"CText"
):///////////////////////////////////////////////////////// // CText window class CText : public CStatic { DECLARE_DYNCREATE(CText) // <<= YOU HAVE TO INSERT THIS // Construction public: CText(); // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CText) public: virtual BOOL Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT &rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext = NULL); protected: virtual void PostNcDestroy(); //}}AFX_VIRTUAL // Implementation public: virtual ~CText(); // Generated message map functions protected: //{{AFX_MSG(CText) //}}AFX_MSG DECLARE_MESSAGE_MAP() };
In the implementation file you have to fill out the 2 overloaded methods:
IMPLEMENT_DYNCREATE(CText, CStatic); // <<== DO NOT FORGET THIS BOOL CText::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT &rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext = NULL) { return CWnd::Create(TEXT("STATIC"), TEXT("text control"), dwStyle|SS_LEFT, rect, pParentWnd, nID); } void CText::PostNcDestroy() { delete this; }
That's all you have to do. The proceeding with other types of controls is similar.
Now you can insert such a control like follows:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { // ... m_wndToolBar.CtrlInsert( RUNTIME_CLASS(CText), // the control's runtime class CRect(-100, -22, 0, 0), // width is 100, height is 22 IDC_TEXT, // control's ID 2 // insert before button with index 2 ); // ... }
Changes in Revision 2
Many thanks to Wolfgang Loch, John Armstrong and Anatoly Ivasyuk who helped me to release that version.
- buttons that are checked and disabled are now looking ok
- don't draw a gripper if the bar is not dockable
- do not adjust space for gripper in "classic" mode
- give the bar itself a real 3D look (as in Office or DevStudio) (see images below)
- some minor code improvements
Old style 3D look of a toolbar.
New style (as in Office or DevStudio).
To enable the new 3D style you have to exchange the call to EnableDocking()
in your CMainFrame
's OnCreate()
method with a call to
FrameEnableDocking()
:
// original code: int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) // ... EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); // ... return 0; // changed code to get "real" 3D style: int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) // ... FrameEnableDocking(this, CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); // ... return 0; }
Help!
Since my commercial projects have a higher priority than my hobby projects, I don't have enough time to complete the persistence and customization of the toolbar in a reasonable time. I'm looking for someone who wants to help me complete this. I have a somewhat more advanced version of the class that implements some aspects of these issues (send me mail to get the source and see the images below). Both these problems became more complex than I expected ...
context menu of the toolbar
"Toolbar" customization dialog in the resource
"Toolbar" customization dialog in action
CToolBarEx
still consists of two files:
- CToolBarEx.h
- CToolBarEx.cpp
Download enhanced source - 25K Download enhanced sample - 56K