WindowThemeSynchronizer.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using Microsoft.CmdPal.Core.Common.Services; 6 using Microsoft.CmdPal.UI.ViewModels.Services; 7 using Microsoft.UI.Xaml; 8 9 namespace Microsoft.CmdPal.UI.Services; 10 11 /// <summary> 12 /// Synchronizes a window's theme with <see cref="IThemeService"/>. 13 /// </summary> 14 internal sealed partial class WindowThemeSynchronizer : IDisposable 15 { 16 private readonly IThemeService _themeService; 17 private readonly Window _window; 18 19 /// <summary> 20 /// Initializes a new instance of the <see cref="WindowThemeSynchronizer"/> class and subscribes to theme changes. 21 /// </summary> 22 /// <param name="themeService">The theme service to monitor for changes.</param> 23 /// <param name="window">The window to synchronize.</param> 24 /// <exception cref="ArgumentNullException">Thrown when <paramref name="themeService"/> or <paramref name="window"/> is null.</exception> 25 public WindowThemeSynchronizer(IThemeService themeService, Window window) 26 { 27 _themeService = themeService ?? throw new ArgumentNullException(nameof(themeService)); 28 _window = window ?? throw new ArgumentNullException(nameof(window)); 29 _themeService.ThemeChanged += ThemeServiceOnThemeChanged; 30 } 31 32 /// <summary> 33 /// Unsubscribes from theme change events. 34 /// </summary> 35 public void Dispose() 36 { 37 _themeService.ThemeChanged -= ThemeServiceOnThemeChanged; 38 } 39 40 /// <summary> 41 /// Applies the current theme to the window when theme changes occur. 42 /// </summary> 43 private void ThemeServiceOnThemeChanged(object? sender, ThemeChangedEventArgs e) 44 { 45 if (_window.Content is not FrameworkElement fe) 46 { 47 return; 48 } 49 50 var dispatcherQueue = fe.DispatcherQueue; 51 52 if (dispatcherQueue is not null && dispatcherQueue.HasThreadAccess) 53 { 54 ApplyRequestedTheme(fe); 55 } 56 else 57 { 58 dispatcherQueue?.TryEnqueue(() => ApplyRequestedTheme(fe)); 59 } 60 } 61 62 private void ApplyRequestedTheme(FrameworkElement fe) 63 { 64 // LOAD BEARING: Changing the RequestedTheme to Dark then Light then target forces 65 // a refresh of the theme. 66 fe.RequestedTheme = ElementTheme.Dark; 67 fe.RequestedTheme = ElementTheme.Light; 68 fe.RequestedTheme = _themeService.Current.Theme; 69 } 70 }