Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / Win32

Custom Controls in Win32 API: Visual Styles

Rate me:
Please Sign up or sign in to vote.
4.98/5 (66 votes)
17 Mar 2014CPOL33 min read 131K   5.7K   106   24
Paint your control consistently with standard/common controls, using the visual styles API.

Articles in this Series

Introduction

The last time we discussed the basics of custom controls' painting. Today, we will continue in the same direction and we will explore visual styles (a.k.a. XP theming).

For creation of high quality custom controls, using this API properly is essential, yet a quite complex task, so this article is longer than its predecessors and I kindly ask my readers for some patience when reading it. It will take some time before we really start using the API because first of all, we must take a look at Windows theming from a somewhat broader perspective.

UXTHEME.DLL and COMCTL32.DLL

The theming API is provided by the library UXTHEME.DLL which appeared in Windows XP. The two following screenshots demonstrate the difference between unthemed and themed user experience:

An unthemed dialog (Windows 2000)

An unthemed dialog (Windows 2000)

A themed dialog (Windows 7, the default theme)

A themed dialog (Windows 7, the default theme Aero)

The introduction of this theming library also affected the common controls library, COMCTL32.DLL. For compatibility reasons, Windows XP (and all newer Windows versions) are equipped with two different versions of COMCTL32.DLL.

At the standard path C:\Windows\System32\, the old COMCTL32.DLL version 5 can be found. This library version contains the implementation of standard controls like list view, combo-box, etc., which are unaware of UXTHEME.DLL existence, hence applications linked with this library generally are not themed.

The newer version 6 of COMCTL32.DLL resides under C:\Windows\WinSxS\, available as a side-by-side assembly. Only applications explicitly manifesting their compatibility with the library version 6 use it. The other applications simply continue to use the version 5.

Also note, that historically some controls used to be implemented in USER32.DLL (window classes like, for example, BUTTON or EDIT), while others (known as common controls) resided in COMCTL32.DLL. This has changed with the introduction of COMCTL32.DLL version 6, and now all theming-aware controls live there.

Gotcha: The old (unthemed) implementation of standard controls is still available in USER32.DLL. If you instruct the linker to link your application with COMCTL32.DLL, it may silently omit it if the application never calls any function from it. Hence I recommend you to call InitCommonControls() when initializing the application. Otherwise, the app may be just unthemed instead of not working, using the old controls, masking perfectly the root cause of the problem.

Application Manifest

The most usual and natural way of how the application can tell the system its desire to use version 6 of the library COMCTL32.DLL, is to specify it in its manifest. The manifest can be a separate file, but the preferred way is to embed it in the application binary as a resource.

As a separate file, it has to have exactly the same name as the application, with the suffix ".manifest" added (e.g., example.exe.manifest for application example.exe) and it must be located in the same directory.

When embedded as a resource, it has to have a resource ID 1 and type RT_MANIFEST, so typically the following line is present in the application's resource script (.RC file):

1 RT_MANIFEST path/to/manifest.xml

Either way, the manifest is an XML document and it should look like this:

XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="app name" type="win32"/>
    <description>app description</description>
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" 
              version="6.0.0.0" processorArchitecture="*" 
              publicKeyToken="6595b64144ccf1df" language="*"/>
        </dependentAssembly>
    </dependency>
    <ms_asmv2:trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
        <ms_asmv2:security>
            <ms_asmv2:requestedPrivileges>
                <ms_asmv2:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
            </ms_asmv2:requestedPrivileges>
        </ms_asmv2:security>
    </ms_asmv2:trustInfo>
</assembly>

Of course for your application the manifest should be adapted to use its name, description, version, and also the processor it is supposed to run on ("X86", "amd64", or "*" meaning any CPU type) in the first <assemblyIdentity> tag.

When an application is being launched on Windows XP or newer, the system (in particular, the application loader) examines the binary and looks whether it can find the manifest. If yes, then the application loader loads DLLs in the versions to satisfy the requirements in the manifest. (Strictly speaking this works only for DLLs available as side-by-side assemblies, not for every DLL. But COMCTL32.DLL fulfills this requirement.)

In our context, the most interesting part of the manifest is the specification of the dependent assembly, identified as Microsoft.Windows.Common-Controls, where we explicitly ask the system for version 6 of COMCTL32.DLL.

Hence, when implementing a reusable control, we should naturally follow and respect the application's manifest too. If the app proclaims it supports COMCTL32.DLL version 6, the custom control should follow it and use the theming API as COMCTL32.DLL does. When the app is not themed, the custom control should respect it and its look should be consistent with it.

Even if the control is implemented in a separate DLL and it has no control over in what application its code is executed in, we can use the version of COMCTL32.DLL as an indicator for the decision making at run time:

C++
#include <windows.h>
#include <shlwapi.h>

