single.php

C# WinFormsプロジェクトでWindowsのダークモードやアクセントカラーの変更を検出したい場合の対処法

C#のWinFormsプロジェクトで、Windows11の[ダークモード]や[アクセントカラー]の変更を検出したい場合の対処法について備忘録的に投稿します。

NativeWindowクラスで検出

Windows側でのダークモードやアクセントカラーの検出は、フォームを持つクラスで行うと容易に取得できますが、フォームを持たない[NativeWindow]クラスでも可能です。

using System;
using System.Collections.Generic;
using System.Text;

namespace SampleForms
{
    internal class ThemeMessageListener : NativeWindow, IDisposable
    {

    }
}

Actionイベントでテーマ変更を検知

[NativeWindow]クラス内で[Action]イベントを使って、Windows側のテーマ変更を受け取ります。

コードにすると、こんな感じです。

internal class ThemeMessageListener : NativeWindow, IDisposable
{
    private bool _isDark;
    private Color _AccentColor;

    public event Action<ThemeInfo>? ThemeChanged;

    public class ThemeInfo
    {
        public bool IsDark { get; set; }
        public Color AccentColor { get; set; }
    }

    public ThemeMessageListener()
    {
        _isDark = IsDarkMode();
        CreateHandle(new CreateParams());
    }

    protected override void WndProc(ref Message m)
    {
        const int WM_SETTINGCHANGE = 0x001A;
        const int WM_THEMECHANGED = 0x031A;

        if (m.Msg == WM_SETTINGCHANGE || m.Msg == WM_THEMECHANGED)
        {
            bool themeChanged = false;

            bool isThemeMessage =
                m.Msg == WM_THEMECHANGED ||
                (m.Msg == WM_SETTINGCHANGE &&
                 m.LParam != IntPtr.Zero);

            if(isThemeMessage)
            {
                Color newColor = GetAccentColor();
                if (newColor != _AccentColor)
                {
                    _AccentColor = newColor;
                    themeChanged = true;
                }
            }

            bool newMode = IsDarkMode();
            if (newMode != _isDark)
            {
                _isDark = newMode;
                themeChanged = true;
            }

            if (themeChanged)
            {
                ThemeChanged?.Invoke(new ThemeInfo { IsDark = _isDark, AccentColor = _AccentColor }
            }
        }

        base.WndProc(ref m);
    }

    public void Dispose()
    {
        if (this.Handle != IntPtr.Zero)
        {
            this.DestroyHandle();
        }
    }

    public Color GetAccentColor()
    {
        using var key = Registry.CurrentUser.OpenSubKey(
            @"Software\Microsoft\Windows\DWM");

        object? val = key?.GetValue("ColorizationColor");
        uint cv;

        if (val is int vi)
        {
            cv = unchecked((uint)vi);
        }
        else if (val is uint vu)
        {
            cv = vu;
        }
        else if (val is long vl)
        {
            cv = unchecked((uint)vl);
        }
        else if (val is short vs)
        {
            cv = (uint)vs;
        }
        else if (val is byte vb)
        {
            cv = vb;
        }
        else if (val is string s && uint.TryParse(s, out uint parsed))
        {
            cv = parsed;
        }
        else
        {
            cv = 0xFF0078D7u; //デフォルトはWindows10からの青
        }

        return Color.FromArgb(
            (int)((cv >> 16) & 0xFF), // 赤
            (int)((cv >> 8) & 0xFF),  // 緑
            (int)(cv & 0xFF));        // 青
    }

    public ThemeInfo GetCurrentTheme()
    {
        return new ThemeInfo
        {
            IsDark = IsDarkMode(),
            AccentColor = GetAccentColor()
        };
    }

    private bool IsDarkMode()
    {
        using var key = Registry.CurrentUser.OpenSubKey(
            @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");

        object? val = key?.GetValue("AppsUseLightTheme");
        if (val is int i) return i == 0;
        if (val is byte b) return b == 0;
        if (val is string s && int.TryParse(s, out int parsed)) return parsed == 0;
        return false;
    }

}

呼び出し側は、クラスを作成して[ThemeChanged]イベントを作成しておくと、Windows側のテーマ変更時にイベントとして受け取れます。

private readonly ThemeMessageListener? _themeMessageListener = new ThemeMessageListener();

_themeMessageListener.ThemeChanged += (themeInfo) => {

    //テーマ変更処理
    ApplyTheme(themeInfo);
};

現在のアクセントカラーやモードをレジストリから取得しています。レジストリ以外でも取得する方法がありそうですが、レジストリが一番安定して取得できました。

Windows11の[個人用設定|色]画面で[モードを選ぶ]や[アクセントカラー]の変更時に[ApplyTheme]プロシージャーでフォームの色変更が可能になります。

まとめ

今回は短い記事ですがC#のWinFormsプロジェクトで、Windows11の[ダークモード]や[アクセントカラー]の変更を検出したい場合の対処法について書きました。

テーマの検出などはフォーム付きのクラスで取得が容易ですが、フォームを持たない[NativeWindow]クラスでも可能です。

WinFormsプロジェクトでWindows11のダークモードやアクセントカラーの変更を検出したい人の参考になれば幸いです。

スポンサーリンク

最後までご覧いただき、ありがとうございます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です