Поддержка темной и светлой тем в приложениях Win32

Windows поддерживает светлую и темную темы в качестве параметра персонализации в параметрах Windows. В Windows по умолчанию используется светлый режим, но пользователи могут выбрать темный режим, в котором цвет большей части пользовательского интерфейса изменяется на темный. Пользователи могут выбрать этот параметр, так как он упрощает просмотр в средах с менее интенсивным освещением, или из-за того, что в целом предпочитают более темный интерфейс. Кроме того, более темные цвета пользовательского интерфейса могут уменьшить использование батареи на дисплеях компьютера некоторых типов, таких как экраны OLED

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

Мы работаем над расширением поддержки темного режима без нарушения работы существующих приложений. Для этого мы предоставляем техническое руководство по обновлению классического приложения Win32 для Windows для поддержки как светлого, так и темного режима.

Сравнение светлого и темного режимов

Цветовой режим в параметрах (в частности, светлый и темный режимы) — это параметр, определяющий общие цвета переднего плана и фона для операционной системы и приложений.

Режим Description Пример
Светлый Светлый фон с контрастным темным передним планом.

Обычно в светлом режиме черный или темный текст отображается на белом или светлом фоне.
A screenshot of the Alarms & Clock app in light mode
Темный Темный фон с контрастным светлым передним планом.

Обычно в темном режиме белый или светлый текст отображается на черном или темном фоне.
A screenshot of the Alarms & Clocks app in Dark mode

Примечание.

Причина использования черного или темного, а также белого или светлого цветов заключается в том, что есть дополнительные цвета, такие как цвет элементов, которые могут оттенять различные цвета переднего плана и фона. Так, в некоторых частях пользовательского интерфейса светло-синий текст может отображаться на темно-синем фоне. Это по-прежнему считается приемлемым пользовательским интерфейсом в темном режиме.

Из-за широкого разнообразия оформления пользовательского интерфейса в различных приложениях настройка определенного цветового режима, а также цвета переднего плана и фона является скорее рекомендацией, чем правилом:

  • Элементы переднего плана, выделенные элементы и текст должны быть ближе к цвету переднего плана, чем к цвету фона.
  • Большие, сплошные фоновые области и фон текста обычно должны быть ближе к цвету фона, чем к цвету переднего плана.

На практике это означает, что в темном режиме большая часть пользовательского интерфейса будет темной. В светлом режиме большая часть пользовательского интерфейса будет светлой. Концепция фона в Windows — это большая область цветов в приложении или цвет страницы. Концепция переднего плана в Windows — это цвет текста.

Совет

Если вам неудобно работать, когда цвет переднего плана светлый в темном режиме и темный в светлом режиме, возможно, целесообразно будет задать для переднего плана цвет текста по умолчанию.

Включение поддержки переключения цветового режима

Есть много подходов к реализации поддержки темного режима в приложении. Некоторые приложения содержат два набора пользовательских интерфейсов (со светлым и с темным цветом). Некоторые платформы пользовательского интерфейса Windows, такие как WinUI 3, автоматически определяют тему системы и настраивают пользовательский интерфейс в соответствии с этой темой. Для полной поддержки темного режима вся область отображения приложения должна соответствовать темной теме.

Для поддержки светлой и темной тем в приложении Win32 можно выполнить два следующих основных действия:

  • Определить, включен ли темный режим.

    Если вы знаете, включен ли в параметрах системы темный режим, вам будет легче понять, когда нужно переключить тему пользовательского интерфейса вашего приложения на темную.

  • Включить для приложений Win32 строки заголовка в темном режиме.

    Не все приложения Win32 поддерживают темный режим, поэтому по умолчанию Windows предоставляет приложениям Win32 светлую строку заголовка. Если вы подготовили все к поддержке темного режима, можно запросить у Windows реализацию темной строки заголовка, если включен темный режим.

Примечание.

В этой статье приведены примеры способов определения изменений темы системы и запрашивания светлой или темной строки заголовка окна приложения Win32. В статье не описаны особенности того, как изменить цвет пользовательского интерфейса приложения и отобразить его с помощью набора цветов темного режима.

Определение того, включен ли темный режим

Первый шаг — отслеживание самого параметра цветового режима. Это позволит настроить код цветового оформления и отображения приложения для использования цветового набора темного режима. Для этого приложение должно считывать параметр цвета при запуске и определять, когда параметры цвета изменяются во время сеанса приложения.

Для этого используйте в приложении Win32 Windows::UI::Color и определите, можно ли классифицировать цвет как светлый или темный. Чтобы использовать Windows::UI::Color, импортируйте (в pch.h) заголовок Windows.UI.ViewManagement из winrt.

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

Также включите это пространство имен в main.cpp.

using namespace Windows::UI::ViewManagement;

В main.cpp с помощью этой функции определите, можно ли классифицировать цвет как светлый.

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

Эта функция выполняет быстрое вычисление воспринимаемой яркости цвета и учитывает способы, с помощью которых различные каналы в значении цвета RGB влияют на то, насколько ярко цвет воспринимается человеческим глазом. Для повышения скорости на стандартных ЦП функция использует целочисленную математику.

