Reputation: 6786
Windows 10 recently added a dark mode; is there any way to make my WPF app respect this setting? Preferably a switch that could flip it over automatically, but if not, I guess I could read a system setting somewhere and switch to an alternate theme in my code or something...
Upvotes: 4
Views: 2893
Reputation: 2160
There is no straightforward API/event to detect dark mode or high contrast mode from wpf. Which is available in uwp. But there is a way to detect the theme changed event by WMI query to watch the registry changes for related registry key. You will find details here. I have a simplified class by which you can detect the registry changes.
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;
}
}
}
Create realated Enum for logical implementation:
public enum WindowsTheme
{
Default = 0,
Light = 1,
Dark = 2,
HighContrast = 3
}
Add Related Resource files to the project.
Define a callback argument that will be passed through event handler when registry change will be occurred.
public class ThemeChangedArgument
{
public WindowsTheme WindowsTheme { set; get; }
}
Now start watching theme changed from OnStartup method of App.xaml.cs.
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)
{
}
}
}
Note: We already merged related style resource from ThemeWatcher class with the method MergeThemeDictionaries() due to theme changed event fired. You can updated it from here also according to your need.
Call below method to update the UI after resource changes due to runtime.
private void InvalidedMainWindow()
{
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.InvokeAsync(() => InvalidedMainWindow());
return;
}
App.Current.MainWindow.UpdateLayout();
}
Upvotes: 2