Click here to Skip to main content
15,915,160 members
Articles / Desktop Programming / MFC
Article

FooButton

Rate me:
Please Sign up or sign in to vote.
4.85/5 (120 votes)
7 Oct 2006CPOL5 min read 577K   15K   231   196
A lightweight general-purpose owner drawn bitmap button.

Introduction

This article describes FooButton, a lightweight owner-drawn button class that's served me well for several years.  Although there are plenty of other excellent button classes at CodeProject, I thought I'd add this trusty friend to the pile in the hope that someone may find it equally useful.

Features

FooButton lets you use a vanilla CButton as a:
  • standard pushbutton
  • pushbutton button with a drop-down indicator
  • multi pushbutton (like IE's "Back" and "Next" buttons)
  • checkbutton
  • hyperlink
  • static text control that's responsive to mouse clicks
  • check box
  • radio button
Support is also provided for:
  • bitmaps (currently only 16-color)
  • left-justified, centered and multi-line captions
  • colored captions
  • gradient shaded button backgrounds
  • popup menus
  • hot tracking
  • optional focus rectangle and "default button" indicator
  • grouped checkbuttons

How to use FooButton

  1. First, associate a standard button control (eg: IDC_FOO_BUTTON) in your dialog with an instance of the object.

    /////////////
    // MyDialog.h
    #include "FooButton.h"
    ...
    FooButton m_fooButton;
    
    ///////////////
    // MyDialog.cpp
    void CMyDialog::DoDataExchange(CDataExchange* pDX)
    {
      CDialog::DoDataExchange(pDX);
      //{{AFX_DATA_MAP(CMyDialog)
      DDX_Control(pDX, IDC_FOO_BUTTON, m_fooButton);
      //}}AFX_DATA_MAP
    }
    

  2. Then, initialize the instance in your dialog's OnInitDialog() method to suit your needs.  In this example, the button is set to display a bitmap and a drop-down indicator.

    // Initialize FooButton
    m_fooButton.setBitmapId (IDB_FOO_BUTTON);
    m_fooButton.setType (FooButton::Type::pushButtonDropDown);
    
         Drop-down pushbutton

API

 MethodPurpose
 getType(), setType()Gets and sets the button's type 
 getTextStyle(), setTextStyle()Gets and sets the button's text style 
 getTextColor(), setTextColor()Gets and sets the button's text color 
 getFocusStyle(), setFocusStyle()Gets and sets the button's focus style 
 getGradient(), setGradient()Gets and sets the button's gradient property 
 getBitmapId(), setBitmapId()Gets and sets the button's (optional) bitmap id 
 displayPopupMenu()Displays a popup menu below the button 
 isChecked(), check()Gets and sets a checkButton's checked state 
 isMultiClicked(), clearMultiClick()Gets and resets a multiPushButton's multi-clicked state 
 addToGroup(), removeFromGroup()Adds/removes a checkButton to/from a button group 
 reset()Frees storage used by all button groups 
 

Using FooButton as a check button

You can freely change any property of the button at run time.  This code snippet turns the button into a checkbutton and checks it.  Use check() and isChecked() to set and retrieve the button's checked state.

// Make it a checkbutton and check it
m_fooButton.setType (FooButton::Type::checkButton);
m_fooButton.check (true);
ASSERT (m_fooButton.isChecked());    // testing
     Unchecked   Checked

Gradient shading

Pushbuttons and checkbuttons can be set to display a gradient shaded background by calling setGradient().  This method has no effect if the button isn't a pushbutton or checkbutton.

// Use a gradient shaded background
m_fooButton.setGradient (true);
    

Button groups

You can make a bunch of checkButtons behave as mutually exclusive radio buttons by adding them to a button group.  A button group is just a named collection of buttons.  FooButton automatically handles group creation, membership and cleanup.

// Make "size" checkbuttons mutually exclusive
m_btnSmall.addToGroup (_T("foo"));
m_btnMedium.addToGroup (_T("foo"));
m_btnLarge.addToGroup (_T("foo"));
m_btnXLarge.addToGroup (_T("foo"));
     A button group

Displaying a popup menu

To display a popup menu in response to a button click, call displayPopupMenu().  You can call this method for any type of FooButton.

void CMyDialog::OnFooButton()
{
  CMenu menu;
  menu.LoadMenu (IDR_POPUP_MENU);
  CMenu* pPopupMenu = menu.GetSubMenu (0);
  int nResult = m_fooButton.displayPopupMenu (pPopupMenu);
  if (0 != nResult)
     PostMessage (WM_COMMAND, nResult);
}
     Normal   Pressed

Multi-pushbuttons

A multi-pushbutton behaves as two buttons in one, similar to IE's "Back" and "Next" buttons.  When the user clicks the button's drop-down region, FooButton sets its "multi-clicked" property to true.  You can query this property by calling isMultiClicked().  Regardless of whether the user clicked in the button's main or drop-down region, a standard notification is sent to the parent.  To clear the button's multi-click property, call clearMultiClick().

void CMyDialog::OnFooButton()
{
  if (m_fooButton.isMultiClicked()) {

      // Display menu if drop-down region was clicked
      CMenu menu;
      menu.LoadMenu (IDR_POPUP_MENU);
      CMenu* pPopupMenu = menu.GetSubMenu (0);
      int nResult = m_fooButton.displayPopupMenu (pPopupMenu);
      if (0 != nResult)
         PostMessage (WM_COMMAND, nResult);

      // Remember to clear the button's multi-click property!
      m_fooButton.clearMultiClick();

  } else {

      // Otherwise do default action
      PostMessage (WM_COMMAND, IDC_DEFAULT_ACTION);
  }
}
     Multi-pushbutton   

Check boxes and radio buttons

You can make a FooButton appear as a standard check box or radio button by using the FooButton:Type::checkBox and FooButton:Type::radio types.  Of course, this is really only useful when you want to also display a bitmap or add menu support to the button.

// Appear as check box and radio button
m_fooButton1.setType (FooButton::Type::checkBox);
m_fooButton2.setType (FooButton::Type::radio);
     Check box   

Hyperlink button

A hyperlink button is just a regular button that renders itself as a hyperlink.  You can navigate to a URL or perform any other action in the button's handler.

// Appear as hyperlink
m_fooButton.setType (FooButton::Type::hyperink);
     

Text color

You can change the color of the button's text at any time by calling setTextColor().  The text of hyperlink buttons is always rendered in C_HyperlinkColor and that of disabled buttons is always rendered in the standard etched format.

// Draw caption in red
m_fooButton.setTextColor (RGB (192, 0, 0));
     Custom caption text color

Focus rectangle

By default, a FooButton doesn't display a focus rectangle.  Call setFocusStyle() with FooButton::Focus::normalFocus to enable the button to display a focus rectangle.

// Allow focus rectangle to be displayed
m_fooButton.setFocusStyle (FooButton::Focus::normalFocus);
     Focus rectangle disabled   

Default button indicator

To enable a default FooButton to display its standard dark border, call setFocusStyle() with FooButton::Focus::defaultFocus.

// Allow focus rectangle and default indicator to be displayed
m_fooButton.setFocusStyle (FooButton::Focus::defaultFocus);
    Default button (unpressed)   

Rendering disabled bitmaps

Use the standard MFC EnableWindow() API to enable and disable the button.  FooButton uses its original bitmap to render a disabled version.

m_fooButton.EnableWindow (TRUE);   // enable button
m_fooButton.EnableWindow (FALSE);  // disable button
     Normal   Disabled

Acknowledgement

Revision history

  • 7 Oct 2006
    • Bug Fixm_hMsimg32 should be set to NULL in destructor. (Thanks, C. Young!)
    • Bug Fix:  Memory leak in DisabledBlt(). (Thanks, Corrado Valli!)

  • 6 Mar 2005
    • Bug Fix:  Added definition of COLOR_HOTLIGHT to enable compilation on older systems.

  • 5 Mar 2005
    • Enhancement:  Added support for gradient shaded buttons.
    • Bug Fix:  Reusing a group name across dialog invocations would cause a crash in FooButton::removeFromGroup().

  • 19 Feb 2005
    • Enhancement:  Added support for colored captions.
    • Enhancement:  Removed requirement to call FooButton::reset() when your app terminates.
    • Enhancement:  Now uses standard Win2000/XP hyperlink cursor.
    • Enhancement:  Code now ASSERTs if you're not subclassing from a button control.
    • Bug Fix:  All calls to Invalidate() now validate window handle, allowing a FooButton to be safely destroyed when clicked.

  • Fixed typo in default state rendering logic
  • 17 Jul 2004
    • Added support for check boxes and radio buttons
    • Fixed typo in default state rendering logic

  • 11 Jul 2004
    • Optimized fix for "unreferenced identifier" compiler warning
    • Exposed focus rectangle and default state modes
    • Added support for button groups

  • 4 Jul 2004
    Added multi-pushbutton and hyperlink styles.

  • 3 Jul 2004
    Submitted to CodeProject.

  • 12 Sep 1998
    Initial version.
  • License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


    Written By
    Technical Lead
    Canada Canada
    Ravi Bhavnani is an ardent fan of Microsoft technologies who loves building Windows apps, especially PIMs, system utilities, and things that go bump on the Internet. During his career, Ravi has developed expert systems, desktop imaging apps, marketing automation software, EDA tools, a platform to help people find, analyze and understand information, trading software for institutional investors and advanced data visualization solutions. He currently works for a company that provides enterprise workforce management solutions to large clients.

    His interests include the .NET framework, reasoning systems, financial analysis and algorithmic trading, NLP, HCI and UI design. Ravi holds a BS in Physics and Math and an MS in Computer Science and was a Microsoft MVP (C++ and C# in 2006 and 2007). He is also the co-inventor of 3 patents on software security and generating data visualization dashboards. His claim to fame is that he crafted CodeProject's "joke" forum post icon.

    Ravi's biggest fear is that one day he might actually get a life, although the chances of that happening seem extremely remote.

    Comments and Discussions

     
    GeneralRe: wrong backbround color if button is disabled and transparent color is not set to default Pin
    Ravi Bhavnani6-Jan-05 11:15
    professionalRavi Bhavnani6-Jan-05 11:15 
    Generalpotential bugs found with boundschecker Pin
    Warren Stevens29-Nov-04 3:36
    Warren Stevens29-Nov-04 3:36 
    GeneralRe: potential bugs found with boundschecker Pin
    Ravi Bhavnani29-Nov-04 16:18
    professionalRavi Bhavnani29-Nov-04 16:18 
    GeneralA minor debugging helper Pin
    PEK13-Sep-04 22:58
    PEK13-Sep-04 22:58 
    GeneralRe: A minor debugging helper Pin
    Ravi Bhavnani28-Sep-04 7:00
    professionalRavi Bhavnani28-Sep-04 7:00 
    GeneralHyperlink cursor might not be destroyed Pin
    PEK10-Sep-04 23:01
    PEK10-Sep-04 23:01 
    GeneralRe: Hyperlink cursor might not be destroyed Pin
    Ravi Bhavnani13-Sep-04 3:37
    professionalRavi Bhavnani13-Sep-04 3:37 
    QuestionHow to implement support for Windows XP themes Pin
    PEK3-Sep-04 6:34
    PEK3-Sep-04 6:34 
    I have successfully added support for Windows XP themes to FooButton. It wasn't so hard that I expected it to be Smile | :) . The code I will show here will compile in VC6, and programs should run without problems in other operating systems than Window XP. I have tried on Win98 and had no problems at all. "FOO_USEXPTHEMES" must be defined, otherwise will the code be compiled without support for themes.

    The first thing to do is to have some kind of theme manager that loads "uxtheme.dll". There is such class in the article "XP Style CBitmapButton (CHoverBitmapButton)". However, I found some bugs in it but have posted a bugfix in the forum. Apply it, and copy the files "theme.cpp", "theme.h" and "ThemeLib.h" to an project that using FooButton. I will assume that the project is the demo project on this article.

    Add the following in "FooButtonDemoDlg.cpp" (the main window):

    #ifdef FOO_USEXPTHEMES
    	#include "theme.h"
    	//Make global theme manager. Init it in OnInitDialog in main window
    	CTheme gThemeManager;
    #endif

    I will use the global variable "gThemeManager". I would prefer with another solution, but this was the easiest I come up to Smile | :) . Anyway, init this in CFooButtonDemoDlg::OnInitDialog() by calling:

    #ifdef FOO_USEXPTHEMES
    	//Init themes
    	gThemeManager.Init(m_hWnd);
    #endif

    It is nice if the theme could be changed when the program is running. Add this member function in the class CFooButtonDemoDlg:

    #ifdef FOO_USEXPTHEMES
    LRESULT CFooButtonDemoDlg::OnThemeChanged(WPARAM, LPARAM)
    {
    	// This feature requires Windows XP or greater.
    	// The symbol _WIN32_WINNT must be >= 0x0501.
    	// TODO: Add your message handler code here and/or call default
    
    	gThemeManager.ThemeChanged(m_hWnd);
    
    	return 1;
    }
    #endif

    Add this the message map:

    #ifdef FOO_USEXPTHEMES
    	//WM_THEMECHANGED = 0x031A 
    	//(WM_THEMECHANGED may not be available if older windows system
    	//is supported, use value instead)
    	ON_MESSAGE(0x031A, OnThemeChanged)
    #endif


    Now it's only some changes to do in the FooButton class Smile | :) . Add the following in the header:

    #ifdef FOO_USEXPTHEMES
    protected:
    	//Returns a CRect where bitmap/text and so own to be drawn.
    	CRect GetInternalRect(LPDRAWITEMSTRUCT lpDrawItemStruct);
    	int GetXPMode(UINT state) const;
    #endif

    Add this in the file foobutton.cpp. This will give the class access to the global theme manager:

    #ifdef FOO_USEXPTHEMES
    	#include "theme.h"
    	extern CTheme gThemeManager; 
    #endif


    I declared two new member functions. The code for these is:

    #ifdef FOO_USEXPTHEMES
    
    CRect FooButton::GetInternalRect(LPDRAWITEMSTRUCT lpDrawItemStruct)
    {
    	CRect rectButton;
    	GetClientRect (&rectButton);
    
    	if(gThemeManager.m_bXPTheme)
    	{
    		CRect newRectButton = rectButton;
    		
    		CDC* pDC   = CDC::FromHandle(lpDrawItemStruct->hDC);
    		
    		gThemeManager.GetThemeBackgroundContentRect(	*pDC,
    			BP_PUSHBUTTON,
    			GetXPMode( lpDrawItemStruct->itemState ),
    			&rectButton,
    			&newRectButton
    			);
    		
    		rectButton = newRectButton;
    	}
    
    	return rectButton;
    
    }
    
    int FooButton::GetXPMode(UINT state) const
    {
    	BOOL	bPressed = state & ODS_SELECTED;
    	BOOL	bGotFocus = state & ODS_FOCUS;
    	BOOL	bDisabled = state & ODS_DISABLED;
    	
    	// BOOL	bFocusRect = !(state & ODS_NOFOCUSRECT);	// Win2K/XP
    	
    	//TODO: Default button???
    	int		iMode=0; 
    	
    	if (m_bTracking) //m_bHovering)
    	{
    		if (bPressed)
    		{
    			iMode = PBS_PRESSED;
    		}
    		else
    		{
    			iMode = PBS_HOT;
    		}
    	}
    	else
    	{
    		if (!bGotFocus && !bDisabled)
    		{
    			iMode = PBS_NORMAL;
    		}
    			
    		if (bDisabled)
    		{
    			iMode = PBS_DISABLED;
    		}
    
    		if(IsDefault() && !bDisabled)
    			iMode |= PBS_DEFAULTED;
    	}
    
    	return iMode;
    }
    #endif

    Button should always be hot tracked if themes are supported. Replace FooButton::OnMouseMove() with the following:

    void FooButton::OnMouseMove(UINT nFlags, CPoint point) 
    {
    	//Changed by PEK. Always track if XP themes
    #ifndef FOO_USEXPTHEMES
    	if (m_bHot)
    #else
    	{
    	  if (!m_bTracking) 
    	  {
    			TRACKMOUSEEVENT tme;
    			tme.cbSize = sizeof(tme);
    			tme.hwndTrack = m_hWnd;
    			tme.dwFlags = TME_LEAVE;
    			tme.dwHoverTime = HOVER_DEFAULT;
    			m_bTracking = _TrackMouseEvent(&tme);
    		  Invalidate();
    	  }
    	}
    #endif
    
      // Do default action
    	COddButton::OnMouseMove(nFlags, point);
    }

    Replace FooButton::drawFrame with this:

    //! Draws the button's frame
    void FooButton::drawFrame
      (CDC* pDC,
       LPDRAWITEMSTRUCT lpDrawItemStruct)
    {
      ASSERT (pDC != NULL);
      ASSERT (lpDrawItemStruct != NULL);
    
      // Draw button outline if we're not a static button or a hyperlink
      if (!m_bStatic && !m_bHyperlink) {
          if (m_bHot && !m_bChecked)
              drawHotButtonFrame (lpDrawItemStruct);
          else 
    	  {
    #ifdef FOO_USEXPTHEMES
    		  if(gThemeManager.m_bXPTheme)
    		  {
    			  CDC* pDC   = CDC::FromHandle(lpDrawItemStruct->hDC);
    			  
    			  int iMode = GetXPMode( lpDrawItemStruct->itemState ); 
    			  
    			  //if (gThemeManager.m_pTheme)
    			  gThemeManager.DrawThemeBackground(	*pDC, 
    				  &(lpDrawItemStruct->rcItem), 
    				  BP_PUSHBUTTON, 
    				  iMode);			  
    		  }
    		  else
    			  
    #endif
    		  {
    		  // Draw frame for non-hot button
    		  UINT uFrameCtrl = DFCS_BUTTONPUSH;
    		  if (m_bChecked)
    			  uFrameCtrl |= DFCS_CHECKED;
    		  else
    			  if (((lpDrawItemStruct->itemState & ODS_SELECTED) == ODS_SELECTED) && !m_bMulti)
    				  uFrameCtrl |= DFCS_PUSHED;
    			  if ((lpDrawItemStruct->itemState & ODS_DISABLED) == ODS_DISABLED)
    				  uFrameCtrl |= DFCS_INACTIVE;
    			  pDC->DrawFrameControl (&(lpDrawItemStruct->rcItem), DFC_BUTTON , uFrameCtrl);
    				  
    			  // For multi pushbuttons, fill the area of the drop-down indicator with
    			  // a light color if the multi button is currently pressed
    			  if (m_bMulti && m_bMultiClicked)
    				  drawMultiDropDownRegion (pDC, lpDrawItemStruct);
    		  }
          }
      }
    }

    In the function FooButton::drawCaption() I was forced to remove code that calculates the height of the text (couldn't figure out how to do this with themes). However, sense the text a centered vertically I don't think this is necessary. The new function looks like this:

    //! Draws the button's caption
    void FooButton::drawCaption
      (CDC* pDC,
       LPDRAWITEMSTRUCT lpDrawItemStruct,
       int nLeftEdge,
       int& nRightEdge)
    {
      // Nothing to do if the isn't set up to display text
      nRightEdge = 0;
      if (!m_bText)
         return;
    
      // Tweak edges
      if (!m_bCenter)
         nLeftEdge += C_BitmapBorder_X; // looks nicer
      CRect rectButton;
      GetClientRect (&rectButton);
      nRightEdge = rectButton.right;
      if (m_bDropDown)
         nRightEdge -= 6;
      else
         if (m_bMulti) {
            // bool bBitmap = (m_nBitmapId != 0);
            // nRightEdge -= (bBitmap ? 12 : 10);
            nRightEdge -= 12;
         }
    
      // Get available caption width - return if none
      int nAvailableWidth = nRightEdge - nLeftEdge;
      if (nAvailableWidth <= 0)
         return;
    
      // Get caption text
      CString strCaption;
      GetWindowText (strCaption);
    
      // Determine drawing format
      DWORD  dwFormat = DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS;
      dwFormat |= (m_bCenter ? DT_CENTER : DT_LEFT);
      if (m_bMultiLine)
         dwFormat = DT_WORDBREAK | DT_LEFT | DT_WORD_ELLIPSIS | DT_VCENTER;
    
      // Determine dimensions of caption
      CRect rectCaption;
      GetClientRect (&rectCaption);
    
      // NOTE! Height isn't calculated in this version, seems to work well anyway
    #ifdef FOO_USEXPTHEMES
    	rectCaption = GetInternalRect(lpDrawItemStruct);	  
    #endif
    
      strCaption.ReleaseBuffer();
      int nWidth = rectCaption.Width();
    
      // Position caption horizontally within available area
      rectCaption.left = nLeftEdge;
      rectCaption.right = nRightEdge;
      if (m_bCenter && (nAvailableWidth > nWidth)) {
          int nOffsetX = (nAvailableWidth - nWidth) / 2;
          rectCaption.left += nOffsetX;
          rectCaption.right -= nOffsetX;
      }
    
      // Position caption vertically within button
      /*int nOffsetY = (rectButton.Height() - nHeight)/2;
      rectCaption.top += nOffsetY;
      rectCaption.bottom = rectCaption.top + nHeight;*/
    
      // Offset caption if button is pressed
      if ((m_type != FooButton::Type::checkBox) && (m_type != FooButton::Type::radio))
         if (!m_bStatic && !m_bHyperlink && !m_bMultiClicked)
            if (lpDrawItemStruct->itemState & ODS_SELECTED)
               rectCaption.OffsetRect (1, 1);
    
      // Set text color
      pDC->SetBkMode (TRANSPARENT);
      pDC->SetBkColor (::GetSysColor (COLOR_BTNFACE));
      if (m_bHyperlink)
         pDC->SetTextColor (C_HyperlinkColor);
       else
    	  pDC->SetTextColor (::GetSysColor(COLOR_BTNTEXT));
    
    
      // Set underline mode if hyperlink
      CFont* pOldFont = NULL;
      CFont underlineFont;
      if (m_bHyperlink) {
          LOGFONT logFont;
          VERIFY (GetFont()->GetLogFont (&logFont));
          logFont.lfUnderline = TRUE;
          VERIFY (underlineFont.CreateFontIndirect (&logFont));
          pOldFont = pDC->SelectObject (&underlineFont);
      }
    
    
    
    #ifdef FOO_USEXPTHEMES
     
    	//Draw text with theme? (don't do this if hyperlink)
    	if(gThemeManager.m_bXPTheme && !m_bHyperlink)
    	{
    		//No need for special code if disabled		
    		gThemeManager.DrawThemeText(	pDC->m_hDC,
    			BP_PUSHBUTTON,
    			GetXPMode(lpDrawItemStruct->itemState),
    			strCaption,
    			strCaption.GetLength(),
    			dwFormat,
    			NULL,
    			&rectCaption
    		);
    	}
    	else
    #endif
    
    	{
    		// Draw caption
    		if ((lpDrawItemStruct->itemState & ODS_DISABLED) == ODS_DISABLED)
    		{
    			::OffsetRect (&rectCaption, 1, 1);
    			::SetTextColor (pDC->m_hDC, ::GetSysColor (COLOR_3DHILIGHT));
    			::DrawTextEx (	pDC->m_hDC, 
    				strCaption.GetBuffer(0), 
    				strCaption.GetLength(),
    				&rectCaption,
    				dwFormat,
    				NULL);
    			strCaption.ReleaseBuffer();
    			
    			::OffsetRect (&rectCaption, -1, -1);
    			::SetTextColor (pDC->m_hDC, ::GetSysColor (COLOR_GRAYTEXT));
    			::DrawTextEx (pDC->m_hDC, strCaption.GetBuffer(0), strCaption.GetLength(),
    				&rectCaption,
    				dwFormat,
    				NULL);
    		}
    		else
    			::DrawTextEx (pDC->m_hDC, strCaption.GetBuffer(0), strCaption.GetLength(),
    			&rectCaption,
    			dwFormat,
    			NULL);
    	}
    
      strCaption.ReleaseBuffer();
    
      // Free underline font
      if (pOldFont != NULL)
         pDC->SelectObject (pOldFont);
    }

    I did a minor changing in FooButton::drawFocus():

    //! Draws the button's focus rectangle
    void FooButton::drawFocus
      (CDC* pDC,
       LPDRAWITEMSTRUCT lpDrawItemStruct,
       int nLeftEdge,
       int nRightEdge)
    {
      ASSERT (pDC != NULL);
      ASSERT (lpDrawItemStruct != NULL);
    
      // Draw focus rectangle (for non-static non-hot buttons)
      if (!m_bStatic && !m_bHot && (getFocusStyle() != FooButton::Focus::noFocus))
         if ((lpDrawItemStruct->itemState & ODS_FOCUS) == ODS_FOCUS) 
    	 {
            CRect rectButton;
    
    #ifdef FOO_USEXPTHEMES
    		if(gThemeManager.m_bXPTheme)
    		{
    			rectButton = GetInternalRect(lpDrawItemStruct);
    		}
    		else
    #endif
    		{
    			GetClientRect (&rectButton);
    			rectButton.left = nLeftEdge + 4;
    			rectButton.right = nRightEdge - 4;
    			rectButton.top += 4;
    			rectButton.bottom -= 4;
    		}
    
            ::DrawFocusRect (pDC->m_hDC, &rectButton);
         }
    }

    It's no need to draw a default border if themes are activated (this is already done in drawFrame()). So FooButton::drawDefaultBorder() could be replaced with:

    void FooButton::drawDefaultBorder
      (CDC* pDC,
       LPDRAWITEMSTRUCT lpDrawItemStruct)
    {
      ASSERT (pDC != NULL);
      ASSERT (lpDrawItemStruct != NULL);
    
      // Only pushbuttons can have a default border
      FooButton::Type btnType = getType();
      if ((btnType != FooButton::Type::pushButton) &&
          (btnType != FooButton::Type::pushButtonDropDown) &&
          (btnType != FooButton::Type::pushButtonMulti))
         return;
    
      //No need if themes are used
    #ifdef FOO_USEXPTHEMES
    	if(gThemeManager.m_bXPTheme)
    		return;
    
    #endif
    
      // Nothing to do if the default border isn't required
      if (getFocusStyle() != FooButton::Focus::defaultFocus)
         return;
    
      // Nothing do if the button doesn't have focus or isn't the default button
      if (!IsDefault())
         return;
    
      COLORREF rgbBorder = GetSysColor (COLOR_3DDKSHADOW);
      CBrush borderBrush (rgbBorder);
      CRect innerRect = lpDrawItemStruct->rcItem;
      if ((lpDrawItemStruct->itemState & ODS_SELECTED) == 0) {
    
          COLORREF rgbBorder = GetSysColor (COLOR_3DDKSHADOW);
          CPen borderGreyPen (PS_SOLID, 1, rgbBorder);
          CPen* pOldPen = pDC->SelectObject (&borderGreyPen );
    
          pDC->MoveTo (innerRect.right - 2, innerRect.top + 1);
          pDC->LineTo (innerRect.right - 2, innerRect.bottom - 2);
          pDC->LineTo (innerRect.left, innerRect.bottom - 2);
    
          COLORREF rgbBrGrey = GetSysColor (COLOR_3DHILIGHT);
          CPen brightGreyPen (PS_SOLID, 1, rgbBrGrey);
          pOldPen = pDC->SelectObject (&brightGreyPen);
    
          pDC->MoveTo (innerRect.right - 3, innerRect.top + 1);
          pDC->LineTo (innerRect.left + 1, innerRect.top + 1);
          pDC->LineTo (innerRect.left + 1, innerRect.bottom - 2);
    
          COLORREF rgbDkGrey = GetSysColor (COLOR_BTNSHADOW);
          CPen darkGreyPen (PS_SOLID, 1, rgbDkGrey);
          pOldPen = pDC->SelectObject (&darkGreyPen);
          pDC->MoveTo (innerRect.right - 3, innerRect.top + 2);
          pDC->LineTo (innerRect.right - 3, innerRect.bottom - 3);
          pDC->LineTo (innerRect.left + 1, innerRect.bottom - 3);
          pDC->SelectObject (pOldPen);
    
      } else {
          innerRect.InflateRect (1, 1, -1, -1);
          pDC->FrameRect (&innerRect, &borderBrush);
      }
      pDC->FrameRect(&lpDrawItemStruct->rcItem, &borderBrush);
    }

    I think that is all! However, themes are not fully supported. Radio buttons and check boxes are drawn in the old way, but it shouldn't be hard to fix this. I also think that GetInternalRect() should be used more often. This returns the inner area inside the frame. This could probably be used to place the bitmap more correctly.

    And I'm sure that a few minor bugs are added, but the code seems to do what I want it to do Smile | :) .
    AnswerFooButton::OnThemeChanged is removed. Pin
    PEK3-Sep-04 22:50
    PEK3-Sep-04 22:50 
    GeneralRe: FooButton::OnThemeChanged is removed. Pin
    Ravi Bhavnani4-Sep-04 3:54
    professionalRavi Bhavnani4-Sep-04 3:54 
    AnswerRe: How to implement support for Windows XP themes Pin
    PEK21-Sep-04 0:49
    PEK21-Sep-04 0:49 
    AnswerText color fix Pin
    PEK29-Sep-04 21:29
    PEK29-Sep-04 21:29 
    AnswerRe: How to implement support for Windows XP themes Pin
    AlexXF24-Nov-04 6:27
    AlexXF24-Nov-04 6:27 
    GeneralOnLButtonUp Pin
    PEK31-Aug-04 11:47
    PEK31-Aug-04 11:47 
    GeneralRe: OnLButtonUp Pin
    Ravi Bhavnani1-Sep-04 4:06
    professionalRavi Bhavnani1-Sep-04 4:06 
    QuestionWhar about XP themes support? Pin
    AlexXF25-Aug-04 10:21
    AlexXF25-Aug-04 10:21 
    AnswerRe: Whar about XP themes support? Pin
    Ravi Bhavnani25-Aug-04 10:37
    professionalRavi Bhavnani25-Aug-04 10:37 
    GeneralRe: Whar about XP themes support? Pin
    AlexXF26-Aug-04 9:54
    AlexXF26-Aug-04 9:54 
    GeneralRe: Whar about XP themes support? Pin
    Ravi Bhavnani26-Aug-04 10:09
    professionalRavi Bhavnani26-Aug-04 10:09 
    GeneralArticle forum clobbered Pin
    Ravi Bhavnani7-Aug-04 14:56
    professionalRavi Bhavnani7-Aug-04 14:56 
    QuestionSimple issue !? Pin
    Amer Gerzic18-Jul-04 4:24
    Amer Gerzic18-Jul-04 4:24 
    AnswerRe: Simple issue !? Pin
    Ravi Bhavnani18-Jul-04 4:47
    professionalRavi Bhavnani18-Jul-04 4:47 
    GeneralShortcut for the pushbutton Pin
    geoyar16-Jul-04 10:20
    professionalgeoyar16-Jul-04 10:20 
    GeneralRe: Shortcut for the pushbutton Pin
    Ravi Bhavnani17-Jul-04 8:29
    professionalRavi Bhavnani17-Jul-04 8:29 
    GeneralMulti pushbutton Pin
    Mandalay12-Jul-04 7:11
    Mandalay12-Jul-04 7:11 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.