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



Comments

  • Drawing separators when toolbar is vertically docked.

    Posted by Legacy on 10/03/2003 12:00am

    Originally posted by: Raghunath

    Is it possible to draw separators when toolbar is vertically docked.

    Reply
  • FrameEnableDocking() ???

    Posted by Legacy on 07/24/2002 12:00am

    Originally posted by: Anton

    I add FrameEnableDocking() into my code and got error "unknown identifier"...

    What I should to do?

    Thanks

    Reply
  • Toolbar in SDK

    Posted by Legacy on 11/08/2001 12:00am

    Originally posted by: Shabbir Taiyabi

    I want to know that the toolbar which is created by MFC can how be simply implemented on SDK platform.
    

    Reply
  • Dropdown arrows doesn't show

    Posted by Legacy on 11/05/2001 12:00am

    Originally posted by: jauwaad

    Drop down arrows using style TBSTYLE_EX_DRAWDDARROWS doesn't draw. does anyone know how to fix this bug.

    Reply
  • How to Change the Bitmap and Text Dynamically

    Posted by Legacy on 01/03/2001 12:00am

    Originally posted by: Sreenivasulu

    My problem is I need to Change the Bitmap and Text Dynamically for the toolbar buttons.

    For Ex: The print icon is there on the toolbar.Now we need to allow the user to change the print icon to some other icon by choosing someicon file at runtime.

    if anyoneknows how to do it Help me otherwise give me some reference where can i get the material for this type of application.

    Reply
  • Some bugs (and fixes)

    Posted by Legacy on 01/24/2000 12:00am

    Originally posted by: Joel Bierling

    If you look at your example app and move an open file dialog box from top to bottom over the toolbar you'll notice the buttons don't get redrawn correctly (the buttons get left in an up position).

    You can fix this by changing line 398 from:
    BOOL bHasCursor = rc.PtInRect(cursor);

    to:
    BOOL bHasCursor = rc.PtInRect(cursor) && GetActiveWindow() == GetParent();

    Also something I haven't found a good fix for yet is that moving a window (open file for example) from right to left or bottom to top over the toolbar messes up the display (the background doesn't get drawn right) of disabled toolbar buttons. The only thing that fixes this is to disable the embossing code in DrawDisabledButton().

    This is under NT4 SP6.

    Reply
  • Bring back closed floated toolbars

    Posted by Legacy on 09/15/1999 12:00am

    Originally posted by: Dirk Schlageter

    This sample application has the same problem that i have.
    When i grap a toolbar and drag it out so that it has its own frame(floated ?) and push the x to close. How can i bring it back without starting again?

    Reply
  • FrameEnableDocking?

    Posted by Legacy on 08/09/1999 12:00am

    Originally posted by: DRider

    I add FrameEnableDocking() into my code and got error "unknown identifier"... What is wrong?
    Thanks

    Reply
  • DROPDOWN style support

    Posted by Legacy on 05/25/1999 12:00am

    Originally posted by: Louis Daoust

    I've been using CToolBarEx for a while but now I would need to be able to display a dropdown arrow beside a button like the ones supported in the toolbar in the new common controls. Can this be added ?

    Thanks,
    Louis.

    Reply
  • How To Save The ToolBarEx State

    Posted by Legacy on 04/06/1999 12:00am

    Originally posted by: zbliu

    Use LoadBarState() In MainFrame's OnCreate() Function When Have two ToolBarEx Bar,seriously Error reported when program runing.
    How To DO?

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • CentreCorp is a fully integrated and diversified property management and real estate service company, specializing in the "shopping center" segment, and is one of the premier retail service providers in North America. Company executives travel a great deal, carrying a number of traveling laptops with critical current business data, and no easy way to back up to the network outside the office. Read this case study to learn how CentreCorp implemented a suite of business continuity services that included …

Most Popular Programming Stories

More for Developers

RSS Feeds