BOOL ShouldUseUxThemeDll(void) 
{
    HMODULE hDll;
    DWORD dwMajorVersion = 0;

    hDll = LoadLibrary(_T("COMCTL32.DLL"));
    if(hDll != NULL) {
        DLLGETVERSIONPROC fn_DllGetVersion;
        DLLVERSIONINFO vi;

        fn_DllGetVersion = (DLLGETVERSIONPROC) GetProcAddress(hDll, "DllGetVersion");
        if(fn_DllGetVersion != NULL) {
            vi.cbSize = sizeof(DLLVERSIONINFO);
            fn_DllGetVersion(&vi);
            dwMajorVersion = vi.dwMajorVersion;
        }
        FreeLibrary(hDll);
    }

    return (dwMajorVersion >= 6);
}

Note that the return value of this function does not change for the life time of the application. (That is, unless the application does something nasty with FreeLibrary() and LoadLibrary(), changing the version of COMCTL32.DLL in process run time. That is such an obscure case, that, in my opinion, we can solve this issue by ignoring it...).

Disabling Visual Styles

Even if an application asks for COMCTL32.DLL version 6 using the manifest, the standard controls may still be painted unthemed, as (in some Windows versions) the user can disable the theming in the whole user session (in Control Panels), or for the particular application (the Properties of the particular .exe file, Compatibility tab).

Note: The setting in Control Panel applies immediately to all (theme-aware) applications, even for those already running. Disabling visual styles on the Compatibility tab of the application's Properties does not affect already running processes. It only applies for any .EXE started after the change.

Gotcha: At least on 64-bit Windows 7, disabling visual themes for a 64-bit application in the Compatibility tab is silently ignored by the system. I guess Microsoft felt no need to provide that compatibility option for 64-bit applications: Prior to Windows XP, there were no 64-bit programs in the first place. So, likely, they just forgot to hide or disable the check-box for 64-bit apps on that dialog.

On Windows 8, the option to disable themes for an application has disappeared altogether.

Compatibility and Testing

Windows 2000 (and older) do not support themes at all and the library UXTHEME.DLL does not exist there. If you aspire to support Windows 2000, then you should load UXTHEME.DLL in run time with LoadLibrary() and GetProcAddress(). If that fails, fall back in your control implementation to the non-themed GDI code paths.

Even when UXTHEME.DLL is present, different Windows versions come with quite different default themes and a small set of alternative themes (usually only color variations of the default one). (Only Windows Vista and 7 share the same themes.)

Of course all of that increases the amount of circumstances under which the control should be tested. As a bare minimum, you should test your control on Windows XP, Windows 7 (or Vista), and on Windows 10 (or 8), and also while the themes are disabled.

Using UXTHEME.DLL

To use the theming API, you need to link (or load in run time) UXTHEME.DLL, and include its header uxtheme.h. There are also accompanying headers vsstyle.h and vssym32.h we will mention later.

Note: UXTHEME.DLL does not follow the historic Win32 API string duality. Whenever dealing with strings, Win32 API usually provides two functions for the same thing, one for Unicode (functions ending with the suffix "-W') and the other one for ANSI strings (suffix "-A"), and a macro (without a suffix) which resolves to one of those depending on whether macro UNICODE is defined.

On the other hand UXTHEME.DLL uses strictly Unicode. It includes functions for painting text as well as various string identifiers (i.e., theme class and subclass names and values of string properties; these terms shall be explained further in the article).

Theme Definition

UXTHEME.DLL itself is a relatively simple DLL. It actually doesn't know anything about how to paint any particular control or its part. Instead, most of its functions just know how to access data of the currently active visual theme, which I call theme definition for the purposes of this article. It is the theme definition as provided on Windows (and the lack of its documentation) which causes more difficulties for the implementation of custom controls rather than UXTHEME.DLL itself.

The theme definition, in general, is a structured storage which defines a hierarchy of some objects (classes, subclasses, parts, states; we will discuss them below) and associates them with various resources (mainly images) and properties describing many aspects of them.

The core of the storage is a resource-only DLL (albeit with a different file extension). For example on Windows 7, the DLL is usually on the path C:\Windows\Resources\Themes\Aero\aero.msstyles. However your application should not rely on such an implementation detail at all.

On Windows XP the default theme definition is called Luna. On Vista and 7 it is Aero. You may already know these names as they were appearing even in Microsoft marketing materials.

The top-level category in the hierarchy of objects defined by the theme definition is class. Please do not confuse the theme class with the term from object-oriented programming or window classes. The class is identified with its string name (the second parameter of OpenThemeData()) and in most (but not all) cases it provides data for a particular standard control, or other aspects of the user interface (e.g., the Start menu, Control Panels, etc.).

Many theme classes actually have the same name as some of the window class names of standard controls (e.g., BUTTON, EDIT, TREEVIEW) but in general the namespace of both are different and there are some standard window classes which do not have their own dedicated counterpart in the Windows theme definitions, and vice versa. A prominent example of such a theme class can be TEXTSTYLE which defines mainly font attributes for various text labels, for general use by applications. (Note though this theme class was added in Aero. Luna on Windows XP does not have it.)

There may also be something called subclasses, which may provide different, alternative, data for the same kind of control or other elements of the Windows user interface, but we will cover them a bit later, to make the main principles easier to understand.

In the second level, below the class in the hierarchy, there are parts. Every part of the given class is identified by an integer and it describes some partial aspect of the theme class which can be painted independently on the other parts of the same theme class, e.g. a border frame, a background, a glyph, a button for the scrollbar or a combo box.

In the third level of the hierarchy, below parts, there are states. Like part, every state is also identified by an integer. Different states of a single part are used to reflect various states of the control, e.g., when it is disabled, when it has focus, or when it is hot (i.e., under the mouse cursor). Parts which are not supposed to change their look according to the control's internal state often have only one state, usually identified by value 1. Other parts, which do change their appearance to reflect the state of the control, may define many states.

Many API functions working with themes take the part and state together with the theme handle as their input. In the relatively rare cases when you need to refer to the part as a whole, you may use state 0 as the API guarantees no state has this identifier. The same stands for parts. Part 0 means the caller has no particular part in mind.

Furthermore, each object on each hierarchy level (including the theme definition as a whole) can have many properties associated with it. The properties vary a lot and can also have different types and they describe many attributes of the objects. Again, we will say a few words about them later.

Windows SDK provides headers vssym32.h and vsstyle.h which provide some symbolic identifiers to the parts and states (enumerations) and also for properties (defined as preprocessor macros). (Some older tutorials on the net may use tmschema.h instead. But Microsoft declared this header obsolete in more recent Windows SDKs. It is less complete and incompatible with the newer headers so avoid it in new code.)

For example, the standard window class BUTTON has a corresponding theme class BUTTON, and it consists of parts:

enum BUTTONPARTS {
    BP_PUSHBUTTON = 1,
    BP_RADIOBUTTON = 2,
    BP_CHECKBOX = 3,
    BP_GROUPBOX = 4,
    BP_USERBUTTON = 5,
    BP_COMMANDLINK = 6,
    BP_COMMANDLINKGLYPH = 7
};

Then there are other enumerations which list all the states for each button part, for example for PB_PUSHBUTTON:

enum PUSHBUTTONSTATES {
    PBS_NORMAL = 1,
    PBS_HOT = 2,
    PBS_PRESSED = 3,
    PBS_DISABLED = 4,
    PBS_DEFAULTED = 5,
    PBS_DEFAULTED_ANIMATING = 6
};

Theme Handle Management

Now, finally, we get to the point where we can start exploring the theming API itself.

Once the control decides it wants to use the visual styles, it needs a valid theme handle (HTHEME). The handle is an opaque type representing the given theme class (or subclass). This handle can be obtained with the function OpenThemeData() (second parameter of it is the class name), and released with CloseThemeData(). All functions for themed painting expect this handle as their first parameter.

Note: The application must be also ready for the situation when the desired theme can not be opened and OpenThemeData() returns NULL. That can happen either because the currently selected theme does not know the desired theme class name, or because theming is disabled as discussed above. Typically, the control should then fall back to a code path, which paints the control without themes, in the plain old way to fit in the gray old unthemed dialog.

Furthermore, the control has to reopen the theme handle and repaint itself with it whenever it receives the message WM_THEMECHANGED, which is a notification that some relevant theme settings have changed (e.g., the user has selected a different theme in Control Panel).

So, in a typical control implementation, the relevant parts of the control implementation may look like this:

C++
#include <uxtheme.h>

// Class name for this control.
static const WCHAR[] wszClass = L"BUTTON"

typedef struct CustomData_tag CustomData;
struct CustomData_tag {
    HWND hwnd;       // handle of control window
    HTHEME hTheme;   // theme handle (NULL when not themed)
    // ...
};

static void
CustomPaint(CustomData* pData, HDC hDC, RECT* rcDirty, BOOL bErase)
{
    if(pData->hTheme != NULL) {
        // Paint with themes.
        // ...
    } else {
        // Fallback to old simple grayish look, painted with plain GDI.
        // ...
    }
}

static LRESULT CALLBACK
CustomProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CustomData* pData = (CustomData*) GetWindowLongPtr(hwnd, 0);

    switch(uMsg) {
        case WM_ERASEBKGND:
            return FALSE;

        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            BeginPaint(hwnd, &ps);
            CustomPaint(pData, ps.hdc, &ps.rcPaint, ps.fErase);
            EndPaint(hwnd, &ps);
            return 0;
        }
 
        case WM_THEMECHANGED:
            if(pData->hTheme)
                CloseThemeData(pData->hTheme);
            pData->hTheme = OpenThemeData(hwnd, wszClass);
            InvalidateRect(hwnd, NULL, TRUE);
            return 0;

        case WM_CREATE:
            if(!DefWindowProc(hwnd, uMsg, wParam, lParam))
                return -1;
            pData->hTheme = OpenThemeData(hwnd, wszClass);
            return 0;

        case WM_DESTROY:
            if(pData->hTheme)
                CloseThemeData(pData->hTheme);
            break;

        case WM_NCCREATE:
            if(!DefWindowProc(hwnd, uMsg, wParam, lParam))
                return FALSE;
            pData = malloc(sizeof(CustomData));
            if(pData == NULL)
                return FALSE;
            ZeroMemory(pData, sizeof(CustomData));
            pData->hwnd = hwnd;
            SetWindowLongPtr(hwnd, 0, (LONG_PTR)pData);
            return TRUE;

        case WM_NCDESTROY:
            if(pData != NULL)
                free(pData);
            break;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Note that for the sake of readability, this example has skipped the checking of the old COMCTL32.DLL version with ShouldUseUxThemeDll() as recommended in the earlier section. The attached downloadable code (at the top of this article) is more complete in this regard.

Also note we will supplement the function CustomPaint() with some real painting code later in this article, after a short intermezzo.

Useless IsThemeActive() and IsAppThemed()

Equipped with the knowledge about theme handle management, let's return little bit back to the discussion about when we should paint with themes and when not to. When checking whether the application or control should be painted with themes, there are two UXTHEME.DLL functions, IsThemeActive() and IsAppThemed(), which developers often attempt to use for this purpose but which cause a lot of confusion. Therefore, let's explain them a bit more.

IsThemeActive() only asks whether the user has enabled themes in his session. It usually returns TRUE, unless the user selected the classical look and feel in Control Panel (so, on Windows 8, it always returns TRUE). In particular, it can return TRUE even when the theming is disabled for the particular application in its Properties, or when the application otherwise is unaware of themes and uses COMCTL32.DLL version 5.

All that matters is the global setting in the Control Panel. Therefore the return value does not say anything about whether the caller should use the visual themes or not.

The other function, IsAppThemed(), does much more. Actually too much, in my opinion. It checks that themes are allowed globally (the same as IsThemeActive()) and then also that the application wants to be themed (manifest) and that themes are not disabled (Compatibility tab of its Properties).

It returns TRUE only when all the three conditions are true. In other words, it asks whether OpenThemeData() would, for reasonable valid input parameters, return a valid non-NULL theme handle at the moment. If it returns FALSE, any call to OpenThemeData() is guaranteed to return NULL.

So, you may just call OpenThemeData() directly and use the returned handle for themed painting, or fall back to unthemed painting when it returns NULL. Again you don't need IsAppThemed() for anything.

Also note that both functions depend on the current settings in the Control Panel, and those may be changed anytime, during the lifetime of your application: I have seen quite a lot of applications which calls one or the other function during app initialization to decide whether to use theming API at all or not. If you need that, use ShouldUseUxThemeDll() as provided aerlier in this article and keep the rest on OpenThemeData() . It knows when to return NULL.

Themed Painting

Alright, back to the themed painting.

Let's supplement code from the section about handle management to paint a button-like control, for example. Note the control is not interactive so we use a constant part and state. Real controls would remember and change the state of the control by monitoring various mouse and keyboard messages in a window procedure.

C++
static void CustomPaint(CustomData* pData, HDC hDC, RECT* rcDirty, BOOL bErase)
{
    RECT rect;
    GetClientRect(pData->hwnd, &rect);

    if(pData->hTheme != NULL) {
        int part;
        int state;

        part = BP_BUTTON;
        state = PBS_NORMAL;
       
        if(IsThemeBackgroundPartiallyTransparent(pData->hTheme, part, state))
            DrawThemeParentBackground(pData->hwnd, hDC, &rect);
        DrawThemeBackground(pData->hTheme, hDC, part, state, &rect, &rect);
    } else {
        // Fallback to old simple grayish look, painted with plain GDI.
        DrawFrameControl(hDC, &rect, DFC_BUTTON, DFCS_BUTTONPUSH);
    }
}

The code above is almost canonical. Every tutorial on the topic shows something like this snippet of code. Two things are essential.

First, the themes in general support (partially) transparent controls so the painting code has to reflect this and (when needed) ask the parent window to paint itself within the transparent areas.

For example, a button in Windows theme definitions has rounded corners and this way, it is the parent's job to paint the few pixels exposed by them.

By the way, although I haven't verified this, I believe IsThemeBackgroundPartiallyTransparent() actually makes some magic based on sending the WM_PRINTCLIENT message to the parent. We already discussed this message in the previous article.

Another useful function is DrawThemeText() (and also DraweThemeTextEx(), since Vista) which is intended for painting control labels. The function uses font and other text attributes as set by the theme definition. In practice however, both Luna and Aero, do not define any font or text properties for majority of classes/parts/states, which just leads the function to paint with HFONT currently selected into the used device context and only a few standard controls actually paint themselves with this function. Most of them stick with the old GDI TextOut or DrawText() to paint with a font determined by the latest WM_SETFONT, but that is another topic.

Note: When the theme does not define any font properties for the class/part/state used, DrawThemeText() simply paints with the font currently selected in the device context.

The API provides a few more functions for painting with themes like DrawThemeEdge() or DrawThemeIcon(). AFAIK, they are rarely used and we will not pay any special attention to them here.

The second point I want to highlight is that if the theme data is not availble (OpenThemeData() returned NULL), we have to paint in the old boring GDI way, here with DrawFrameControl().

Theme Subclasses

Let's now talk a bit about the theme subclasses. First of all, note the term has nothing common with the technique called control subclassing, which is used for augmenting an existing control by implanting another window procedure via SetWindowLongPtr(hwnd, GWLP_WNDPROC, pfn_WinProc).

The theme subclass is an alternative set of properties and resources in the theme definition, differentiated from an ordinary theme class (and from all the other subclasses of the same class) by an additional supplementary string identifier, the subclass name.

That allows applications to change the look of some control by enforcing some particular subclass name to it. Some programs which are integral parts of Windows (including explorer.exe which is responsible for managing the desktop and the open folder windows) take advantage of this feature. It is no accident that the Aero theme definition provides a subclass named EXPLORER for many standard controls.

The screenshot below demonstrates how the tree view control differs in Aero (Windows 7), depending on whether it uses the standard class TREEVIEW, or when it is told to use the EXPLORER subclass of it:

Image 3

It can be seen that the two tree views look a bit different and even have a bit different item dimensions. From a programmatic point of view, the difference is achieved with this single line of code which forces the right control to use the subclass:

C++
SetWindowTheme(hwndTreeOnRight, L"EXPLORER", NULL);

That one line has the following effect: Window manager remembers the subclass name in association with the control, until the window dies or the function is used once again to reset the subclass name. Whenever the control implementation calls OpenThemeData() (with its own window handle as the first parameter), UXTHEME.DLL returns a handle to the subclass data, not the parent class itself. Also the function SetWindowTheme() immediately sends WM_THEMECHANGED to the given control. So, in response to it, the control reopens the theme handle and repaints itself, getting the alternative look defined by the subclass instantly.

Right now, you will probably ask about the last parameter of SetWindowTheme(), especially if you have read the documentation of the function on MSDN, as the description there is very cryptic. To be honest, I am not too sure about it. I think (but never verified) that it allows to associate a class (not subclass) name with the control and hence to override the class name the control itself uses whenever it calls OpenThemeData(). (This speculation is based on an information that using L" " as the last parameter forces the particular window handle to paint itself without any theme. That, I guess, is caused by the fact that the theme definition does not know a class named with a space.)

Theme Properties

I already disclosed that properties are some data associated with classes, subclasses, parts, and states in the theme definition, or with the theme definition itself.

The API defines a huge number of properties, and usually each object in the theme definition defines just a small portion of them. Various properties are of different types and it is this variability of properties which reflects in the API itself: Majority of its functions are intended for retrieving values of the properties.

There are various enumeration properties (GetThemeEnumValue()), integer properties (GetThemeIntValue()), string properties (GetThemeStringValue()), boolean properties (GetThemeBoolValue()), color properties (GetThemeColorValue), and many more. It does not make any sense to list all their types here.

The good news, though, is that in most cases, the app or control does not need to retrieve them. Instead the painting functions of the theming API reflect them automatically. For example, the property TMT_BGTYPE specifies whether painting a background of a class/part/state it is associated to, should be painted as bliting an image, painting a frame, or if no background should be painted at all. Depending on that, other properties may specify what image or what color to use and how.

The function DrawThemeBackground() retrieves all the relevant properties and follows them. This is why we won't talk much about properties: There are hundreds of them and I have ever needed just a few of them in very special situations. (We may touch such properties in future articles when we talk about some topic when using some specific property is needed.)

All that said there is one exception: There are also some global properties, i.e. properties not related to any object in the theme definition, but to the theme definition itself, as a whole. We will cover these in next section.

Global Properties

As mentioned above, there are also some global properties, which apply to the whole system as long as the particular theme definition is in use. Simply put most of these properties are just a replacement of system parameters as retrieved with some old functions provided by USER32.DLL.

All of UXTHEME.DLL functions whose names begin with GetThemeSys...() are of this nature. One nice feature of this set of functions is the fact that when no theme definition applies (theme handle is NULL), the functions fall back to retrieve the right values with the corresponding USER32.DLL function for you.

So let's provide a small table of how these translate to each other:

UXTHEME.DLL USER32.DLL
Function Fundamental constant Function Fundamental constant
GetThemeSysBool() TMT_FLATMENUS SystemParametersInfo() SPI_GETFLATMENU
GetThemeSysColor()   GetSysColor()  
GetThemeSysColorBrush()   GetSysColorBrush()  
GetThemeSysFont() TMT_ICONTITLEFONT SystemParametersInfo() SPI_GETICONTITLELOGFONT
the other ones SPI_GETNONCLIENTMETRICS
GetThemeSysInt()   no counterpart  
GetThemeSysSize()   GetSystemMetrics()  
GetThemeSysString()   no counterpart  

Note: Wherever the fundamental constant is not filled in the table, both functions share the set of allowed parameters (with the same meaning).

Only in the case you want to also support Windows 2000 or older, and hence need to deal with the possibility of missing UXTHEME.DLL, you may need to implement the fallback manually.

Troubleshooting

You could now quite easily have the impression that once you solve the problem whether to use the theming API, and know how to manage the theme handles, the painting with the visual themes is then quite straightforward. But in controls of more complex nature, you can encounter many problems. Below I summarize the problems I have been facing when implementing my controls, especially controls in the mCtrl project.

Unfortunately, for many of them, there is no simple solution (as far as I know). Nevertheless, I believe it is valuable to list the problems here anyway. It is beneficial to know about as many traps as possible before you decide to take any particular path.

It is simply a matter of fact that using this API requires often a trial-and-error approach to find a feasible way to implement your control.

Gotcha: Heavily Under-Documented

The biggest problem of the API is the lack of good documentation. MSDN documents fairly well all the functions and types of the API. But documentation of the theme definition objects, i.e., classes and subclasses, as well as their parts and states, consists mainly of pure listings of their identifiers.

There is no documentation of what each class, subclass, part, or state is meant for. Often it is quite obvious from the name of the class/sublclass or part/state identifier, but in many cases it is not.

Even when it is obvious, it is not documented which Windows introduced them, or under which circumstances they are usable. For example some parts are only used by a standard control when you switch it to a particular subclass. It is a case of the class TREEVIEW which defines (among others) the part TVP_HOTGLYPH. Both Luna as well as Aero do not define this part for the class TREEVIEW at all so painting with it does nothing. But Aero defines it for the subclass EXPLORER of the class TREEVIEW.

So when implementing a control resembling tree-view, you should use the part only conditionally by checking it is available with IsThemePartDefined(), and fall back to the part TVP_GLYPH if it is not.

Unfortunately, MSDN does not provide any information on which parts or states of each class are considered to be guaranteed to be defined and which are optional. Obviously, some must be guaranteed to be defined, or the painting with themes does not make much sense: Otherwise using the API would grow to the task of unrealistic complexity both for implementation as well as testing it.

A partial solution to this problem is using some common sense and using some utility for exploring the theme definitions: Often you may recognize what they are for from their visual appearance. There are several such utilities available on the net. I offer one, called Theme Explorer, by the end of this article.

Gotcha: Not Really Designed for Custom Controls

When implementing a custom control, the developer should design it to be consistent with standard controls. Arguably, one might assume that it is a purpose of such API to paint other controls consistently with the standard ones, despite the theme currently in use; and that the task should be fairly straightforward.

Actually the reality differs. Both the API and the way how the Windows themes are defined, often lead to frustration when trying to achieve the desired goal.

As an example, I remember my attempt to implement an edit control with one or more buttons similar to what a standard combo-box is equipped with. The only difference should be a different glyph on the button(s) but unlike that, its appearance should be as close to the standard control as possible.

Similar example would be to only reuse the combo box glyph (the drop down arrow), when one wants to present a small button for opening a drop-down menu somewhere in the application or in another custom control.

Unfortunately, the class COMBOBOX has a part CP_DROPDOWNBUTTON which paints not just the background of the button, but also the glyph. You can have both, or nothing.

Yes, the API has a dedicated function to get a bitmap from the theme (GetThemeBitmap()). So one might think retrieving the glyph bitmap can be done with this function. Otherwise what would the function be good for? But, seemingly, the theme definition authors have a different opinion. There is no glyph for the combo box, there are only some pixels in the background part which, to the untrained eyes of a poor user, may make a false impression that some symbol is present...

My personal opinion is that the theming API and especially the themes, defined the way they are, were designed by someone who was not having third party custom controls in mind at all. That's quite a pitiful situation if you ask me, as it significantly lowers the potential of the API.

Gotcha: Not Used Consistently in Standard Controls

However, there are also problems with standard controls.

When Microsoft created COMCTL32.DLL, they simply omitted support for some controls or their styles. A well known example is that buttons with the style BS_BITMAP or BS_ICON were painted unthemed on Windows XP although the implementation of theme paint for it is trivial once you support BS_PUSHBUTTON. (This was fixed in Windows Vista.)

In a similar way, since version 6 of the library, Microsoft made tab styles and TCS_BOTTOM and TCS_VERTICAL unsupported, and indeed, there are no data in the theme definitions corresponding to those control styles. Any custom control wanting to achieve something similar cannot rely on it at all.

This fairly limits how custom controls may differ (i.e., be customized) from standard ones.

On the contrary, there are also data in Windows theme definitions clearly intended for some purpose like determining a size, margin, or other property, but which way too often are not used by implementation of the standard controls. Instead the controls use undocumented magical constants, hard-coded in COMCTL32.DLL.

For example, when I was implementing a tree-list view control (mCtrl project), I had encountered this issue. The control was designed to be visually as close to the standard tree-view as possible. I was expecting that, when themed, the standard control uses GetThemeBackgroundContentRect() to apply some margins of tree-view items when painting them. To my surprise, any attempt to use this function returned 0 for all the margins, while it is apparent the standard tree view has some margins around them (they are easy to spot if you select an item in a tree-view, there are a few pixels as margins at all the edges).

Said in other words: In order to create a custom control consistently with a standard one, the developer has to magically mix standard GDI with theming API and magical constants. The particular mixture is considered an implementation detail of each standard control by Microsoft, it is not documented anywhere and hence it has to be guessed or reverse-engineered. Furthermore the poor custom-control developer is then in some danger, the mixture recipe shall change in future versions of Windows. Indeed, it is not a situation which can make any developer happy.

The only partial solution I can offer is that developers of Wine already made a huge effort to make their controls to be as close to the originals as possible, so the easiest way is looking at their code. It is not the first time I recommend their code base, and I am pretty sure it is not the last one either. (Some links are provided by the end of this article.)

Gotcha: OpenThemeData() and Windows XP

According to MSDN, in OpenThemeData() the second parameter is more capable than just being a single class name. In particular it says the following:

  • It can directly open the theme subclass with OpenThemeData(hwnd, L"subclass::class").
  • It can accept a list of semicolon-delimited class names, using the first one found in the theme definition.

Both features are nice. What is not nice is the (undocumented) fact, that according to my experience they do not work on Windows XP. So for compatibility with WinXP, you need open subclasses only via SetWindowTheme() as explained earlier in this article.

The other feature can be replaced with using multiple calls of the function:

C
const WCHAR* pszClassList[] = { L"class1", L"class2", ..., NULL };
HTHEME hTheme = NULL;
int i;

for(i = 0; pszClassList[i] != NULL; i++) {
    hTheme = OpenThemeData(hwnd, pszClassList[i]);
    if(hTheme != NULL)
        break;
}

Gotcha: GetWindowTheme() Limited to One Theme Handle

For every HWND, Window manager remembers one theme handle. It is the most recently opened theme handle by the control, i.e. the return value of most recent OpenThemeData() where the given HWND was passed in as the 1st parameter). This remembered handle can be asked for with GetWindowTheme(). Similarly, only this one handle can be actually overriden with SetWindowTheme().

