Using Visual Styles with Owner-Drawn Controls

This topic describes the use of the visual styles API to create custom controls or owner-drawn controls that take advantage of the visual styles available in Microsoft Windows XP and later versions of the operating system.

  • Drawing Controls with Visual Styles
  • Responding to Theme Changes

Drawing Controls with Visual Styles

You can choose whether or not to display owner-drawn controls using the available visual styles, and if you choose to apply visual styles, you can do so selectively.

First your application should determine whether visual styles are available, by calling IsAppThemed. If visual styles are not available, use fallback code to draw the control.

If visual styles are available, you can use visual-styles functions such as DrawThemeText to render your control. Note that DrawThemeTextEx enables you to customize the appearance of text, retaining some properties of the theme font while modifying others.

To draw a control in the current visual style

  1. Call OpenThemeData, passing the hwnd of the control you want to apply visual styles to and a class list that describes the control's type. The classes are defined in Vssym32.h. OpenThemeData returns an HTHEME handle, but if the visual styles manager is disabled or the current visual style does not supply specific information for a given control, the function returns NULL. If the return value is NULL, use non-visual-styles drawing functions.
  2. To draw the control background, call DrawThemeBackground or DrawThemeBackgroundEx.
  3. To determine the location of the content rectangle, call GetThemeBackgroundContentRect.
  4. To render text, use either DrawThemeText or DrawThemeTextEx, basing the coordinates on the rectangle returned by GetThemeBackgroundContentRect. These functions can render text either in the theme's font for a specified control part and state, or in the font currently selected into the device context (DC).
  5. When your control receives a WM_DESTROY message, call CloseThemeData to release the theme handle that was returned when you called OpenThemeData.

The following example code demonstrates one way to draw a button control in the current visual style.

HTHEME hTheme = NULL;

hTheme = OpenThemeData(hwndButton, L"Button");
...
DrawMyControl(hDC, hwndButton, hTheme, iState);
...
if (hTheme)
{
    CloseTheme(hTheme);
}

void DrawMyControl(HDC hDC, HWND hwndButton, HTHEME hTheme, int iState)
{
    RECT rc, rcContent;
    TCHAR szButtonText[255];
    HRESULT hr;
    size_t cch;

    GetWindowRect(hwndButton, &rc);
    GetWindowText(hwndButton, szButtonText,
                  (sizeof(szButtonText)/sizeof(szButtonText[0])+1));
    hr = StringCchLength(szButtonText,
         (sizeof(szButtonText)/sizeof(szButtonText[0])), &cch);
    if (hTheme)
    {
        hr = DrawThemeBackground(hTheme, hDC, BP_BUTTON,
                iState, &rc, 0);
        // Always check your result codes.

        hr = GetThemeBackgroundContentRect(hTheme, hDC,
                BP_BUTTON, iState, &rc, &rcContent);
        hr = DrawThemeText(hTheme, hDC, BP_BUTTON, iState,
                szButtonText, cch,
                DT_CENTER | DT_VCENTER | DT_SINGLELINE,
                0, &rcContent);
    }
    else
    {
        // Draw the control without using visual styles.
    }
}

The following example code is in the WM_PAINT message handler for a subclassed button control. The text for the control is drawn in the visual styles font, but the color is application-defined depending on the state of the control.

// textColor is a COLORREF whose value has been set according to whether the button is "hot".
// paint is the PAINTSTRUCT whose members are filled in by BeginPaint.
HTHEME theme = OpenThemeData(hWnd, L"button");
if (theme)
{
    DTTOPTS opts = { 0 };
    opts.dwSize = sizeof(opts);
    opts.crText = textColor;
    opts.dwFlags |= DTT_TEXTCOLOR;
    WCHAR caption[255];
    size_t cch;
    GetWindowText(hWnd, caption, 255);
    StringCchLength(caption, 255, &cch);
    DrawThemeTextEx(theme, paint.hdc, BP_PUSHBUTTON, CBS_UNCHECKEDNORMAL, 
        caption, cch, DT_CENTER | DT_VCENTER | DT_SINGLELINE, 
        &paint.rcPaint, &opts);
    CloseThemeData(theme);
}
else
{
    // Draw the control without using visual styles.
}

You can use parts from other controls and render each part separately. For example, for a calendar control that consists of a grid, you can treat each square formed by the grid as a toolbar button, by obtaining the theme handle as follows:

OpenThemeData(hwnd, L"Toolbar");

You can mix and match control parts by calling OpenThemeData multiple times for a given control and using the appropriate theme handle to draw different parts. In some visual styles, however, certain parts might not be compatible with other parts.

Another approach to rendering controls in the active visual style is to use the system colors. Most system colors are set when a visual style file is applied.

Responding to Theme Changes

When your control receives a WM_THEMECHANGED message and is holding a global handle to the theme, it should do the following:

  • Call CloseThemeData to close the existing theme handle.
  • Call OpenThemeData to get the theme handle to the newly loaded visual style.
//This code sample illustrates the two calls.
case WM_THEMECHANGED:
     CloseThemeData (g_hTheme);
     g_hTheme = OpenThemeData (hwnd, L"MyClassName");