using WPFluent.Controls; using WPFluent.Interop; namespace WPFluent.Appearance; /// /// Automatically updates the application background if the system theme or color is changed. settings work globally and cannot be changed for each . /// /// /// SystemThemeWatcher.Watch(this as System.Windows.Window); SystemThemeWatcher.UnWatch(this as /// System.Windows.Window); SystemThemeWatcher.Watch( /// _serviceProvider.GetRequiredService<MainWindow>() ); /// public static class SystemThemeWatcher { private static readonly List _observedWindows = []; private static void ObserveLoadedHandle(ObservedWindow observedWindow) { if(!observedWindow.HasHook) { System.Diagnostics.Debug .WriteLine( $"INFO | {observedWindow.Handle} ({observedWindow.RootVisual?.Title}) registered as watched window.", nameof(SystemThemeWatcher)); observedWindow.AddHook(WndProc); _observedWindows.Add(observedWindow); } } private static void ObserveLoadedWindow(Window window, WindowBackdropType backdrop, bool updateAccents) { IntPtr hWnd = (hWnd = new WindowInteropHelper(window).Handle) == IntPtr.Zero ? throw new InvalidOperationException("Could not get window handle.") : hWnd; if(hWnd == IntPtr.Zero) { throw new InvalidOperationException("Window handle cannot be empty"); } ObserveLoadedHandle(new ObservedWindow(hWnd, backdrop, updateAccents)); } private static void ObserveWindowWhenLoaded(Window window, WindowBackdropType backdrop, bool updateAccents) { window.Loaded += (_, _) => { IntPtr hWnd = (hWnd = new WindowInteropHelper(window).Handle) == IntPtr.Zero ? throw new InvalidOperationException("Could not get window handle.") : hWnd; if(hWnd == IntPtr.Zero) { throw new InvalidOperationException("Window handle cannot be empty"); } ObserveLoadedHandle(new ObservedWindow(hWnd, backdrop, updateAccents)); }; } private static void UpdateObservedWindow(nint hWnd) { if(!UnsafeNativeMethods.IsValidWindow(hWnd)) { return; } ObservedWindow? observedWindow = _observedWindows.FirstOrDefault(x => x.Handle == hWnd); if(observedWindow is null) { return; } ApplicationThemeManager.ApplySystemTheme(observedWindow.UpdateAccents); ApplicationTheme currentApplicationTheme = ApplicationThemeManager.GetAppTheme(); System.Diagnostics.Debug .WriteLine( $"INFO | {observedWindow.Handle} ({observedWindow.RootVisual?.Title}) triggered the application theme change to {ApplicationThemeManager.GetSystemTheme()}.", nameof(SystemThemeWatcher)); if(observedWindow.RootVisual is not null) { WindowBackgroundManager.UpdateBackground( observedWindow.RootVisual, currentApplicationTheme, observedWindow.Backdrop); } } /// /// Listens to system messages on the application windows. /// private static IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if(msg == (int)User32.WM.WININICHANGE) { UpdateObservedWindow(hWnd); } return IntPtr.Zero; } /// /// Unwatches the window and removes the hook to receive messages from the system. /// public static void UnWatch(Window? window) { if(window is null) { return; } if(!window.IsLoaded) { throw new InvalidOperationException("You cannot unwatch a window that is not yet loaded."); } IntPtr hWnd = (hWnd = new WindowInteropHelper(window).Handle) == IntPtr.Zero ? throw new InvalidOperationException("Could not get window handle.") : hWnd; ObservedWindow? observedWindow = _observedWindows.FirstOrDefault(x => x.Handle == hWnd); if(observedWindow is null) { return; } observedWindow.RemoveHook(WndProc); _ = _observedWindows.Remove(observedWindow); } /// /// Watches the and applies the background effect and theme according to the system theme. /// /// The window that will be updated. /// Background effect to be applied when changing the theme. /// If , the accents will be updated when the change is detected. public static void Watch( Window? window, WindowBackdropType backdrop = WindowBackdropType.Mica, bool updateAccents = true) { if(window is null) { return; } if(window.IsLoaded) { ObserveLoadedWindow(window, backdrop, updateAccents); } else { ObserveWindowWhenLoaded(window, backdrop, updateAccents); } if(_observedWindows.Count == 0) { System.Diagnostics.Debug .WriteLine( $"INFO | {typeof(SystemThemeWatcher)} changed the app theme on initialization.", nameof(SystemThemeWatcher)); ApplicationThemeManager.ApplySystemTheme(updateAccents); } } }