When implementing a more complex control, typically a control looking as a combination of multiple standard controls, you might want to open multiple different theme handles for the purposes of its painting. It is of course, possible but you have to think carefully which of them should be considered the main handle. For the other theme handles, NULL should be passed as the 1st parameter of OpenThemeData() so that the main handle is not replaced with it.

Gotcha: GetThemeIntList() Designed to Crash

Sadly, the function GetThemeIntList() and the corresponding structure INTLIST was mis-designed. The structure contains the member iValues, actually an integer array of a fixed size. Unfortunately the size depends on the preprocessor value _WIN32_WINNT and there is no run-time check in a similar way as many WinAPI functions do with a member typically named cbSize.

If _WIN32_WINNT is not defined, or is lower than 0x0600 (_WIN32_WINNT_VISTA), the array size is 10, otherwise it is 402. The UXTHEME.DLL implementation on Vista and later blindly assumes, it can write up to 402 integers into the array. I.e., if your application was built with _WIN32_WINNT set as for example _WIN32_WINNT_WINXP, you can get a buffer overrun on Vista and newer, leading likely either to application crash or other data overwrite.

The solution is easy: If you need to use this function, build your project with the predefined _WIN32_WINNT accordingly to 0x600 or higher; or use a buffer large enough to hold the complete structure with 402 integers.

