using System.Windows; using Microsoft.Win32; using Microsoft.Extensions.Logging; namespace SharepointToolbox.Services; public enum ThemeMode { System, Light, Dark } /// /// Swaps the merged palette dictionary at runtime so all DynamicResource brush lookups retint live. /// "System" mode reads HKCU AppsUseLightTheme (0 = dark, 1 = light) and subscribes to system theme changes. /// public class ThemeManager { private const string LightPaletteSource = "pack://application:,,,/Themes/LightPalette.xaml"; private const string DarkPaletteSource = "pack://application:,,,/Themes/DarkPalette.xaml"; private const string PersonalizeKey = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"; private readonly ILogger _logger; private ThemeMode _mode = ThemeMode.System; private bool _systemSubscribed; public event EventHandler? ThemeChanged; public ThemeMode Mode => _mode; public bool IsDarkActive { get; private set; } public ThemeManager(ILogger logger) { _logger = logger; } public void ApplyMode(ThemeMode mode) { _mode = mode; bool dark = ResolveDark(mode); ApplyPalette(dark); EnsureSystemSubscription(mode); } public void ApplyFromString(string? value) { var mode = (value ?? "System") switch { "Light" => ThemeMode.Light, "Dark" => ThemeMode.Dark, _ => ThemeMode.System, }; ApplyMode(mode); } private bool ResolveDark(ThemeMode mode) => mode switch { ThemeMode.Light => false, ThemeMode.Dark => true, _ => ReadSystemPrefersDark(), }; private bool ReadSystemPrefersDark() { try { using var key = Registry.CurrentUser.OpenSubKey(PersonalizeKey); if (key?.GetValue("AppsUseLightTheme") is int v) return v == 0; } catch (Exception ex) { _logger.LogWarning(ex, "Failed to read system theme preference, defaulting to light"); } return false; } private void ApplyPalette(bool dark) { var app = Application.Current; if (app is null) return; var newPalette = new ResourceDictionary { Source = new Uri(dark ? DarkPaletteSource : LightPaletteSource, UriKind.Absolute) }; var dicts = app.Resources.MergedDictionaries; int replaced = -1; for (int i = 0; i < dicts.Count; i++) { var src = dicts[i].Source?.OriginalString ?? string.Empty; if (src.EndsWith("LightPalette.xaml", StringComparison.OrdinalIgnoreCase) || src.EndsWith("DarkPalette.xaml", StringComparison.OrdinalIgnoreCase)) { replaced = i; break; } } if (replaced >= 0) dicts[replaced] = newPalette; else dicts.Insert(0, newPalette); IsDarkActive = dark; ThemeChanged?.Invoke(this, EventArgs.Empty); } private void EnsureSystemSubscription(ThemeMode mode) { if (mode == ThemeMode.System && !_systemSubscribed) { SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged; _systemSubscribed = true; } else if (mode != ThemeMode.System && _systemSubscribed) { SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged; _systemSubscribed = false; } } private void OnUserPreferenceChanged(object? sender, UserPreferenceChangedEventArgs e) { if (e.Category != UserPreferenceCategory.General) return; if (_mode != ThemeMode.System) return; var app = Application.Current; if (app is null) return; app.Dispatcher.BeginInvoke(new Action(() => { bool dark = ReadSystemPrefersDark(); if (dark != IsDarkActive) ApplyPalette(dark); })); } }