/ src / modules / cmdpal / Microsoft.CmdPal.UI / Services / WindowThemeSynchronizer.cs
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  }