Uso de estilos visuales con controles personalizados y de Owner-Drawn

En este tema se describe cómo usar la API de estilos visuales para aplicar estilos visuales a controles personalizados o controles dibujados por el propietario.

Controles de dibujo con estilos visuales

Los estilos visuales son compatibles con ComCtrl32.dll versión 6 y posteriores. Si la aplicación está configurada para usar ComCtrl32.dll versión 6 y posteriores y, si esa versión está disponible en el sistema, los estilos visuales actuales se aplican automáticamente a todos los controles comunes de la aplicación. Sin embargo, los estilos visuales actuales no se aplican automáticamente a controles personalizados o controles dibujados por el propietario. La aplicación debe incluir código que compruebe si los estilos visuales están disponibles y, si es así, usa la API de estilos visuales para aplicar los estilos visuales seleccionados actualmente a los controles personalizados y dibujados por el propietario.

Para comprobar si los estilos visuales están disponibles, llame a la función IsAppThemed . Si los estilos visuales no están disponibles, use el código de reserva para dibujar el control.

Si hay estilos visuales disponibles, puede usar funciones de estilos visuales como DrawThemeText para representar el control. Tenga en cuenta que DrawThemeTextEx permite personalizar la apariencia del texto, conservando algunas propiedades de la fuente del tema mientras modifica otras.

Para dibujar un control en el estilo visual actual

  1. Llame a OpenThemeData y pase el hwnd del control al que desea aplicar estilos visuales y una lista de clases que describa el tipo del control. Las clases se definen en Vssym32.h. OpenThemeData devuelve un identificador HTHEME, pero si el administrador de estilos visuales está deshabilitado o el estilo visual actual no proporciona información específica para un control determinado, la función devuelve NULL. Si el valor devuelto es NULL, use funciones de dibujo que no sean de estilos visuales.
  2. Para dibujar el fondo del control, llame a DrawThemeBackground o DrawThemeBackgroundEx.
  3. Para determinar la ubicación del rectángulo de contenido, llame a GetThemeBackgroundContentRect.
  4. Para representar texto, use DrawThemeText o DrawThemeTextEx, basando las coordenadas en el rectángulo devuelto por GetThemeBackgroundContentRect. Estas funciones pueden representar texto en la fuente del tema para una parte de control y un estado especificados, o bien en la fuente seleccionada actualmente en el contexto del dispositivo (DC).
  5. Cuando el control recibe un mensaje de WM_DESTROY , llame a CloseThemeData para liberar el identificador de tema que se devolvió cuando llamó a OpenThemeData.

En el código de ejemplo siguiente se muestra una manera de dibujar un control de botón en el estilo visual actual.

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

El código de ejemplo siguiente está en el controlador de mensajes WM_PAINT para un control de botón con subclases. El texto del control se dibuja en la fuente de estilos visuales, pero el color se define en función del estado del 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.
}

Puede usar elementos de otros controles y representar cada pieza por separado. Por ejemplo, para un control de calendario que consta de una cuadrícula, puede tratar cada cuadrado formado por la cuadrícula como un botón de barra de herramientas, obteniendo el identificador de tema de la siguiente manera:

OpenThemeData(hwnd, L"Toolbar");

Puede mezclar y hacer coincidir elementos de control llamando a OpenThemeData varias veces para un control determinado y usando el identificador de tema adecuado para dibujar diferentes partes. Sin embargo, en algunos estilos visuales, es posible que algunas partes no sean compatibles con otras partes.

Otro enfoque para representar controles en el estilo visual activo es usar los colores del sistema. Por ejemplo, puede usar colores del sistema para establecer el color del texto al llamar a la función DrawThemeTextEx . La mayoría de los colores del sistema se establecen cuando se aplica un archivo de estilo visual.

Responder a los cambios de tema

Cuando el control recibe un mensaje de WM_THEMECHANGED y contiene un identificador global para el tema, debe hacer lo siguiente:

  • Llame a CloseThemeData para cerrar el identificador de tema existente.
  • Llame a OpenThemeData para obtener el identificador del tema al estilo visual recién cargado.

En el ejemplo siguiente se muestran las dos llamadas.

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

Habilitar los estilos visuales

Estilos visuales