Skip to content

Commit

Permalink
Fix WPF 'System' theme not syncing with the Windows app theme after s…
Browse files Browse the repository at this point in the history
…tartup
  • Loading branch information
ScrubN committed Jul 27, 2023
1 parent 7e0ab9d commit f955305
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 57 deletions.
78 changes: 42 additions & 36 deletions TwitchDownloaderWPF/Services/ThemeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Windows;
using System.Windows.Media;
using System.Xml.Serialization;
using HandyControl.Tools;
using TwitchDownloaderWPF.Models;
using TwitchDownloaderWPF.Properties;

Expand Down Expand Up @@ -39,7 +40,7 @@ public ThemeService(App app, WindowsThemeService windowsThemeService)
if (!Settings.Default.GuiTheme.Equals("System", StringComparison.OrdinalIgnoreCase) && !File.Exists(Path.Combine("Themes", $"{Settings.Default.GuiTheme}.xaml")))
{
MessageBox.Show(
Translations.Strings.ThemeNotFoundMessage.Replace("{theme}", Settings.Default.GuiTheme + ".xaml"),
Translations.Strings.ThemeNotFoundMessage.Replace("{theme}", $"{Settings.Default.GuiTheme}.xaml"),
Translations.Strings.ThemeNotFound,
MessageBoxButton.OK,
MessageBoxImage.Information);
Expand All @@ -51,26 +52,26 @@ public ThemeService(App app, WindowsThemeService windowsThemeService)

private void WindowsThemeChanged(object sender, string newWindowsTheme)
{
if (_wpfApplication.Windows.Count > 0)
if (_wpfApplication.Windows.Count == 0)
return;

if (Settings.Default.GuiTheme.Equals("System", StringComparison.OrdinalIgnoreCase))
{
if (Settings.Default.GuiTheme.Equals("System", StringComparison.OrdinalIgnoreCase))
{
ChangeAppTheme();
}
ChangeAppTheme();
}
}

public void ChangeAppTheme()
{
string newTheme = Settings.Default.GuiTheme;
var newTheme = Settings.Default.GuiTheme;
if (newTheme.Equals("System", StringComparison.OrdinalIgnoreCase))
{
newTheme = WindowsThemeService.GetWindowsTheme();
}
ChangeThemePath(_wpfApplication, newTheme);
ChangeThemePath(newTheme);

SkinType newSkin = _darkHandyControl ? SkinType.Dark : SkinType.Default;
SetHandyControlTheme(newSkin, _wpfApplication);
var newSkin = _darkHandyControl ? SkinType.Dark : SkinType.Default;
SetHandyControlTheme(newSkin);

if (_wpfApplication.Windows.Count > 0)
{
Expand All @@ -92,51 +93,56 @@ public void SetTitleBarTheme(WindowCollection windows)
NativeFunctions.SetWindowAttribute(windowHandle, TITLEBAR_THEME_ATTRIBUTE, ref _darkAppTitleBar, Marshal.SizeOf(_darkAppTitleBar));
}

Window _wnd = new()
Window wnd = new()
{
SizeToContent = SizeToContent.WidthAndHeight
};
_wnd.Show();
_wnd.Close();
wnd.Show();
wnd.Close();
// Dark title bar is a bit buggy, requires window resize or focus change to fully apply
// Win11 might not have this issue but Win10 does so please leave this
}

private void ChangeThemePath(App app, string newTheme)
private void ChangeThemePath(string newTheme)
{
string[] themeFiles = Directory.GetFiles("Themes", "*.xaml");
string newThemeString = $"{Path.Combine("Themes", newTheme)}.xaml";
var themeFiles = Directory.GetFiles("Themes", "*.xaml");
var newThemeString = Path.Combine("Themes", $"{newTheme}.xaml");

foreach (string themeFile in themeFiles)
foreach (var themeFile in themeFiles)
{
if (newThemeString.Equals(themeFile, StringComparison.OrdinalIgnoreCase))
if (!newThemeString.Equals(themeFile, StringComparison.OrdinalIgnoreCase))
continue;

var xmlReader = new XmlSerializer(typeof(ResourceDictionaryModel));
using var streamReader = new StreamReader(themeFile);
var themeValues = (ResourceDictionaryModel)xmlReader.Deserialize(streamReader)!;

foreach (var solidBrush in themeValues.SolidColorBrush)
{
var xmlReader = new XmlSerializer(typeof(ResourceDictionaryModel));
using var streamReader = new StreamReader(themeFile);
var themeValues = (ResourceDictionaryModel)xmlReader.Deserialize(streamReader);
_wpfApplication.Resources[solidBrush.Key] = (SolidColorBrush)new BrushConverter().ConvertFrom(solidBrush.Color);
}

foreach (SolidColorBrushModel solidBrush in themeValues.SolidColorBrush)
{
app.Resources[solidBrush.Key] = (SolidColorBrush)new BrushConverter().ConvertFrom(solidBrush.Color);
}
foreach (BooleanModel boolean in themeValues.Boolean)
foreach (var boolean in themeValues.Boolean)
{
switch (boolean.Key)
{
switch (boolean.Key)
{
case "DarkTitleBar": _darkAppTitleBar = boolean.Value; break;
case "DarkHandyControl": _darkHandyControl = boolean.Value; break;
default: break;
}
case "DarkTitleBar":
_darkAppTitleBar = boolean.Value;
break;
case "DarkHandyControl":
_darkHandyControl = boolean.Value;
break;
}
return;
}

return;
}
}

private void SetHandyControlTheme(SkinType newSkin, App app)
private void SetHandyControlTheme(SkinType newSkin)
{
app.Resources.MergedDictionaries[0].Source = new Uri($"pack://application:,,,/HandyControl;component/Themes/Skin{newSkin}.xaml", UriKind.Absolute);
app.Resources.MergedDictionaries[1].Source = new Uri($"pack://application:,,,/HandyControl;component/Themes/Theme.xaml", UriKind.Absolute);
_wpfApplication.Resources.MergedDictionaries[0].Source = new Uri($"pack://application:,,,/HandyControl;component/Themes/Skin{newSkin}.xaml", UriKind.Absolute);
_wpfApplication.Resources.MergedDictionaries[1].Source = new Uri($"pack://application:,,,/HandyControl;component/Themes/Theme.xaml", UriKind.Absolute);
}
}
}
39 changes: 18 additions & 21 deletions TwitchDownloaderWPF/Services/WindowsThemeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,43 @@ public class WindowsThemeService : ManagementEventWatcher
{
public event EventHandler<string> ThemeChanged;

private const string REGISTRY_KEY_PATH = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
private const string REGISTRY_KEY_PATH = @"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
private const string REGISTRY_KEY_NAME = "AppsUseLightTheme";
private const string LIGHT_THEME = "Light";
private const string DARK_THEME = "Dark";

public WindowsThemeService() : base()
public WindowsThemeService()
{
var currentUser = WindowsIdentity.GetCurrent();
var windowsQuery = $"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_USERS' AND KeyPath = " +
$"'{currentUser.User.Value}\\{REGISTRY_KEY_PATH}' AND ValueName = '{REGISTRY_KEY_NAME}'";
windowsQuery = windowsQuery.Replace("\\", @"\\");
var currentUser = WindowsIdentity.GetCurrent().User;

if (currentUser is null)
return;

var windowsQuery = $@"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_USERS' AND KeyPath = '{currentUser.Value}\\{REGISTRY_KEY_PATH}' AND ValueName = '{REGISTRY_KEY_NAME}'";

Query = new EventQuery(windowsQuery);
EventArrived += WindowsThemeService_EventArrived;

Start();
}

private void WindowsThemeService_EventArrived(object sender, EventArrivedEventArgs e)
{
var newWindowsTheme = GetWindowsTheme();
ThemeChanged?.Invoke(this, newWindowsTheme);
App.AppSingleton.Dispatcher.BeginInvoke(() => ThemeChanged?.Invoke(this, newWindowsTheme));
}

public static string GetWindowsTheme()
{
try
{
using var key = Registry.CurrentUser.OpenSubKey(REGISTRY_KEY_PATH);
if (!(key.GetValue(REGISTRY_KEY_NAME) is int windowsThemeValue))
{
return LIGHT_THEME;
}

return windowsThemeValue > 0
? LIGHT_THEME
: DARK_THEME;
}
catch (NullReferenceException) // Usually means key does not exist
using var key = Registry.CurrentUser.OpenSubKey(REGISTRY_KEY_PATH);
if (key?.GetValue(REGISTRY_KEY_NAME) is not int windowsThemeValue)
{
return LIGHT_THEME;
}

return windowsThemeValue > 0
? LIGHT_THEME
: DARK_THEME;
}
}
}
}

0 comments on commit f955305

Please sign in to comment.