Using Visual Styles with Owner-Drawn Controls

 

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.

 

C++

 

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.

  }

}

 

PowerBASIC

 

LOCAL hTheme AS DWORD

LOCAL bstrClassList AS STRING

 

bstrClassList = UCODE$("Button")

hTheme = OpenThemeData(hwndButton, STRPTR(bstrClassList))

...

DrawMyControl(hDC, hwndButton, hTheme, iState)

...

IF hTheme THEN

 CloseTheme(hTheme)

END IF

 

SUB DrawMyControl(BYVAL hDC AS DWORD, BYVAL hwndButton AS DWORD, BYVAL hTheme AS DWORD, BYVAL iState AS LONG)

 

 LOCAL rc, rcContent AS RECT

 LOCAL strButtonText AS STRING

 LOCAL hr AS LONG

 

 GetWindowRect(hwndButton, rc)

 strButtonText = SPACE$(255)

 GetWindowText(hwndButton, BYVAL STRPTR(strButtonText), _

            (LEN(strButtonText))

 strButtonText = UCODE$(strButtonText)

 IF hTheme THEN

    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, _

            STRPTR(strButtonText), LEN(strButtonText), _

            %DT_CENTER OR %DT_VCENTER OR %DT_SINGLELINE, _

            0, rcContent)

 ELSE

    ' // Draw the control without using visual styles.

 END IF

END SUB

 

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.

 

C++

 

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

}

 

PowerBASIC

 

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

 

LOCAL theme AS DWORD

LOCAL bstrClassList AS STRING

bstrClassList = UCODE$("button")

 

theme = OpenThemeData(hWnd, STRPTR(bstrClassList))

IF theme THEN

 LOCAL opts AS DTTOPTS

 opts.dwSize = SIZEOF(opts)

 opts.crText = textColor

 opts.dwFlags = %DTT_TEXTCOLOR

 LOCAL caption AS STRING

 caption = SPACE$(255)

 GetWindowText(hWnd, caption, 255)

 caption = UCODE$(caption)

 DrawThemeTextEx(theme, paint.hdc, %BP_PUSHBUTTON, %CBS_UNCHECKEDNORMAL, _

     STRPTR(caption), LEN(caption), %DT_CENTER OR DT_VCENTER OR %DT_SINGLELINE, _

     paint.rcPaint, opts)

 CloseThemeData(theme)

ELSE

 ' // Draw the control without using visual styles.

END IF

 

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:

 

C++

OpenThemeData(hwnd, L"Toolbar");

 

PowerBASIC

LOCAL bstrClassList AS STRING

bstrClassList = UCODE$("Toolbar")

OpenThemeData(hwnd, STRPTR(bstrClassList))

 

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.

 

C++

//This code sample illustrates the two calls.

case WM_THEMECHANGED:

   CloseThemeData (g_hTheme);

   g_hTheme = OpenThemeData (hwnd, L"MyClassName");

 

PowerBASIC

' // This code sample illustrates the two calls.

CASE %WM_THEMECHANGED

CloseThemeData(g_hTheme)

LOCAL bstrClassList AS STRING

bstrClassList = UCODE$("MyClassName")

g_hTheme = OpenThemeData(hwnd, STRPTR(bstrClassList))

 

Valid XHTML 1.0 Transitional