File: System\Windows\ThemeManager.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
using Standard;
using System.Windows.Appearance;
using System.Windows.Media;
using Microsoft.Win32;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Interop;
 
namespace System.Windows;
 
internal static class ThemeManager
{
 
    #region Constructor
 
    static ThemeManager()
    {
        // TODO : Temprorary way of checking if setting Fluent theme enabled flag. Provide a property for theme switch.
        if (Application.Current != null)
        {
            foreach (ResourceDictionary mergedDictionary in Application.Current.Resources.MergedDictionaries)
            {
                if (mergedDictionary.Source != null && mergedDictionary.Source.ToString().EndsWith("Fluent.xaml"))
                {
                    _isFluentThemeEnabled = true;
                    break;
                }
            }
        }
    }
 
    #endregion
 
    #region Internal Methods
 
    internal static void InitializeFluentTheme()
    {
        if(IsFluentThemeEnabled && !_isFluentThemeInitialized)
        {
            _currentApplicationTheme = GetSystemTheme();
            _currentUseLightMode = IsSystemThemeLight();
 
            var themeColorResourceUri = GetFluentWindowThemeColorResourceUri(_currentApplicationTheme, _currentUseLightMode);
            Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary() { Source = themeColorResourceUri });
 
            DwmColorization.UpdateAccentColors();
            _isFluentThemeInitialized = true;
        }
    }
 
    /// <summary>
    ///    Apply the system theme one window.
    /// </summary>
    /// <param name="forceUpdate"></param>
    internal static void ApplySystemTheme(Window window, bool forceUpdate = false)
    {
        ApplySystemTheme(new List<Window> { window }, forceUpdate);
    }
 
    /// <summary>
    ///   Apply the system theme to a list of windows.
    ///   If windows is not provided, apply the theme to all windows in the application.
    /// </summary>
    /// <param name="window"></param>
    /// <param name="forceUpdate"></param>
    internal static void ApplySystemTheme(IEnumerable windows = null, bool forceUpdate = false)
    {
        if(windows == null)
        {
            // If windows is not provided, apply the theme to all windows in the application.
            windows = Application.Current?.Windows;
            
            if(windows == null)
            {
                return;
            }
        }
 
        string systemTheme = GetSystemTheme();
        bool useLightMode = IsSystemThemeLight();
        Color systemAccentColor = DwmColorization.GetSystemAccentColor();
        ApplyTheme(windows , systemTheme, useLightMode, systemAccentColor, forceUpdate);
    }
 
    /// <summary>
    ///  Apply the requested theme and color mode to the windows.
    ///  Checks if any update is needed before applying the changes.
    /// </summary>
    /// <param name="windows"></param>
    /// <param name="requestedTheme"></param>
    /// <param name="requestedUseLightMode"></param>
    /// <param name="requestedAccentColor"></param>
    /// <param name="forceUpdate"></param>
    private static void ApplyTheme(
        IEnumerable windows, 
        string requestedTheme, 
        bool requestedUseLightMode,
        Color requestedAccentColor, 
        bool forceUpdate = false)
    {
        if(forceUpdate || 
                requestedTheme != _currentApplicationTheme || 
                requestedUseLightMode != _currentUseLightMode ||
                DwmColorization.GetSystemAccentColor() != DwmColorization.CurrentApplicationAccentColor)
        {
            DwmColorization.UpdateAccentColors();
 
            Uri dictionaryUri = GetFluentWindowThemeColorResourceUri(requestedTheme, requestedUseLightMode);
            AddOrUpdateThemeResources(dictionaryUri);
 
            foreach(Window window in windows)
            {
                if(window == null)
                {
                    continue;
                }
                
                SetImmersiveDarkMode(window, !requestedUseLightMode);
                WindowBackdropManager.SetBackdrop(window, SystemParameters.HighContrast ? WindowBackdropType.None : WindowBackdropType.MainWindow);
            }
 
            _currentApplicationTheme = requestedTheme;
            _currentUseLightMode = requestedUseLightMode;
        }
    }
 
    /// <summary>
    ///  Set the immersive dark mode windowattribute for the window.
    /// </summary>
    /// <param name="window"></param>
    /// <param name="useDarkMode"></param>
    /// <returns></returns>
    private static bool SetImmersiveDarkMode(Window window, bool useDarkMode)
    {
        if (window == null)
        {
            return false;
        }
 
        IntPtr handle = new WindowInteropHelper(window).Handle;
 
        if (handle != IntPtr.Zero)
        {
            var dwmResult = NativeMethods.DwmSetWindowAttributeUseImmersiveDarkMode(handle, useDarkMode);
            return dwmResult == HRESULT.S_OK;
        }
 
        return false;
    }
 
    #region Helper Methods
 
    /// <summary>
    ///   Reads the CurrentTheme registry key to fetch the system theme.
    ///   This along with UseLightTheme is used to determine the theme and color mode.
    /// </summary>
    /// <returns></returns>
    internal static string GetSystemTheme()
    {
        string systemTheme = Registry.GetValue(_regThemeKeyPath,
            "CurrentTheme", null) as string ?? "aero.theme";
 
        return systemTheme;
    }
   
    /// <summary>
    ///   Reads the AppsUseLightTheme registry key to fetch the color mode.
    ///   If the key is not present, it reads the SystemUsesLightTheme key.
    /// </summary>
    /// <returns></returns>
    internal static bool IsSystemThemeLight()
    {
        var useLightTheme = Registry.GetValue(_regPersonalizeKeyPath,
            "AppsUseLightTheme", null) as int?;
 
        if (useLightTheme == null)
        {
            useLightTheme = Registry.GetValue(_regPersonalizeKeyPath,
                "SystemUsesLightTheme", null) as int?;
        }
 
        return useLightTheme != null && useLightTheme != 0;
    }
 
    /// <summary>
    ///  Update the Fluent theme resources with the values in new dictionary.
    /// </summary>
    /// <param name="dictionaryUri"></param>
    private static void AddOrUpdateThemeResources(Uri dictionaryUri)
    {
        ArgumentNullException.ThrowIfNull(dictionaryUri, nameof(dictionaryUri));
 
        var newDictionary = new ResourceDictionary() { Source = dictionaryUri };
 
        ResourceDictionary currentDictionary = Application.Current?.Resources;
        foreach (var key in newDictionary.Keys)
        {
            if (currentDictionary.Contains(key))
            {
                currentDictionary[key] = newDictionary[key];
            }
            else
            {
                currentDictionary.Add(key, newDictionary[key]);
            }
        }
    }
 
    #endregion
 
    #endregion
 
    #region Internal Properties
 
    internal static bool IsFluentThemeEnabled => _isFluentThemeEnabled;
    // TODO : Find a better way to deal with different default font sizes for different themes.
    internal static double DefaultFluentThemeFontSize => 14;
 
    #endregion
 
    #region Private Methods
 
    private static Uri GetFluentWindowThemeColorResourceUri(string systemTheme, bool useLightMode)
    {
        string themeColorFileName = useLightMode ? "light.xaml" : "dark.xaml";
 
        if(SystemParameters.HighContrast)
        {
            themeColorFileName = systemTheme switch
            {
                string s when s.Contains("hcblack") => "hcblack.xaml",
                string s when s.Contains("hcwhite") => "hcwhite.xaml",
                string s when s.Contains("hc1") => "hc1.xaml",
                _ => "hc2.xaml"
            };
        }
 
        return new Uri("pack://application:,,,/PresentationFramework.Fluent;component/Resources/Theme/" + themeColorFileName, UriKind.Absolute);
    }
 
    #endregion
 
    #region Private Members
 
    private static readonly string _regThemeKeyPath = "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes";
 
    private static readonly string _regPersonalizeKeyPath = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
 
    private static string _currentApplicationTheme;
 
    private static bool _currentUseLightMode = true;
 
    private static bool _isFluentThemeEnabled = false;
 
    private static bool _isFluentThemeInitialized = false;
 
    #endregion
}