Click here to Skip to main content
Click here to Skip to main content

Custom Controls in Win32 API: Visual Styles

, 17 Mar 2014
Rate this:
Please Sign up or sign in to vote.
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 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 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:

#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 minimum, you should test on Windows XP, Windows7 (or Vista), and on Windows 8 (unless, of course, you decide to not support some Windows version), 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, providing the functions for both Unicode and ANSI, and using the macro UNICODE to resolve it. UXTHEME.DLL uses strictly Unicode strings. 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 relatively a simple DLL. It actually does not know anything about how to paint a particular control or its part. Instead, most of its functions just know how to access the currently active theme data, 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 them 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. 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.).

Some 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 many 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 know it.)

There are also 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. A part 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.

In the third level of the hierarchy, there is a state. Like part, it 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, 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 may have many states.

Many API functions working with themes take the part and state together with the theme handle as their input. In cases where 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 level of the hierarchy (including the theme definition as a whole) can be associated with a set of properties. The properties vary a lot and can also have different types and they support 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 listing styles for each 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:

#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(pCustom, 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()

With the knowledge about theme handle management, let's return a 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, remember 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.

When the setting has changed, all the application's top-level windows receive WM_THEMECHANGED and they (unless the top-level windows suppress it) forward the message to all controls (DefWindowProc() does that). As explained earlier, the controls then (re)open the theme handle(s) and, when the theming is disabled in that particular time, they get NULL handles.

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 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.

As both functions depend on the user settings, the return value of both functions can vary during the application's run time. Due to this property of the functions, the application or control would have to call them again always when it gets WM_THEMECHANGED, but then the work can be fully handled by OpenThemeData(). Because of that, I consider both of these functions mostly useless.

Instead of them, I recommend to use my function ShouldUseUxThemeDll(). Actually the function ShouldUseUxThemeDll() is similar to IsAppThemed() but it ignores the user settings. Hence, its return value is the same during the app's life time. So it may be called just once during application or DLL initialization (though not in the context of DllMain()). When this function returns FALSE, avoid using the theming API altogether for the whole life time of your application. When it returns TRUE, use the API and rely on OpenThemeData() and WM_THEMECHANGED to reflect the run-time settings.

An additional added value of ShouldUseUxThemeDll() is that it works also on older Windows versions, prior to Windows XP. If you want to be compatible with, for example, Windows 2000 and load UXTHEME.DLL in run time with LoadLibrary(), you simply need to load it only when the function ShouldUseUxThemeDll() returns TRUE.

Themed Painting

Alright, back to 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.

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. The essential part of theming is its support for (partially) transparent controls so the painting code has to reflect this and (when needed) ask the parent window to paint to fill 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.

Theme Subclasses

Let's now talk a bit about the theme subclasses. First of all, note that 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 indeed something different. It is an alternative set of properties and resources in the theme definition, associated with the class, and differentiated from the normal class (and from other subclasses of the same class) by some string identifier: the subclass name.

That allows applications to change the look of some control by enforcing some subclass name to it. Many 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:

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 done with this single line of code which forces the right control to use the subclass:

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 so in response, the control reopens the theme handle and repaints itself, applying 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 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 theme definition defines relatively a small number 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 much more. It likely 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, as 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 the relevant properties and follows them.

This is why we won't talk much about properties: There are hundreds of them and I have used only a small part of them. There are some useful global properties we will cover more, but otherwise we will only touch a few other properties in future articles when we talk about some topic when using a specific property is needed.

Useful Global Properties

As mentioned above, there are also some global properties, meaning they apply to the whole system as long as the particular theme definition is in use. Actually many of these properties are 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.DLLUSER32.DLL
FunctionFundamental constantFunctionFundamental constant
GetThemeSysBool()TMT_FLATMENUS SystemParametersInfo()SPI_GETFLATMENU
GetThemeSysColor() GetSysColor()
GetThemeSysColorBrush() GetSysColorBrush()
GetThemeSysFont()TMT_ICONTITLEFONT SystemParametersInfo()SPI_GETICONTITLELOGFONT
the other onesSPI_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 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

So far, you could quite easily get an impression that once you solve the problem whether to use the theming API, the painting itself with visual themes is quite straightforward. But in controls of a bit more complex nature, you can face 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). Still, I believe it is valuable to list the problems here. At least, you should be aware of a trap before you decide to step into it. 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 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. However 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 (e.g., by checking it with IsThemePartDefined()) and fall back to the part TVP_GLYPH.

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 it 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 to explore the theme definitions so 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 an 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 design should be as close to the standard control as possible.

Another 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.

However, combo-box has a part CP_DROPDOWNBUTTON which paints not just the background of the button, but also the glyph. You can only 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 pure user, may make a false impression that some symbol is present...

My personal impression is that the theming API and 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.

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 counterparts in theme definitions for custom controls. Any custom control wanting to achieve that cannot rely on it.

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 standard controls. Those sometimes rather 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:

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

When implementing a really complex control, for example a control looking as a combination of multiple standard controls, you might want to open multiple different theme handles. It is, of course, possible, but it leads to one side effect.

When the application calls a function GetWindowTheme() to retrieve the theme data the control uses, it can get only one of them (actually the last one opened).

Therefore the control should ensure that every time (i.e., typically in handling WM_(NC)CREATE and WM_THEMECHANGED), the control opens multiple themes in a consistent order, and that the most significant theme is opened as the last one to guarantee that is the handle the application gets.

Gotcha: GetThemeIntList() Designed to Crash

The function GetThemeIntList() and the corresponding structure INTLIST are mis-designed. The structure contains a 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 (e.g., _WIN32_WINNT_WINXP), you can get a buffer overrun on Vista and newer, leading either to application crash or 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 many aspects of the theming API are poorly documented. For this reason, I have developed a small tool for exploring the current theme, Theme Explorer. 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 some limitations: 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). And to explore the themes in different Windows versions, you need to use this tool on each 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 in them is that one has an application manifest 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)

Share

About the Author

Martin Mitáš
Team Leader
Czech Republic Czech Republic
I'm a senior developer with more then 15 years of experience. I currently work for Avast Software. Although I professionally focus on unix, linux and other unix-like systems, I am also (in my leisure time) involved in some Windows-centric open-source development, including occasional patches into mingw-w64 project and being the main developer of mCtrl project.

Comments and Discussions

 
QuestionThank you PinmemberB2kguga14-Jun-14 11:17 
QuestionMy vote of 5 Pinmemberkanalbrummer27-Mar-14 23:13 
QuestionGreat Work PinmentorTom Clement22-Jul-13 12:17 
GeneralExcellent, well written article! PinmvpEspen Harlinn21-Jul-13 10:02 
GeneralRe: Excellent, well written article! PinprofessionalMartin Mitáš22-Jul-13 7:24 
QuestionGreat article PinmvpRichard MacCutchan20-Jul-13 23:35 
AnswerRe: Great article PinprofessionalMartin Mitáš21-Jul-13 0:03 
GeneralRe: Great article PinmvpRichard MacCutchan21-Jul-13 0:07 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140821.2 | Last Updated 17 Mar 2014
Article Copyright 2013 by Martin Mitáš
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid