Click here to Skip to main content
15,867,330 members
Articles / Desktop Programming / MFC
Article

CImageButtonWithStyle - Buttons using Images with XP Visual Styles

Rate me:
Please Sign up or sign in to vote.
4.50/5 (27 votes)
24 Nov 2005CPOL6 min read 268.5K   4.8K   72   58
How to get buttons using an icon or bitmap to use XP visual styles.

Introduction

Imagine you've finally gotten around to adding a manifest file to your MFC application so all your controls will take advantage of the new XP visual styles. One of your dialogs mixes buttons with ordinary text captions along with buttons using images as captions, and it ends up looking like this:

Sample Dialog without CImageButtonWithStyle class

That's not what you wanted! The buttons with text labels came out the way you expected, but those using images are ignoring the XP visual style setting and are being drawn with the old 3D-effect. You want all your buttons to use the XP visual styles, like this:

Sample Dialog using CImageButtonWithStyle class

CImageButtonWithStyle is a small class that makes this easy, and it won't change how your application runs on pre-XP Windows versions.

Background

Windows applications don't automatically use the new "theme aware" version of the common controls library comctl32.dll when running under Windows XP. Your application has to include a manifest file as one of its resources to tell Windows you want to use the newer version of the library, since there are some minor incompatibilities. See the article Add XP Theme Style to your current projects by Jian Hong for more details (or look over the demo app for this article).

If you just want to test out what happens without making a permanent change, just copy a suitable manifest file to same directory as your executable appname.exe and rename it to appname.exe.manifest.

You'll notice that buttons using icons or bitmaps (using window style flags BS_ICON or BS_BITMAP) are rendered with the 3D-effect you would expect if visual styles weren't in use. This behavior makes sense in some cases, like bitmaps that completely fill the face of the button. In other cases, like when mixing symbolic and text captions, it just looks ugly.

You would think there would be a simple solution to choose the new appearance (like an extended style flag), but I searched in vain. There were a number of published solutions that used the BS_OWNERDRAW style to completely take over all button rendering, but this means re-implementing most of the basic behavior of the button control (see the articles CXPStyleButtonST v1.2 by Davide Calabro or Native Win32 Theme aware Owner-draw Controls without MFC by Ewan Ward). It's very hard to be sure these classes will behave just like normal Windows button controls in all other respects.

Forget BS_OWNERDRAW, Just Use NM_CUSTOMDRAW Instead

Fortunately, SfaeJ had added a comment to Ewan Ward's article that put me on the right path. When running with the newer version of comctl32.dll, the button control sends NM_CUSTOMDRAW notifications. Once I knew what to look for, I was able to find some small tidbits in Microsoft's documentation and get a working solution.

Using the code

