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のダークモードやアクセントカラーの変更を検出したい人の参考になれば幸いです。
スポンサーリンク
最後までご覧いただき、ありがとうございます。
