Flat Toolbar (requires IE3+)

. The toolbar class discussed in this article works with the standard MFC CToolBar.

NOTE: You must also have new the COMCTL32.DLL (version 4.7 or later). This is installed with Internet Explorer 3 and will come standard with the new Windows 98. So if you are using VC++ 5 then you probably already have this DLL.

Here is a toolbar enhancement that gives you DevStudio-like toolbars.

These have a flat look with a "gripper" on the left hand side and separator lines between groups. As the mouse moves over the toolbar, the tools get a button created around them.

There are a number of issues that needed to be addressed here.

MFC uses the style bits that control the flat look for its own purposes. So you cannot set this style when creating the toolbar, but must modify the style afterwards. To do this, I have added a SetFlatLookStyle() function.

Flat look toolbars are painted transparently. Unfortunately, MFC has not been written with transparent toolbars in mind, so one needs to repaint the background. This is done on size and move messages, for example when draggin a docked toolbar. They are also done when the tool button's style changes. For example, when a button changes from pushed to released, the background needs to be repainted.

The toolbar control does not draw the separators between groups of toolbar buttons, but instead leaves a gap. The class intecepts the WM_PAINT method and adds separators at the correct position.

The toolbar control also does not support the gripper at the left or top end of the toolbar. The class adjusts the client area and draws the gripper approriately.

To use this class, simply change your use of CToolBar to CFlatToolBar and call the SetFlatLookStyle() function after creating the toolbar itself (ie. after toolbar bitmaps are loaded).



//***************************************************************
// FlatToolBar.h
// (c) 1997, Roger Onslow

#if !defined(__FLATTOOLBAR_H__)
#define __FLATTOOLBAR_H__

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

class CFlatToolBar : public CToolBar 
{
	DECLARE_DYNAMIC(CFlatToolBar);
public:
	void SetFlatLookStyle();
	void RepaintBackground();
	void DrawSeparators();
	void DrawSeparators(CClientDC* pDC);
	void EraseNonClient();
	void DrawGripper(CWindowDC *pDC, CRect& rectWindow);
protected:
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CFlatToolBar)
	virtual void OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler);
	//}}AFX_VIRTUAL
	// Message Handlers
protected:
	//{{AFX_MSG(CFlatToolBar)
	afx_msg void OnWindowPosChanging(LPWINDOWPOS lpWndPos);
	afx_msg void OnPaint();
	afx_msg void OnNcPaint();
	afx_msg void OnNcCalcSize( BOOL bCalcValidRects, NCCALCSIZE_PARAMS*	lpncsp );
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP();
};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(__FLATTOOLBAR_H__)

//***************************************************************
// FlatToolBar.cpp
// (c) 1997, Roger Onslow

#include "stdafx.h"
#include "FlatToolBar.h"

#ifdef _DEBUG
#undef THIS_FILE
#define new DEBUG_NEW

static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

BEGIN_MESSAGE_MAP(CFlatToolBar, CToolBar)
	//{{AFX_MSG_MAP(CFlatToolBar)
	ON_WM_WINDOWPOSCHANGING()
	ON_WM_PAINT()
	ON_WM_NCPAINT()
	ON_WM_NCCALCSIZE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

IMPLEMENT_DYNAMIC(CFlatToolBar,CToolBar)

// Must set after creation, as MFC wipes out extra style bits
void CFlatToolBar::SetFlatLookStyle()
{
	// Set flat style (transparent)
	ModifyStyle(0,TBSTYLE_FLAT);
	// others are...
	// #define TBSTYLE_TOOLTIPS        0x0100
	// #define TBSTYLE_WRAPABLE        0x0200
	// #define TBSTYLE_ALTDRAG         0x0400
	// #define TBSTYLE_FLAT            0x0800
	// #define TBSTYLE_LIST            0x1000
}