Hopefully, Microsoft will not augment the limit further in future Windows versions.

Theme Explorer

I already noted that especially the identifiers for accessing the theme definitions are poorly documented. For this reason, I have developed a small tool for exploring the current theme, called Theme Explorer. It allows to see how the themes (in various states) look like, and it helps to recognize or guess more easily what their purpose is and how they should be used.

Originally I created it for my own purposes, when developing my custom controls, but I feel it may be useful to broader audience.

 

Theme Explorer screenshot

Screenshot of Theme Explorer

The left part is a selector of an object to explore (class or subclass on the first level, parts and states deeper in the tree), the right side then paints the part/state with DrawThemeBackground() in several sizes and lists the properties associated with the object.

It can be downloaded from the mCtrl project website and its sources are hosted on github:

(The tool is licensed under GNU GPL version 2 or, at your option, any later version.)

The tool has quite a severe limitation: It can only explore the theme currently in use, so to be useful you have to run it on Windows XP or later (with the theming support enabled in the system). To explore the themes in different Windows versions, you need to use this tool in all of them. Still, I hope you can find it useful.

Downloadable Example

You can download an example package at the top of this page. It contains a Visual Studio 2010 solution with three little projects: DLL with theme-aware custom control implementation using what this article presents, and two small applications which just host the custom control. Actually the two applications share their sources and the only difference between them is that one has an application manifest enabling the visual theme support while the other one does not.

