Suporte a temas escuros e claros em aplicativos Win32

O Windows dá suporte aos temas Claro e Escuro como uma opção de personalização nas configurações do Windows. O Windows usa o modo Claro por padrão, mas os usuários podem escolher o modo Escuro, que altera grande parte da interface do usuário para uma cor escura. Os usuários podem preferir essa configuração porque é mais fácil para os ambientes com menos luz eles podem simplesmente preferir uma interface mais escura em geral. Além disso, as interfaces do usuário coloridas podem reduzir o uso da bateria em alguns tipos de monitores de computador, como os que têm telas OLED.

A split image of an app in light theme on the left, and dark theme on the right.

Estamos trabalhando para ampliar o suporte para o modo Escuro sem interromper os aplicativos existente. Para isso, estamos fornecendo diretrizes técnicas para atualizar um aplicativo de Windows da área de trabalho do Win32 para dar suporte aos modos Claro e Escuro.

Modo escuro vs. Modo claro

O Modo de Cor nas configurações (que inclui os modos Claro e Escuro) é uma configuração que define as cores gerais do primeiro plano e da tela de fundo para o sistema operacional e os aplicativos.

Mode Descrição Exemplo
Claro Uma tela de fundo clara com um primeiro plano escuro em contraste.

No Modo Claro, você geralmente verá texto preto ou escuro em telas de fundo brancas ou claras.
A screenshot of the Alarms & Clock app in light mode
Escuro Uma tela de fundo escura com um primeiro plano claro em contraste.

No modo Escuro, você geralmente verá texto em branco ou claro em telas de fundo pretas ou escuras.
A screenshot of the Alarms & Clocks app in Dark mode

Observação

A razão pela qual usamos "preto ou escuro" e "branco ou claro" é porque há cores adicionais, como a de ênfase, que podem diferenciar várias cores de primeiro plano e de tela de fundo. Portanto, você pode, de fato, ver texto azul claro em uma tela de fundo azul escuro em algumas partes da interface do usuário, e isso ainda seria considerado aceitável para a interface do usuário no modo escuro.

Devido à ampla diversidade da interface do usuário em diferentes aplicativos, o modo de cor e as cores de primeiro plano e de tela de fundo são mais uma diretriz direcional do que uma regra rígida:

  • Os elementos no primeiro plano, realces e texto devem estar mais próximos da cor de primeiro plano do que da cor da tela de fundo.
  • Áreas da tela de fundo grandes e sólidas e telas de fundo com texto geralmente devem estar mais próximas da cor da tela de fundo do que da cor de primeiro plano.

Na prática, isso significa que, no modo Escuro, a maior parte da interface do usuário será escura e, no modo Claro, a maior parte da interface do usuário será clara. O conceito de uma tela de fundo no Windows é a grande área de cores em um aplicativo ou a cor da página. O conceito de primeiro plano no Windows é a cor do texto.

Dica

Se você achar confuso que a cor de primeiro plano é clara no modo escuro e escura no modo claro, pode ajudar a pensar na cor de primeiro plano como "a cor do texto padrão".

Habilitar o suporte para alternar modos de cor

Há muitas abordagens para implementar o suporte ao modo escuro em um aplicativo. Alguns aplicativos contêm dois conjuntos de interfaces do usuário (um com uma cor clara e outro com uma cor escura). Algumas estruturas de interface do usuário do Windows, como o WinUI 3, detectam automaticamente o tema de um sistema e ajustam a interface do usuário para seguir o tema do sistema. Para dar suporte total ao modo Escuro, a totalidade da superfície de um aplicativo deve seguir o tema escuro.

Há duas coisas principais que você pode fazer em seu aplicativo Win32 para dar suporte a temas claros e escuros.

  • Saber quando o modo Escuro está habilitado

    Saber quando o modo escuro está habilitado nas configurações do sistema pode ajudar você a saber quando alternar a interface do usuário do aplicativo para uma interface do usuário com tema do modo escuro.

  • Habilitar uma barra de título do modo Escuro para aplicativos Win32

    Nem todos os aplicativos Win32 dão suporte ao modo Escuro, portanto, o Windows dá aos aplicativos Win32 uma barra de título clara por padrão. Se você estiver preparado para dar suporte ao modo Escuro, poderá solicitar que Windows desenhe a barra de título escura quando o modo escuro estiver habilitado.