Примечание.

Это не является моделью для реального анализа яркости цвета. Это полезно для быстрых вычислений, требующих определения того, можно ли классифицировать цвет как светлый или темный. Цвета темы часто могут быть светлыми, но не чисто-белыми, или темными, но не чисто-черными.

Теперь, когда у вас есть функция, которая проверяет, является ли цвет светлым, с ее помощью можно определить, включен ли темный режим.

Темный режим определяется как темный фон с контрастным светлым передним планом. Так как IsColorLight проверяет, является ли цвет светлым, с помощью функции можно узнать, является ли светлым передний план. Если передний план светлый, значит, включен темный режим.

Для этого необходимо получить тип цвета пользовательского интерфейса переднего плана из параметров системы. Используйте этот код в main.cpp.

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

UISettings позволяет получить все параметры пользовательского интерфейса, включая параметр цвета. Вызовите UISettings.GetColorValue(UIColorType::Foreground), чтобы получить значение цвета переднего плана из параметров пользовательского интерфейса.

Теперь вы можете выполнить проверку, чтобы узнать, считается ли передний план светлым (в main.cpp).

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

wprintf(L"\nisDarkMode: %u\n", isDarkMode);
  • Если передний план светлый, то isDarkMode будет иметь значение 1 (true). Это означает, что включен темный режим.
  • Если передний план темный, то isDarkMode будет иметь значение 0 (false). Это означает, что темный режим не включен.

Чтобы автоматически отслеживать изменения параметров темного режима во время сеанса приложения, вы можете создать для проверок программу-оболочку, как показано ниже.

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);
});

Полный код должен выглядеть примерно так:

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);
    }
}

При выполнении этого кода:

Если включен темный режим, isDarkMode будет иметь значение 1.

A screenshot of an app in dark mode.

При изменении параметра с темного режима на светлый isDarkModeRevoker будет иметь значение 0.

A screenshot of an app in light mode.

Включить для приложений Win32 строки заголовка в темном режиме.

У Windows нет данных о том, может ли приложение поддерживать темный режим, поэтому предполагается, что оно не может поддерживать этот режим, из соображений обратной совместимости. Некоторые платформы разработки Windows, такие как пакет SDK приложений для Windows, нативно поддерживают темный режим и изменяют определенные элементы пользовательского интерфейса без дополнительного кода. Приложения Win32 часто не поддерживают темный режим, поэтому по умолчанию Windows предоставляет приложениям Win32 светлую строку заголовка.

Но для любого приложения, использующего стандартную строку заголовка Windows, можно включить темную версию строки заголовка, если система находится в темном режиме. Чтобы включить темную строку заголовка, вызовите функцию диспетчера окон рабочего стола (DWM) с именем DwmSetWindowAttribute в окне верхнего уровня, используя атрибут окна DWMWA_USE_IMMERSIVE_DARK_MODE (DWM отображает атрибуты для окна).

В следующих примерах предполагается, что у вас есть окно со стандартной строкой заголовка, например окно, созданное с помощью этого кода.

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

Сначала импортируйте API DWM, как показано ниже.

#include <dwmapi.h>

Затем определите макросы DWMWA_USE_IMMERSIVE_DARK_MODE над функцией InitInstance.

#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
…

В результате с помощью API DWM вы сможете настроить темный цвет для заголовка строки. На этом этапе создается BOOL с именем value и ему присваивается значение TRUE. BOOL позволяет активировать этот параметр атрибута Windows. Затем с помощью DwmSetWindowAttribute вы можете изменить атрибут окна для использования цветов темного режима.

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

Ниже назначение этого вызова описано более подробно.

Блок синтаксиса для DwmSetWindowAttribute выглядит примерно так:

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

После передачи hWnd (дескриптора для окна, которое вам нужно изменить) в качестве первого параметра нужно передать DWMWA_USE_IMMERSIVE_DARK_MODE в качестве параметра dwAttribute. Это константа в API DWM, которая обеспечивает отображение интерфейса Windows в цветах темного режима, когда включен системный параметр темного режима. Если вы переключитесь на светлый режим, вам нужно будет изменить значение DWMWA_USE_IMMERSIVE_DARK_MODE с 20 на 0, чтобы строка заголовка отображались в цветах светлого режима.

Параметр pvAttribute указывает на значение типа BOOL (поэтому вы установили значение BOOL ранее). Требуется, чтобы для pvAttribute было установлено значение TRUE. Это позволит применить для окна темный режим. Если для pvAttribute установлено значение FALSE, для окна будет использоваться светлый режим.

Наконец, для cbAttribute необходимо задать размер атрибута в pvAttribute. Чтобы без труда выполнить эту задачу, мы передаем sizeof(value).

Код для реализации темной строки заголовка окна должен выглядеть примерно так:

#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;
}

При выполнении этого кода заголовок окна приложения должен быть темным:

A screenshot of an app with a dark title bar.

См. также