The DLL uses the function ShouldUseUxThemeDll() to detect whether it should use the themed painting or not. When the function returns FALSE, the DLL then uses its own dummy_OpenThemeData() (instead of OpenThemeData()) which simply returns NULL.

Only when ShouldUseUxThemeDll() returns TRUE, the example loads UXTHEME.DLL and the rest depends on the return value of OpenThemeData().

Real World Code

In case you want to study real world code, I can refer to you the following:

Wine reimplementation of COMCTL32.DLL is (mostly) aware of themes. In particular, the controls implemented historically in USER32.DLL are subclassed in it so in their case the source focuses only on making the controls themed which makes them perfect candidates for further studying the topic:

Also its reimplementation of UXTHEME.DLL may be of interest for readers of this article:

mCtrl implements a wrapper of UXTHEME.DLL, to deal with the library (or some of its exported symbols) missing in dependence on particular Windows version:

In addition to that, majority of its controls are theme-aware, using the wrapper, so lets list just few more interesting ones:

Next Time: Handling Standard Messages

That's it for today. The article is much longer than I originally anticipated. On the other side, its length reflects the importance I attach to this topic for creation of good custom controls, and also it reflects the lack of documentation about it. I hope this text has enhanced the situation a bit and that you found at least some information presented here useful and in understandable form. Some time later we will return to control painting, for example, to discuss some animating techniques or painting into a non-client area. Even in those areas, UXTHEME.DLL has something to offer.