// Because buttons are transparent, we need to repaint the background
void CFlatToolBar::RepaintBackground()
{
	// get parent window (there should be one)
	CWnd* pParent = GetParent();
	if (pParent) {
		// get rect for this toolbar
		CRect rw; GetWindowRect(&rw);
		// convert rect to parent coords
		CRect rc = rw; pParent->ScreenToClient(&rc);
		// invalidate this part of parent
		pParent->InvalidateRect(&rc);
		// now do all the other toolbars (etc) that belong to the parent
		for (
			CWnd* pSibling = pParent->GetWindow(GW_CHILD);
		pSibling;
		pSibling = pSibling->GetNextWindow(GW_HWNDNEXT)
			) {
			// but do not draw ourselves
			if (pSibling == this) continue;
			// convert rect to siblings coords
			CRect rc = rw; pSibling->ScreenToClient(&rc);
			// invalidate this part of sibling
			pSibling->InvalidateRect(&rc);
		}
	}
}

// Draw the separators in the client area
void CFlatToolBar::DrawSeparators()
{
	// get a dc for the client area
	CClientDC dc(this);
	// draw the separators on it
	DrawSeparators(&dc);
}

// Draw the separators
void CFlatToolBar::DrawSeparators(CClientDC* pDC)
{
	// horizontal vs vertical
	bool ishorz = (m_dwStyle & CBRS_ORIENT_HORZ) != 0;
	// get number of buttons
	int nIndexMax = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
	int nIndex;
	
	// try each button
	for (nIndex = 0; nIndex < nIndexMax; nIndex++)
	{
		UINT dwStyle=GetButtonStyle(nIndex);
		UINT wStyle=LOWORD(dwStyle);
		
		// if it is a separator
		if (wStyle == TBBS_SEPARATOR)
		{
			// get it's rectangle and width
			CRect rect;
			GetItemRect(nIndex,rect);
			
			// if small enough to be a true separator
			int w=rect.Width();
			if (w <= 8)
			{
				if (ishorz)
				{
					// draw the separator bar in the middle
					CRect rectbar=rect;
					int x=(rectbar.left+rectbar.right)/2;
					rectbar.left=x-1;
					rectbar.right=x+1;
					pDC->Draw3dRect(rectbar,::GetSysColor(COLOR_3DSHADOW),::GetSysColor(COLOR_3DHILIGHT));
				}
				else
				{
					// draw the separator bar in the middle
					CRect rectbar = rect;
					rectbar.left = rectbar.left - m_sizeButton.cx;
					rectbar.right = rectbar.left + m_sizeButton.cx;
					rectbar.top = rectbar.bottom+1;
					rectbar.bottom = rectbar.top+3;
					int y = (rectbar.top+rectbar.bottom)/2;
					rectbar.top = y-1;
					rectbar.bottom = y+1;
					pDC->Draw3dRect(rectbar,::GetSysColor(COLOR_3DSHADOW),::GetSysColor(COLOR_3DHILIGHT));
					
				}
			}
		}
	}
}

// Draw the gripper at left or top
void CFlatToolBar::DrawGripper(CWindowDC *pDC, CRect& rectWindow)
{
	// get the gripper rect (1 pixel smaller than toolbar)
	CRect gripper = rectWindow;
	gripper.DeflateRect(1,1);
	if (m_dwStyle & CBRS_FLOATING) {
		// no grippers
	} else if (m_dwStyle & CBRS_ORIENT_HORZ) {
		// gripper at left
		gripper.right = gripper.left+3;
		pDC->Draw3dRect(gripper,::GetSysColor(COLOR_3DHIGHLIGHT),::GetSysColor(COLOR_3DSHADOW));
		gripper.OffsetRect(+4,0);
		pDC->Draw3dRect(gripper,::GetSysColor(COLOR_3DHIGHLIGHT),::GetSysColor(COLOR_3DSHADOW));
		rectWindow.left += 8;
	} else {
		// gripper at top
		gripper.bottom = gripper.top+3;
		pDC->Draw3dRect(gripper,::GetSysColor(COLOR_3DHIGHLIGHT),::GetSysColor(COLOR_3DSHADOW));
		gripper.OffsetRect(0,+4);
		pDC->Draw3dRect(gripper,::GetSysColor(COLOR_3DHIGHLIGHT),::GetSysColor(COLOR_3DSHADOW));
		rectWindow.top += 8;
	}
}