Using the CImageButtonWithStyle is simple.

  1. First, add the source files for the class CImageButtonWithStyle and its helper class CVisualStylesXP to your project (the four files: ImageButtonWithStyle.h, ImageButtonWithStyle.cpp, VisualStylesXP.h and VisualStylesXP.cpp).
  2. Add a CButton member to your dialog for each button that will display an image and associate it with the Windows control. If you use the Visual Studio class wizard, it will both add the member variable and add a call to DDX_Control( in your CDialog derived class' DoDataExchange() override). This will associate the CButton instance with the Windows control created by the dialog template.
    void CSampleDlg::DoDataExchange(CDataExchange* pDX)
    {
        CDialog::DoDataExchange(pDX);
        DDX_Control(pDX, IDC_BUTTON, m_wnd_button);
        // other DDX_ and DDV_ calls
    }
  3. Now, add the line #include "ImageButtonWithStyle.h" to the header for your CDialog derived class, and change the declaration of the CButton members to use the CImageButtonWithSyle class instead.
    CImageButtonWithStyle m_wnd_button;
  4. Recompile and you're done.

If you want to use CImageButtonWithStyle in other situations, you will have to call SubclassDlgItem() or SubclassWindow() to associate the CImageButtonWithStyle instance with a particular control (unless you create the control dynamically by calling the Create() member function from a CImageButtonWithStyle instance).

The Demo Application

The demo application was produced using the Visual Studio App-Wizard. I created a minimal SDI application and added a menu command "View|View Dialog..." to invoke the sample dialog shown at the top of the article.

Under the Hood

I derived my new CImageButtonWithStyle class from the MFC CButton class and added a handler for the NM_CUSTOMDRAW notification. If an older version of comctl32.dll is being used (i.e. pre Windows XP), then no NM_CUSTOMDRAW notifications will be sent to button controls, so my handler won't even be invoked. There should be no worries about backwards compatibility, since the new code isn't active in this case.

Rather than calling the uxtheme.dll visual styles API functions directly, I used the CVisualStylesXP class from David A. Zhao's article Add XP Visual Style Support to OWNERDRAW Controls to load the uxtheme.dll dynamically. When running on older Windows versions where uxtheme.dll isn't present, CVisualStylesXP is unable to load the library, so it provides stub versions of the functions that always fail. If I called the uxtheme.dll functions directly, applications using CImageButtonWithStyle wouldn't be able to load on older Windows versions.

The NM_CUSTOMDRAW handler OnNotifyCustomDraw() is passed a pointer to an NMCUSTOMDRAW structure. The CImageButtonWithStyle handler does the following:

  1. If the button control doesn't have either of the style flags BS_ICON or BS_BITMAP set, or if XP visual style themes aren't in use, then the handler simply returns CDRF_DODEFAULT. This causes the default window procedure (provided by comctl32.dll) to render the button just like it normally would (just as if I hadn't handled the NM_CUSTOMDRAW in the first place).
  2. If the dwDrawStage member of the NMCUSTOMDRAW structure has the value CDDS_PREERASE, erase the background by calling the DrawThemeParentBackground() member of the global CVisualStylesXP instance g_xpStyle.
  3. Get a handle to the XP visual styles theme data by calling g_xpStyle.OpenThemeData().
  4. Use the button's window style and the uItemState member of the NMCUSTOMDRAW structure to figure out which button state to use for drawing the background: PBS_DISABLED, PBS_PRESSED, PBS_HOT, PBS_DEFAULTED or PBS_NORMAL, and then draw it using g_xpStyle.DrawThemeBackground().
  5. Get the rectangle describing the interior of the button image from g_xpStyle.GetThemeBackgroundContentRect().
  6. Close the theme handle with g_xpStyle.CloseThemeData().
  7. Use the button style bits BS_LEFT, BS_RIGHT, BS_TOP and BS_BOTTOM to determine the image position, and then draw the bitmap or icon image using the Windows DrawState() function (passing in the flag DSS_DISABLED if the button's window style includes the WS_DISABLED flag).
  8. If uItemState includes the flag CDIS_FOCUS, call DrawFocusRect() to draw the focus rectangle just inside the border.
  9. Finally, set the return result to CDRF_SKIPDEFAULT to tell the default window procedure (from comctl32.dll) not to draw the button (since my code already has).

License

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


Written By
Software Developer
Canada Canada
Stephen was originally trained as an experimental physicist at the University of Toronto. He specialized in physics at ultra-low temperatures, and has worked at a number of universities: University of B.C. in Vancouver, British Columbia; Queen's University in Kingston, Ontario and Leiden University in the Netherlands.

Since 1996 he has worked for a small firm that provides custom software for the broadcasting industry based outside Toronto, Canada.

Comments and Discussions

 
GeneralQuestion for Visual Studio 2005 Pin
prolong29-Apr-06 22:49
prolong29-Apr-06 22:49 
GeneralRe: Question for Visual Studio 2005 Pin
Stephen C. Steel9-May-06 4:57
Stephen C. Steel9-May-06 4:57 
GeneralRe: Question for Visual Studio 2005 Pin
Mr.-G22-Sep-06 5:25
Mr.-G22-Sep-06 5:25 
GeneralRe: Question for Visual Studio 2005 Pin
edwardking23-Dec-06 9:32
edwardking23-Dec-06 9:32 
GeneralRe: Question for Visual Studio 2005 Pin
Tony Matthews13-Mar-07 5:40
Tony Matthews13-Mar-07 5:40 
GeneralDisabled State with Alpha Bitmaps Pin
csturg11-Apr-06 7:39
csturg11-Apr-06 7:39 
GeneralRe: Disabled State with Alpha Bitmaps Pin
dc_20008-May-06 18:29
dc_20008-May-06 18:29 
GeneralRe: Disabled State with Alpha Bitmaps Pin
™byTM21-Oct-09 2:26
™byTM21-Oct-09 2:26 
I can confirm the problem is the alpha channel, and the "imperfection" of API function DrawState to handle 32bit icons (with alpha channel) and the flag value DSS_DISABLED. This problem can be solved converting the colored icon to a grayed icon and draw it on the button instead using DrawState.
I enhanced the class for my own purpose to show disabled 32 bit icons (with alpha channel) correctly.
GeneralRe: Disabled State with Alpha Bitmaps Pin
Nic Wilson25-Jul-10 17:27
Nic Wilson25-Jul-10 17:27 
GeneralMemory leak Pin
Rubén V.30-Mar-06 6:36
Rubén V.30-Mar-06 6:36 
GeneralRe: Memory leak Pin
Stephen C. Steel30-Mar-06 8:13
Stephen C. Steel30-Mar-06 8:13 
GeneralRe: Memory leak Pin
Eric Strennen23-Apr-10 7:42
Eric Strennen23-Apr-10 7:42 
GeneralRe: Memory leak Pin
Nic Wilson25-Jul-10 17:32
Nic Wilson25-Jul-10 17:32 
Generalcomctl32.dll 5.82 Pin
SamJo7-Mar-06 10:57
SamJo7-Mar-06 10:57 
GeneralRe: comctl32.dll 5.82 Pin
Stephen C. Steel7-Mar-06 14:23
Stephen C. Steel7-Mar-06 14:23 
GeneralRe: comctl32.dll 5.82 Pin
SamJo7-Mar-06 21:31
SamJo7-Mar-06 21:31 
GeneralRe: comctl32.dll 5.82 Pin
Stephen C. Steel8-Mar-06 4:14
Stephen C. Steel8-Mar-06 4:14 
GeneralRe: comctl32.dll 5.82 Pin
SamJo8-Mar-06 21:45
SamJo8-Mar-06 21:45 
GeneralRe: comctl32.dll 5.82 Pin
Stephen C. Steel9-Mar-06 5:39
Stephen C. Steel9-Mar-06 5:39 
GeneralRe: comctl32.dll 5.82 Pin
SamJo9-Mar-06 6:34
SamJo9-Mar-06 6:34 
GeneralRe: comctl32.dll 5.82 Pin
Ceri30-Mar-06 23:05
Ceri30-Mar-06 23:05 
GeneralDrawFrameControl Pin
SamJo28-Feb-06 0:12
SamJo28-Feb-06 0:12 
GeneralRe: DrawFrameControl Pin
Stephen C. Steel28-Feb-06 5:21
Stephen C. Steel28-Feb-06 5:21 
GeneralNeed help Pin
jack lai22-Feb-06 18:57
jack lai22-Feb-06 18:57 
GeneralRe: Need help Pin
Stephen C. Steel23-Feb-06 4:52
Stephen C. Steel23-Feb-06 4:52 

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.