没有直接的 API/事件来检测 wpf 的暗模式或高对比度模式。可以在 uwp 中找到。但是有一种方法可以通过 WMI 查询来检测主题更改事件,以观察相关注册表项的注册表更改。你会发现详情here https://engy.us/blog/2018/10/20/dark-theme-in-wpf/。
我有一个简化的类,您可以通过它检测注册表更改。
public class ThemeWatcher
{
private const string RegistryKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
private const string RegistryValueName = "AppsUseLightTheme";
private static WindowsTheme windowsTheme;
public WindowsTheme WindowsTheme
{
get { return windowsTheme; }
set { windowsTheme = value; }
}
public void StartThemeWatching()
{
var currentUser = WindowsIdentity.GetCurrent();
string query = string.Format(
CultureInfo.InvariantCulture,
@"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_USERS' AND KeyPath = '{0}\\{1}' AND ValueName = '{2}'",
currentUser.User.Value,
RegistryKeyPath.Replace(@"\", @"\\"),
RegistryValueName);
try
{
windowsTheme = GetWindowsTheme();
MergeThemeDictionaries(windowsTheme);
var watcher = new ManagementEventWatcher(query);
watcher.EventArrived += Watcher_EventArrived;
SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
// Start listening for events
watcher.Start();
}
catch (Exception ex)
{
// This can fail on Windows 7
windowsTheme = WindowsTheme.Default;
}
}
private void MergeThemeDictionaries(WindowsTheme windowsTheme)
{
string appTheme = "Light";
switch (windowsTheme)
{
case WindowsTheme.Light:
appTheme = "Light";
break;
case WindowsTheme.Dark:
appTheme = "Dark";
break;
case WindowsTheme.HighContrast:
appTheme = "HighContrast";
break;
}
App.Current.Resources.MergedDictionaries[0].Source = new Uri($"/Themes/{appTheme}.xaml", UriKind.Relative);
}
private void SystemParameters_StaticPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
windowsTheme = GetWindowsTheme();
MergeThemeDictionaries(windowsTheme);
ThemeChangedArgument themeChangedArgument = new ThemeChangedArgument();
themeChangedArgument.WindowsTheme = windowsTheme;
App.WindowsThemeChanged?.Invoke(this, themeChangedArgument);
}
private void Watcher_EventArrived(object sender, EventArrivedEventArgs e)
{
windowsTheme = GetWindowsTheme();
MergeThemeDictionaries(windowsTheme);
ThemeChangedArgument themeChangedArgument = new ThemeChangedArgument();
themeChangedArgument.WindowsTheme = windowsTheme;
App.WindowsThemeChanged?.Invoke(this, themeChangedArgument);
}
public WindowsTheme GetWindowsTheme()
{
WindowsTheme theme = WindowsTheme.Light;
try
{
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(RegistryKeyPath))
{
object registryValueObject = key?.GetValue(RegistryValueName);
if (registryValueObject == null)
{
return WindowsTheme.Light;
}
int registryValue = (int)registryValueObject;
if (SystemParameters.HighContrast)
theme = WindowsTheme.HighContrast;
theme = registryValue > 0 ? WindowsTheme.Light : WindowsTheme.Dark;
}
return theme;
}
catch (Exception ex)
{
return theme;
}
}
}
创建相关的Enum进行逻辑实现:
public enum WindowsTheme
{
Default = 0,
Light = 1,
Dark = 2,
HighContrast = 3
}
将相关资源文件添加到项目中。
定义一个回调参数,当注册表发生更改时,该参数将通过事件处理程序传递。
public class ThemeChangedArgument
{
public WindowsTheme WindowsTheme { set; get; }
}
现在开始观看从 App.xaml.cs 的 OnStartup 方法更改的主题。
public partial class App : Application
{
private ThemeWatcher themeWatcher;
private WindowsTheme systrayTheme = WindowsTheme.Light;
public static EventHandler<ThemeChangedArgument> WindowsThemeChanged;
.......
protected override void OnStartup(StartupEventArgs e)
{
.......................
themeWatcher = new ThemeWatcher();
systrayTheme = themeWatcher.GetWindowsTheme();
themeWatcher.StartThemeWatching();
if(WindowsThemeChanged != null)
{
WindowsThemeChanged -= OnWindowsThemeChanged;
}
WindowsThemeChanged += OnWindowsThemeChanged;
.......................
}
private void OnWindowsThemeChanged(object sender, ThemeChangedArgument e)
{
systrayTheme = e.WindowsTheme;
//Now do whatever you want to do with this updated theme.
}
protected override void OnExit(ExitEventArgs e)
{
base.OnExit(e);
try
{
if (WindowsThemeChanged != null)
{
WindowsThemeChanged -= OnWindowsThemeChanged;
}
Application.Current?.Shutdown();
Process.GetCurrentProcess()?.Kill();
}
catch (Exception ex)
{
}
}
}
注意:我们已经将 ThemeWatcher 类中的相关样式资源与该方法合并合并主题字典()由于主题更改事件被触发。您也可以根据您的需要从这里更新它。
由于运行时资源发生变化后,调用以下方法来更新 UI。
private void InvalidedMainWindow()
{
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.InvokeAsync(() => InvalidedMainWindow());
return;
}
App.Current.MainWindow.UpdateLayout();
}