共用方式為


支援 Win32 應用程式中的深色和淺色主題

Windows 支援淺色深色主題作為 Windows 設定中的個人化選項。 Windows 預設使用淺色模式,但使用者可以選擇深色模式,將大部分 UI 變更為深色。 使用者偏好此設定可能是因為在昏暗環境下較不刺眼,或只是大致偏好較深色的介面。 此外,較深的 UI 色彩可降低特定類型電腦顯示器 (例如 OLED 螢幕) 的電池用量。

分割影像:左側為淺色主題的應用程式,右側為深色主題的應用程式。

我們正努力擴大對深色模式的支援,同時不妨礙現有應用程式的正常運作,為此提供一份技術指引,說明如何更新 Win32 傳統型 Windows 應用程式以支援淺色和深色模式。

深色模式 vs.淺色模式

在設定中的色彩模式 (包括淺色和深色模式) 是定義作業系統和應用程式前景背景整體色彩的設定值。

[模式] 描述 範例
淺色 淺色背景搭配對比色的深色前景。

在淺色模式中,您通常會在白色或淺色背景上看到黑色或深色文字。
處於淺色模式之鬧鐘 & 時鐘應用程式的螢幕擷取畫面
深色 深色背景搭配對比色的淺色前景。

在深色模式中,您通常會在黑色或深色背景上看到白色或淺色文字。
處於深色模式之鬧鐘 & 時鐘應用程式的螢幕擷取畫面

注意

我們使用「黑色或深色」和「白色或淺色」的原因是可以使用其他色彩 (例如輔色) 來調和各種前景和背景色彩。 因此,您實際上可能會在 UI 的某些部分看到深藍色背景上的淺藍色文字,這仍會視為可接受的深色模式 UI。

由於不同應用程式中的 UI 種類繁多,因此下列色彩模式、前景和背景配色方針較具參考性質,而非硬性規定:

  • 前景元素、醒目提示和文字應較背景色彩更接近前景色彩。
  • 大型、純色背景區域和文字背景通常應較前景色彩更接近背景色彩。

在實務上,這表示深色模式中的大部分 UI 會是深色,而淺色模式中的大部分 UI 會是淺色。 Windows 中的背景概念是指應用程式中的大片區域色彩或頁面色彩。 Windows 中的前景概念是指文字色彩。

提示

若您不理解為何前景色彩在深色模式中顯得較淺,而在淺色模式中顯得較深,則將前景色彩視為「預設文字色彩」可能會有幫助。

啟用切換色彩模式支援

有多種方法可在應用程式中實作深色模式支援。 某些應用程式包含兩組 UI (淺色與深色各一組)。 某些 Windows UI 架構 (例如 WinUI 3) 會自動偵測系統主題,並調整 UI 以遵循該系統主題。 應用程式介面必須全部遵循深色主題,才能完全支援深色模式。

您可以在 Win32 應用程式中執行兩項主要動作,以支援淺色和深色主題。

  • 瞭解何時啟用深色模式

    瞭解何時在系統設定中啟用深色模式,有助於知道何時要將應用程式 UI 切換至深色模式主題 UI。

  • 啟用 Win32 應用程式的深色模式標題列

    並非所有 Win32 應用程式都支援深色模式,因此 Windows 依預設為 Win32 應用程式提供淺色標題列。 若已準備好支援深色模式,您可以要求 Windows 在啟用深色模式時改為繪製深色標題列。

注意

本文將舉例說明如何偵測系統主題變更,並為 Win32 應用程式視窗要求淺色或深色標題列。 本文並未具體介紹如何使用深色模式色彩集來重新繪製和轉譯應用程式 UI。

瞭解何時啟用深色模式

第一個步驟是追蹤色彩模式設定本身。 這可讓您調整應用程式的繪製和轉譯程式碼,以使用深色模式色彩集。 要執行此步驟,應用程式需要在啟動時讀取色彩設定,並知道色彩設定在應用程式工作階段何時變更。

若要在 Win32 應用程式中執行這項操作,請使用 Windows::UI::Color,並偵測色彩是否可以分類為淺色深色。 若要使用 Windows::UI::Color,您必須從 winrt 匯入 Windows.UI.ViewManagement 標頭 (至 pch.h 中)。

#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 色彩值中的不同色版 (channel) 如何影響該色彩在人眼觀看下的亮度。 此函式採用純整數的數學,以提升典型 CPU 上的計算速度。

注意

此模型並非用於真正的色彩亮度分析, 而是適合在需要時進行快速計算,以判斷色彩是否可以分類為淺色深色。 主題色彩通常可以是非純白的淺色,或是非純黑的深色。

現在您已有函式來檢查色彩是否為淺色,該函式亦可用於偵測是否已啟用深色模式。

深色模式定義為以深色背景搭配對比色的淺色前景。 由於 IsColorLight 會檢查色彩是否為淺色,因此您可以使用該函式來查看前景是否為淺色。 如果前景是淺色,則會啟用深色模式。

為此,您必須從系統設定中取得前景的 UI 色彩類型。 在 main.cpp 中使用此程式碼。

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

UISettings 會取得 UI 的所有設定,包括色彩。 呼叫 UISettings.GetColorValue (UIColorType::Foreground) 以從 UI 設定取得前景色彩值。

現在您可以執行檢查,以查看前景是否判定為淺色 (在 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。

處於深色模式之應用程式的螢幕擷取畫面。

若將設定從深色模式變更為淺色模式,會使 isDarkModeRevoker 評估為 0。

處於淺色模式之應用程式的螢幕擷取畫面。

啟用 Win32 應用程式的深色模式標題列

Windows 不知道應用程式是否支援深色模式,因此會基於回溯相容性的原因,而將其預設為不支援。 某些 Windows 開發架構 (例如 Windows 應用程式 SDK) 原生支援深色模式,且無需其他程式碼即可變更特定 UI 元素。 Win32 應用程式通常不支援深色模式,因此 Windows 預設會為 Win32 應用程式提供淺色標題列。

然而任何使用標準 Windows 標題列的應用程式,都可以在系統處於深色模式時啟用深色版本的標題列。 若要啟用深色標題列,請使用視窗屬性 DWMWA_USE_IMMERSIVE_DARK_MODE,在頂層視窗呼叫一項名為 DwmSetWindowAttributeDesktop Windows Manager (DWM) 函式。 (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;
}

首先,您需要匯入 DWM API,如下所示。

#include <dwmapi.h>

然後,定義 InitInstance 函式上方的 DWMWA_USE_IMMERSIVE_DARK_MODE 巨集。

#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
…

最後,您可以使用 DWM API 將標題列設定為使用深色。 在此,您會建立名為 valueBOOL,並將其設定為 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 參數傳入。 這是 DWM API 中的常數,可在啟用深色模式系統設定時,以深色模式色彩繪製 Windows 框架。 如果您切換到淺色模式,則必須將標題列的 DWMWA_USE_IMMERSIVE_DARK_MODE 從 20 變更為 0,才能以淺色模式色彩繪製標題列。

pvAttribute 參數會指向類型為 BOOL 的值 (這就是您稍早建立 BOOL 值的原因)。 pvAttribute 必須為 TRUE,才能對視窗套用深色模式。 如果 pvAttributeFALSE,則視窗會使用淺色模式。

最後,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;
}

執行此程式碼時,應用程式標題列應為深色:

具有深色標題列之應用程式的螢幕擷取畫面。

另請參閱