WPFでWindowsのダーク/ライトモード、ハイコントラストの状態と、状態変化時のイベントを取る

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

下記の記事で、WinUI3で、Windowsのダークモード、ライトモード、ハイコントラストの対応を以前行った。

tera1707.com

が、今回はWPFでも同じようなことをしたい。

※なんとなく、WPFの方が古いから、その辺の仕組みが整備されてないんじゃないか?と心配。

とりあえず、まずはダークライト、ハイコントラストの現在値と、それぞれが変化したときを検出できるのか?を調べてみる。

やったこと

結果、そういうことはできた。下記のようにした。

こうやってとる(現在値)

ダークライトはレジストリ、ハイコントラストは.netのSystemParametersクラスからとる。

設定 取得方法 値の意味
ダーク/ライトモード(user) キーHKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalizeの値AppsUseLightTheme 1:ライトモード
0:ダークモード
ダーク/ライトモード(system) キーHKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalizeの値SystemUsesLightTheme 1:ライトモード
0:ダークモード
ハイコントラスト SystemParameters.HighContrast true:ハイコントラスト
false:通常

こうやってとる(変化時イベント)

ウインドウメッセージで取る。

イベント 取得方法 備考
ダーク/ライトモード WM_SETTINGCHANGEメッセージ到来時 LParamに文字列"ImmersiveColorSet"が入っていたら。
一回設定変えるだけで複数回来るっぽい
ハイコントラスト WM_THEMECHANGEDメッセージ到来時
ハイコントラスト? WM_SETTINGCHANGEメッセージ到来時 LParamに文字列"WindowsThemeElement"が入っていたら。※

※ハイコントラスト変化時のイベントはWM_THEMECHANGED到来時、という情報が多いが、
同時にWM_SETTINGCHANGEでLPに"WindowsThemeElement"が入っているMsgも来ている気がする。まぁWM_THEMECHANGED使えばいいか。。。

※WinUIで同様のことを調べたときに、また別のやり方もあったような気がする。
(今見る気力ないので、あとで↓見てみる。。)

tera1707.com

サンプルコード

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace Jikken
{
    public partial class MainWindow : Window
    {
        bool themeSwitch = false;
        private const int WM_SETTINGCHANGE = 0x001A;
        private const int WM_THEMECHANGED = 0x031A;

        // ダークライト変更時に、複数回のイベントがくるので最後だけとるためのタイマ
        DispatcherTimer dt = new DispatcherTimer();

        public MainWindow()
        {
            InitializeComponent();
            
            //WndProcのフック準備
            var hWnd = new WindowInteropHelper(this).EnsureHandle();
            HwndSource source = HwndSource.FromHwnd(hWnd);
            source.AddHook(new HwndSourceHook(WndProc));

            dt.Interval = TimeSpan.FromSeconds(1);
            dt.Tick += ((sender, e) =>
            {
                dt.Stop();

                // レジストリを呼んで、いまがダークなのかライトなのか、ハイコントラストなのかを取る
                var userUseLightTheme = (int?)Microsoft.Win32.Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme", null);
                var systemUseLightTheme = (int?)Microsoft.Win32.Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "SystemUsesLightTheme", null);
                var isHighContrast = SystemParameters.HighContrast;

                Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")} light(user) = {userUseLightTheme}, light(system) = {systemUseLightTheme}, highContrast = {isHighContrast}");
            });

        }

        // メッセージループを記述したメソッド
        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == WM_SETTINGCHANGE)
            {
                var lp = Marshal.PtrToStringAuto(lParam);

                if (lp == "ImmersiveColorSet")
                {
                    Debug.WriteLine("ダーク/ライト 切替された");
                    dt.Start();
                }

                if (lp == "WindowsThemeElement")
                {
                    // これもハイコントラスト時に来てるっぽい?
                    Debug.WriteLine("ハイコントラスト 切替された2");
                    dt.Start();
                }
            }
            if (msg == WM_THEMECHANGED)
            {
                // こっちが一般的なハイコントラスト変化時に来るイベントっぽい
                Debug.WriteLine("ハイコントラスト 切替された1");
                dt.Start();
            }
            return IntPtr.Zero;
        }
    }
}

下記のようなデバッグMsgが出る。

ハイコントラスト 切替された1
ダーク/ライト 切替された
ハイコントラスト 切替された2
23:50:51.075 light(user) = 0, light(system) = 0, highContrast = False
ハイコントラスト 切替された1
ダーク/ライト 切替された
ハイコントラスト 切替された2
23:51:07.279 light(user) = 1, light(system) = 1, highContrast = True
ハイコントラスト 切替された1
ダーク/ライト 切替された
ハイコントラスト 切替された2
23:51:24.296 light(user) = 0, light(system) = 0, highContrast = False
ダーク/ライト 切替された
ダーク/ライト 切替された
23:51:34.635 light(user) = 1, light(system) = 1, highContrast = False
ダーク/ライト 切替された
ダーク/ライト 切替された
23:51:46.215 light(user) = 0, light(system) = 0, highContrast = False

■注意

WinUI3のときにもあったが、ダークライトのフラグは、ハイコントラスト時には1(ライト)扱いになる様子。

ダークライトのuserとsystemは何が違うか?

勉強中...

参考

ダークライトの取得、イベント取得の詳細↓

https://zenn.dev/tenka/articles/win32_darkmode

WPFのハイコントラスト対応

https://qiita.com/okazuki/items/9428aa8e63df84a34ef3