Observação

Este artigo fornece exemplos de maneiras de detectar alterações de tema do sistema e solicitar uma barra de título clara ou escura para a janela do aplicativo Win32. Ele não aborda detalhes de como repintar e renderizar a interface do usuário do aplicativo usando um conjunto de cores do modo escuro.

Saber quando o modo Escuro está habilitado

A primeira etapa é acompanhar a própria configuração do modo de cor. Isso permitirá que você ajuste o código de pintura e renderização do aplicativo para usar um conjunto de cores do modo Escuro. Fazer isso requer que o aplicativo leia a configuração de cores na inicialização e saiba quando a configuração de cor é alterada durante uma sessão de aplicativo.

Para fazer isso em um aplicativo Win32, use Windows::UI::Color e detecte se uma cor pode ser classificada como clara ou escura. Para usar Windows::UI::Color, você precisa importar (no pch.h) o cabeçalho Windows.UI.ViewManagement do winrt.

#include <winrt/Windows.UI.ViewManagement.h>

Inclua também esse namespace em main.cpp.

using namespace Windows::UI::ViewManagement;

No main.cpp, use essa função para detectar se uma cor pode ser classificada como clara.

inline bool IsColorLight(Windows::UI::Color& clr)
{
    return (((5 * clr.G) + (2 * clr.R) + clr.B) > (8 * 128));
}

Essa função executa um cálculo rápido do brilho percebido de uma cor e leva em consideração maneiras que diferentes canais em um valor de cor RGB contribuem para o quão brilhante é a sua aparência para o olho humano. Ela usa matemática de inteiro para aceleração em CPUs típicas.

Observação

Isso não é um modelo para análise real de brilho de cor. É bom para cálculos rápidos que exigem que você determine se uma cor pode ser classificada como clara ou escura. As cores do tema geralmente podem ser claras, mas não brancas puras, ou escuras, mas não pretas puras.

Agora que você tem uma função para verificar se uma cor é clara, você pode usar essa função para detectar se o modo Escuro está habilitado.

O modo Escuro é definido como uma tela de fundo escura com um primeiro plano de luz em contraste. Como IsColorLight verifica se uma cor é considerada clara, você pode usar essa função para ver se o primeiro plano é claro. Se o primeiro plano for claro, o modo Escuro estará habilitado.

Para fazer isso, você precisa obter o tipo de cor da interface do usuário do primeiro plano das configurações do sistema. Use este código no main.cpp.

auto settings = UISettings();
    
auto foreground = settings.GetColorValue(UIColorType::Foreground);

UISettings obtém todas as configurações da interface do usuário incluindo a cor. Chame UISettings.GetColorValue(UIColorType::Foreground) para obter o valor da cor de primeiro plano das configurações da interface do usuário.

Agora você pode executar uma verificação para ver se o primeiro plano é considerado claro (no main.cpp).

bool isDarkMode = static_cast<bool>(IsColorLight(foreground));

wprintf(L"\nisDarkMode: %u\n", isDarkMode);
  • Se o primeiro plano for claro, então isDarkMode avaliado como 1 (true), o que significa que o modo Escuro está habilitado.
  • Se o primeiro plano for escuro, então isDarkMode avaliado como 0 (false), o que significa que o modo Escuro não está habilitado.

Para acompanhar automaticamente quando a configuração do modo Escuro é alterada durante uma sessão de aplicativo, você pode encapsular suas verificações assim.

auto revoker = settings.ColorValuesChanged([settings](auto&&...)
{
    auto foregroundRevoker = settings.GetColorValue(UIColorType::Foreground);
    bool isDarkModeRevoker = static_cast<bool>(IsColorLight(foregroundRevoker));
    wprintf(L"isDarkModeRevoker: %d\n", isDarkModeRevoker);
});

Seu código completo deve ter esta aparência.

inline bool IsColorLight(Windows::UI::Color& clr)
{
    return (((5 * clr.G) + (2 * clr.R) + clr.B) > (8 * 128));
}

