对自定义控件和所有者绘制的控件使用视觉样式

本主题介绍如何使用视觉样式 API 将视觉样式应用于自定义控件或所有者绘制的控件。

使用视觉样式绘制控件

ComCtrl32.dll 6 及更高版本支持视觉样式。 如果应用程序被配置为使用 ComCtrl32.dll 版本 6 及更高版本,且系统中存在该版本,则当前的视觉样式会被自动应用于应用程序中的所有常用控件。 但是,当前的视觉样式不会自动应用于自定义控件或所有者绘制的控件。 应用程序必须包含检查视觉样式是否可用的代码,如果可用,则使用视觉样式 API 将当前选定的视觉样式应用于自定义控件和所有者绘制的控件。

要检查视觉样式是否可用,请调用 IsAppThemed 函数。 如果没有视觉样式,请使用回退代码来绘制控件。

如果视觉样式可用,则可以使用视觉样式函数(如 DrawThemeText)来呈现控件。 请注意,可以通过 DrawThemeTextEx 来自定义文本的外观,保留主题字体的某些属性,同时修改其他属性。

以当前视觉样式绘制控件

  1. 调用 OpenThemeData,传递要应用视觉样式的控件的 hwnd 和描述控件类型的类列表。 这些类在 Vssym32.h 中定义。 OpenThemeData 返回 HTHEME 句柄,但如果禁用了视觉样式管理器或当前视觉样式没有为给定控件提供特定信息,则函数会返回 NULL。 如果返回值为 NULL,请使用非视觉样式绘制函数。
  2. 要绘制控件背景,请调用 DrawThemeBackgroundDrawThemeBackgroundEx
  3. 要确定内容矩形的位置,请调用 GetThemeBackgroundContentRect
  4. 要呈现文本,请使用 DrawThemeTextDrawThemeTextEx,并根据 GetThemeBackgroundContentRect 返回的矩形来确定坐标。 这些函数可以为指定的控制部分和状态使用主题字体,或使用当前在设备上下文 (DC) 中选择的字体来呈现文本。
  5. 当控件收到 WM_DESTROY 消息时,请调用 CloseThemeData 以释放调用 OpenThemeData 时返回的主题句柄。

以下示例代码演示了一种以当前视觉样式绘制按钮控件的方法。

HTHEME hTheme = NULL;

hTheme = OpenThemeData(hwndButton, L"Button");
// ...
DrawMyControl(hDC, hwndButton, hTheme, iState);
// ...
if (hTheme)
{
    CloseThemeData(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_PUSHBUTTON, iState, &rc, 0);
        if (SUCCEEDED(hr))
        {
            hr = GetThemeBackgroundContentRect(hTheme, hDC, BP_PUSHBUTTON, 
                    iState, &rc, &rcContent);
        }

        if (SUCCEEDED(hr))
        {
            hr = DrawThemeText(hTheme, hDC, BP_PUSHBUTTON, iState, 
                    szButtonText, cch,
                    DT_CENTER | DT_VCENTER | DT_SINGLELINE,
                    0, &rcContent);
        }

    }
    else
    {
        // Draw the control without using visual styles.
    }
}

以下示例代码位于子类化按钮控件的 WM_PAINT 消息处理程序中。 控件的文本以视觉样式字体绘制,但颜色则根据控件的状态由应用程序定义。

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

可以使用其他控件的部件,并单独呈现每个部件。 例如,对于由网格组成的日历控件,可以将网格形成的每个正方形视为工具栏按钮,获取主题句柄的方法如下:

OpenThemeData(hwnd, L"Toolbar");

可以针对给定控件多次调用 OpenThemeData,并使用相应的主题句柄来绘制不同部分,从而混合和匹配控件部分。 但是,在某些视觉样式中,一些部件可能与其他部件不兼容。

另一种以活动视觉样式呈现控件的方法是使用系统颜色。 例如,在调用 DrawThemeTextEx 函数时,可以使用系统颜色来设置文本颜色。 大多数系统颜色都是在应用视觉样式文件时设置的。

响应主题更改

当控件收到 WM_THEMECHANGED 消息并拥有主题的全局句柄时,它应执行以下操作:

下面的示例说明了这两种调用。

case WM_THEMECHANGED:
     CloseThemeData (g_hTheme);
     g_hTheme = OpenThemeData (hwnd, L"MyClassName");

启用视觉样式

视觉样式