もくじ
https://tera1707.com/entry/2022/02/06/144447
やりたいこと
下記の記事で、WinUI3で、Windowsのダークモード、ライトモード、ハイコントラストの対応を以前行った。
が、今回は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で同様のことを調べたときに、また別のやり方もあったような気がする。
(今見る気力ないので、あとで↓見てみる。。)
サンプルコード
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は何が違うか?
勉強中...
参考
ダークライトの取得、イベント取得の詳細↓