int main()
{
    init_apartment();

    auto settings = UISettings();
    auto foreground = settings.GetColorValue(UIColorType::Foreground);

    bool isDarkMode = static_cast<bool>(IsColorLight(foreground));
    wprintf(L"\nisDarkMode: %u\n", isDarkMode);

    auto revoker = settings.ColorValuesChanged([settings](auto&&...)
        {
            auto foregroundRevoker = settings.GetColorValue(UIColorType::Foreground);
            bool isDarkModeRevoker = static_cast<bool>(IsColorLight(foregroundRevoker));
            wprintf(L"isDarkModeRevoker: %d\n", isDarkModeRevoker);
        });
    
    static bool s_go = true;
    while (s_go)
    {
        Sleep(50);
    }
}

Quando esse código é executado:

Se o modo Escuro estiver habilitado, isDarkMode será avaliado como 1.

A screenshot of an app in dark mode.

A alteração da configuração do modo Escuro para o modo Claro fará com que isDarkModeRevoker seja avaliado como 0.

A screenshot of an app in light mode.

Habilitar uma barra de título do modo Escuro para aplicativos Win32

O Windows não sabe se um aplicativo pode dar suporte ao modo Escuro, portanto, ele pressupõe que não, por motivos de compatibilidade com versões anteriores. Algumas estruturas de desenvolvimento do Windows, como o SDK do Aplicativo Windows, dão suporte ao modo Escuro nativamente e alteram determinados elementos de interface do usuário sem nenhum código adicional. Os aplicativos Win32 geralmente não dão suporte ao modo Escuro, portanto, o Windows dá aos aplicativos Win32 uma barra de título clara por padrão.

No entanto, para qualquer aplicativo que use a barra de título padrão do Windows, você poderá habilitar a versão escura da barra de título quando o sistema estiver no modo Escuro. Para habilitar a barra de título escura, chame uma função DWM (Gerenciador de Janelas da Área de Trabalho) chamada DwmSetWindowAttribute em sua janela de nível superior, usando o atributo de janela DWMWA_USE_IMMERSIVE_DARK_MODE. (O DWM renderiza atributos para uma janela.)

Os exemplos a seguir pressupõem que você tenha uma janela com uma barra de título padrão, como a criada por esse código.

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, 
     CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

Primeiro, você precisa importar a API do DWM, assim.

#include <dwmapi.h>

Em seguida, defina as macros DWMWA_USE_IMMERSIVE_DARK_MODE acima da sua função InitInstance.

#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
…

Por fim, você pode usar a API do DWM para definir que a barra de título use uma cor escura. Aqui, você cria um BOOL chamado value e o define como TRUE. Esse BOOL é usado para disparar essa configuração de atributo do Windows. Em seguida, você usa DwmSetWindowAttribute para alterar o atributo de janela para usar cores do modo Escuro.

BOOL value = TRUE;
::DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));

Veja uma explicação adicional do que essa chamada faz.

O bloco de sintaxe de DwmSetWindowAttribute tem essa aparência.

HRESULT DwmSetWindowAttribute(
       HWND    hwnd,
       DWORD   dwAttribute,
  [in] LPCVOID pvAttribute,
       DWORD   cbAttribute
);

Depois de passar hWnd (o identificador para a janela que você deseja alterar) como seu primeiro parâmetro, você precisa passar DWMWA_USE_IMMERSIVE_DARK_MODE como o parâmetro dwAttribute. Essa é uma constante na API do DWM que permite que o quadro do Windows seja desenhado em cores do modo Escuro quando a configuração do sistema do modo escuro estiver habilitada. Se você alternar para o modo Claro, precisará alterar DWMWA_USE_IMMERSIVE_DARK_MODE de 20 para 0 para que a barra de título seja desenhada em cores do modo claro.

O parâmetro pvAttribute aponta para um valor de tipo BOOL (e é por isso que você criou o valor BOOL anteriormente). Você precisa que pvAttribute seja TRUE para honrar o modo Escuro da janela. Se pvAttribute for FALSE, a janela usará o modo Claro.

Por fim, cbAttribute precisa ter o tamanho do atributo que está sendo definido em pvAttribute. Para fazer isso facilmente, passamos sizeof(value).

Seu código para desenhar uma barra de título de janelas escuras deve ter essa aparência.

#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif


BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   BOOL value = TRUE;
   ::DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

Quando esse código é executado, a barra de título do aplicativo deve estar escura:

A screenshot of an app with a dark title bar.

Confira também