// Erase the non-client area (borders) - copied from MFC implementation
void CFlatToolBar::EraseNonClient()
{
	// get window DC that is clipped to the non-client area
	CWindowDC dc(this);
	CRect rectClient;
	GetClientRect(rectClient);
	CRect rectWindow;
	GetWindowRect(rectWindow);
	ScreenToClient(rectWindow);
	rectClient.OffsetRect(-rectWindow.left, -rectWindow.top);
	dc.ExcludeClipRect(rectClient);
	
	// draw borders in non-client area
	rectWindow.OffsetRect(-rectWindow.left, -rectWindow.top);
	DrawBorders(&dc, rectWindow);
	
	// erase parts not drawn
	dc.IntersectClipRect(rectWindow);
	SendMessage(WM_ERASEBKGND, (WPARAM)dc.m_hDC);
	
	DrawGripper(&dc, rectWindow); // 
} 

// Because buttons are transparaent, we need to repaint background if style changes 
void CFlatToolBar::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler)
{
    static CUIntArray styles; // get the number of buttons
    int nIndexMax=(int)DefWindowProc(TB_BUTTONCOUNT, 0, 0); 
	
    int nIndex; // save styles 
    for (nIndex=0; nIndex < nIndexMax; nIndex++)
    {
        UINT dwStyle=GetButtonStyle(nIndex);
        styles.SetAtGrow(nIndex,dwStyle); 
    } 
    
    // do base class processing 
    CToolBar::OnUpdateCmdUI(pTarget,bDisableIfNoHndler); 
	
    // make checked button appear pushed in 
    for (nIndex=0; nIndex < nIndexMax; nIndex++) 
    { 
        UINT dwStyle=GetButtonStyle(nIndex);
        if (dwStyle & TBBS_DISABLED) 
        { 
            // don't touch if disabled (avoids flicker) 
        } 
        else if (dwStyle & TBBS_CHECKBOX) 
        {
            UINT dwStyleWas=dwStyle; // if checked, make it pressed, else not pressed 
            if (dwStyle & TBBS_CHECKED) 
            { 
                dwStyle |=TBBS_PRESSED;
            }
            else if (!(styles[nIndex]&TBBS_CHECKED) && (styles[nIndex]&TBBS_PRESSED)) 
            {
                dwStyle |=TBBS_PRESSED; 
            } 
            else
            {
                dwStyle &=~TBBS_PRESSED;
            }
            // set new style if changed 
            if (dwStyleWas !=dwStyle) SetButtonStyle(nIndex,dwStyle); 
        } 
    }
    // check for changes to style (buttons presssed/released)
    for (nIndex=0; nIndex < nIndexMax; nIndex++) 
    { 
        UINT dwStyle=GetButtonStyle(nIndex);
        if (styles[nIndex] !=dwStyle) {
            // repaint whole toolbar (not just this button) 
            Invalidate(); 
            // no need to check any more 
            break; 
        }
    }
} 

// Because buttons are transparent, we need to repaint background on size or move 
void CFlatToolBar::OnWindowPosChanging(LPWINDOWPOS lpwp) 
{ 
    // default processing 
    CToolBar::OnWindowPosChanging(lpwp); 
    RepaintBackground(); 
}

// Paint the toolbar 
void CFlatToolBar::OnPaint() 
{
    // standard tolbar 
    CToolBar::OnPaint(); 
    
    // erase the background 
    EraseNonClient(); 
	
    // plus separators 
    DrawSeparators(); 
}

// Paint the non-client area copied from MFC implementatios 
void CFlatToolBar::OnNcPaint() 
{ 
    // EraseNonClient(); don't do it here 
}

// Calculate the non-client area adjusting for grippers 
void CFlatToolBar::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp) 
{
    CToolBar::OnNcCalcSize(bCalcValidRects,lpncsp); 
    
    // adjust non-client area for gripper at left or top 
    if (m_dwStyle & CBRS_FLOATING) 
    {
        // no grippers 
    } 
    else if (m_dwStyle & CBRS_ORIENT_HORZ) 
    {
        // move 2 pixels right to make room 
        lpncsp->rgrc[0].left += 2;
        lpncsp->rgrc[0].right += 2;
    }
    else 
    {
        // move 4 pixels downto make room
        lpncsp->rgrc[0].top += 4;
        lpncsp->rgrc[0].bottom += 4;
    }
}