Another Flat ToolBar (does not require MSIE)

For those of you, who have already read this article: The major enhancements start here.
Changes for Revision 2.
Download section
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 (165KB) 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
Download Source 8KB
Download Project 165KB

To use CToolBarEx in an MFC application, you have to perform the following steps (I assume you use App-/Class-Wizard):
1. #include "ToolBarEx.h"
     in either StdAfx.h or MainFrm.h

2. Change the type of CMainFrame::m_wndToolBar from
    CToolBar to CToolBarEx

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 of CToolBarEx 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:

  1. 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 as CToolBarCtrl 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.
  2. 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:

  1. 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 ...
  2. There is still no (Office97- or DevStudio-like) customization dialog

Limitations:

  1. 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),       // insert control of type CComboBox
                IDC_COMBOBOX,                   // id of the button that is to replace with the control
                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)
                // NOTE - the ClassWizard will add and remove member functions here.
        //}}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, CCrea
{
        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 completing 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 got more complex than I expected ...

Context Menu
context menu of the toolbar

Toolbar Dialog Resource
"Toolbar" customization dialog in the resource

Toolbar Dialog
"Toolbar" customization dialog in action

CToolBarEx still consists of two files:
CToolBarEx.h and
CToolBarEx.cpp
Download enhanced source 25K
Download enhanced sample 56K