Win32 アプリでのダーク テーマとライト テーマのサポート

Windows は、Windows 設定の個人用設定オプションとして、"ライト" と "ダーク" の各テーマをサポートしています。 Windows では既定でライト モードが使用されますが、ユーザーはダーク モードを選択して UI の大部分を濃色に変更することができます。 ユーザーがこの設定を選ぶのは、この方が低照度環境で目に優しいという場合もあれば、一般的に単に暗いインターフェイスが好みという場合もあります。 また、OLED 画面など、一部の種類のコンピューター ディスプレイでは、UI の色を暗くすることでバッテリー消費を抑えることができます。

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

Microsoft は、既存アプリケーションを中断させることなくダーク モードのサポートを広げることに取り組んでいます。そのため、Win32 デスクトップ Windows アプリを更新してライトとダークの両モードをサポートするための技術ガイダンスを提供しています。

ダーク モードとライト モードの違い

設定のカラー モード (ライトとダークの各モードを含む) は、オペレーティング システムとアプリの全体的な "前景色" と "背景色" を定義する設定です。

モード 説明
"ライト" 明るい背景と対照的な暗い前景。

ライト モードでは、一般に、白または明るい背景に黒などの濃色のテキストが表示されます。
A screenshot of the Alarms & Clock app in light mode
"ダーク" 暗い背景と対照的な明るい前景。

ダーク モードでは、一般に、黒または暗い背景に白などの淡色のテキストが表示されます。
A screenshot of the Alarms & Clocks app in Dark mode

注意

"黒などの濃色" や "白などの淡色" と表現する理由は、アクセント カラーなど、ほかにもさまざまな前景色や背景色を彩る色が用意されているためです。 そのため、実際に UI の一部で濃い青の背景に水色のテキストが表示されることがありますが、これもダーク モードの UI として許容範囲内と見なされます。

アプリの種類に応じた UI の多様性により、カラー モード、前景色と背景色は、厳格なルールというよりは方向性のガイドラインを意味します。

  • 前景の要素、ハイライト、テキストは、背景色よりも前景色に近い色である必要があります。
  • 大きく塗りつぶされた背景領域とテキスト背景は、一般的に前景色よりも背景色に近い色である必要があります。

これは実際には、ダーク モードでは UI のほとんどが暗くなり、ライト モードでは UI のほとんどが明るくなることを意味します。 Windows における背景の概念は、アプリの色の大きな領域、つまりページの色です。 Windows における前景の概念は、テキストの色です。

ヒント

ダーク モードでは "前景" 色が明るく、ライト モードでは暗くなることがわかりにくい場合は、前景色を「既定のテキストの色」と考えるとよいでしょう。

カラー モード切り替えのサポートを有効にする

アプリケーションでダーク モードのサポートを実装するには、さまざまな方法があります。 アプリによっては、2 セットの UI (1 つは淡色、1 つは濃色) が含まれているものがあります。 WinUI 3 などの Windows UI フレームワークの中には、システム テーマが自動的に検出され、システムのテーマに沿うように UI が調整されるものがあります。 ダーク モードを完全にサポートするには、アプリのサーフェス全体がダーク テーマに沿っている必要があります。

Win32 アプリでは、ライト テーマとダーク テーマの両方をサポートするために、主に 2 つの操作を行うことができます。

  • ダーク モードが有効になるタイミングを把握する

    システム設定でダーク モードが有効になるタイミングを知ることで、アプリ 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 カラー値の異なるチャネルが人間の目に見える明るさにどのように寄与するかが考慮されます。 一般的な 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 と評価されます。

A screenshot of an app in dark mode.

設定がダーク モードからライト モードに変更されると、isDarkModeRevoker は 0 と評価されます。

A screenshot of an app in light mode.

Win32 アプリケーションのダーク モード タイトル バーを有効にする

Windows では、アプリケーションがダーク モードをサポートできるかどうかが分からないため、下位互換性上の理由からサポートできないことを前提としています。 Windows アプリ SDK など一部の Windows 開発フレームワークでは、ダーク モードがネイティブにサポートされており、追加のコードなしで特定の UI 要素が変更されます。 Win32 アプリはダーク モードをサポートしていないことが多いため、Windows では Win32 アプリに既定でライト タイトル バーが表示されます。

ただし、標準の Windows タイトル バーが使用されているアプリの場合は、システムがダーク モードの際にタイトル バーのダーク バージョンを有効にすることができます。 ダーク タイトル バーを有効にするには、ウィンドウ属性 DWMWA_USE_IMMERSIVE_DARK_MODE を使用して、最上位のウィンドウの DwmSetWindowAttribute というデスクトップ ウィンドウ マネージャー (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 を使用して、濃色を使用するようにタイトル バーを設定できます。 ここでは、value という BOOL を作成し、それを 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 (変更するウィンドウのハンドル) を渡した後、dwAttribute パラメータとして DWMWA_USE_IMMERSIVE_DARK_MODE を渡す必要があります。 これは DWM API の定数であり、ダーク モードのシステム設定が有効になっている場合に、Windows フレームをダーク モードの色で描画するようにするものです。 ライト モードに切り替える場合、タイトル バーをライト モードの色で描画するには、DWMWA_USE_IMMERSIVE_DARK_MODE を 20 から 0 に変更する必要があります。

pvAttribute パラメーターは BOOL 型の値を指しています (先ほど BOOL 値を作ったのはこのためです)。 ウィンドウのダーク モードを有効にするには、pvAttributeTRUE にする必要があります。 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;
}

このコードを実行すると、アプリのタイトル バーが以下のように暗くなるはずです。

A screenshot of an app with a dark title bar.

関連項目