That said, however, we should first step aside a bit and explore other aspects of custom controls implementation. Every control should support a number of standard messages which the application and system uses to communicate with it. MSDN often documents messages from the point of an application, but we should look on them mainly from a control's point of view and that will be the subject of the next article.

License

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


Written By
Team Leader
Czech Republic Czech Republic
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionBy the way about "Theme Explorer" Pin
mbue2-Jan-23 12:36
mbue2-Jan-23 12:36 
Questioncompile notes for Windows 7 with Code::Blocks and gcc compiler with C11 Pin
KenLevin27-Dec-20 10:50
KenLevin27-Dec-20 10:50 
Thank you for posting this series of c++ projects.

I had to make the following changes to get the program to compile using C11, on Windows 7 with gcc compiler.

1) remove:

#define <windows.h>


from custom.c (renamed to custom.cpp on my system). I removed because the include is already in custom.h.

2) add:

#define WINVER 0x0601


to custom.h file before #define <windows.h>.

3) vsstyle.h was not in my compiler's usual search path. I added definitions for bp_pushbutton and pbs_normal
to custom.h and omitted the vsstyle.h include. I actually added the entire enum BUTTONPARTS and enum PUSHBUTTONSTATES
from vsstyle.h.

4) I changed one line of code in custom.cpp (a file renamed by me) to convert the result to a HBITMAP:

hOldBmp = (HBITMAP) SelectObject(hMemDC, hBmp);


5) I handled the WM_LBUTTONDOWN message in Custom.cpp in the CustomProc function to show a messagebox when the custom button is clicked.

I hope this helps someone else.

Thanks again.
QuestionThanks! - and: how to determine layout Pin
peterchen25-Dec-20 2:10
peterchen25-Dec-20 2:10 
QuestionTreeView question for visual style Pin
Member 1453541218-Jul-19 23:04
Member 1453541218-Jul-19 23:04 
QuestionGreat article - comment and question Pin
Christopher P Taylor19-Jun-19 7:54
Christopher P Taylor19-Jun-19 7:54 
AnswerRe: Great article - comment and question Pin
Martin Mitáš20-Jun-19 8:56
Martin Mitáš20-Jun-19 8:56 
GeneralRe: Great article - comment and question Pin
Martin Mitáš20-Jun-19 9:07
Martin Mitáš20-Jun-19 9:07 
GeneralRe: Great article - comment and question Pin
Christopher P Taylor8-Sep-19 9:09
Christopher P Taylor8-Sep-19 9:09 
GeneralRe: Great article - comment and question Pin
Martin Mitáš9-Sep-19 0:37
Martin Mitáš9-Sep-19 0:37 
GeneralRe: Great article - comment and question Pin
Christopher P Taylor9-Sep-19 13:24
Christopher P Taylor9-Sep-19 13:24 
GeneralRe: Great article - comment and question Pin
Christopher P Taylor11-Sep-19 20:17
Christopher P Taylor11-Sep-19 20:17 
GeneralRe: Great article - comment and question Pin
Christopher P Taylor11-Sep-19 20:20
Christopher P Taylor11-Sep-19 20:20 
GeneralMy vote of 5 Pin
XTAL2564-Apr-17 16:09
XTAL2564-Apr-17 16:09 
QuestionGreat resource Pin
Larry Weinheimer5-Feb-16 12:15
Larry Weinheimer5-Feb-16 12:15 
GeneralMy vote of 5 Pin
Member 1116586712-Nov-15 19:07
Member 1116586712-Nov-15 19:07 
QuestionThank you Pin
B2kguga14-Jun-14 11:17
B2kguga14-Jun-14 11:17 
QuestionMy vote of 5 Pin
kanalbrummer27-Mar-14 23:13
kanalbrummer27-Mar-14 23:13 
QuestionGreat Work Pin
Tom Clement22-Jul-13 12:17
professionalTom Clement22-Jul-13 12:17 
GeneralExcellent, well written article! Pin
Espen Harlinn21-Jul-13 10:02
professionalEspen Harlinn21-Jul-13 10:02 
GeneralRe: Excellent, well written article! Pin
Martin Mitáš22-Jul-13 7:24
Martin Mitáš22-Jul-13 7:24 
QuestionGreat article Pin
Richard MacCutchan20-Jul-13 23:35
mveRichard MacCutchan20-Jul-13 23:35 
AnswerRe: Great article Pin
Martin Mitáš21-Jul-13 0:03
Martin Mitáš21-Jul-13 0:03 
GeneralRe: Great article Pin
Richard MacCutchan21-Jul-13 0:07
mveRichard MacCutchan21-Jul-13 0:07 

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.