Detecting Dark and Light themes in a WPF application

 
 
  • Gérald Barré

When an application supports both light and dark themes, it is important to detect the current system theme and update the application accordingly. This post covers two approaches for detecting whether a WPF application should use a light or dark theme.

#Method 1: Using the registry and the Windows message loop

Windows stores the current theme in the registry. The following code reads it:

C#
private static bool IsLightTheme()
{
    using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
    var value = key?.GetValue("AppsUseLightTheme");
    return value is int i && i > 0;
}

Then, you can use the following code to detect when the theme changes:

C#
public partial class MainWindow : Window
{
    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);

        // Detect when the theme changed
        HwndSource source = (HwndSource)PresentationSource.FromVisual(this);
        source.AddHook((IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) =>
        {
            const int WM_SETTINGCHANGE = 0x001A;
            if (msg == WM_SETTINGCHANGE)
            {
                if (wParam == IntPtr.Zero && Marshal.PtrToStringUni(lParam) == "ImmersiveColorSet")
                {
                    var isLightTheme = IsLightTheme();
                    // TODO Change app theme accordingly
                }
            }

            return IntPtr.Zero;
        });
    }
}

#Method 2: Using the UISettings class

First, you need to update the TargetFramework to specify the minimum Windows version required. This way, the Windows API will be accessible from your code.

csproj (MSBuild project file)
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net7.0-windows10.0.17763.0</TargetFramework>
  </PropertyGroup>
</Project>

Then, you can use UISettings to get the current theme:

C#
var settings = new UISettings();
var isLightTheme = IsColorLight(settings.GetColorValue(UIColorType.Background));

settings.ColorValuesChanged += (sender, args) =>
{
    var isLightTheme = IsColorLight(settings.GetColorValue(UIColorType.Background));
    // TODO Change app theme accordingly
};

// From https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes?WT.mc_id=DT-MVP-5003978#know-when-dark-mode-is-enabled
static bool IsColorLight(Color clr)
    => ((5 * clr.G) + (2 * clr.R) + clr.B) > (8 * 128);

#Updating the title bar to a dark theme

The title bar is part of the application window, so you need to update its style when switching themes. The following code applies the dark mode attribute using the DWM API:

C#
[DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

private const int DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19;
private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;

private static bool UseImmersiveDarkMode(IntPtr handle, bool enabled)
{
    if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17763))
    {
        var attribute = DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1;
        if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 18985))
        {
            attribute = DWMWA_USE_IMMERSIVE_DARK_MODE;
        }

        int useImmersiveDarkMode = enabled ? 1 : 0;
        return DwmSetWindowAttribute(handle, attribute, ref useImmersiveDarkMode, sizeof(int)) == 0;
    }

    return false;
}

You can use the previous code in OnSourceInitialized to update the window style:

C#
protected override void OnSourceInitialized(EventArgs e)
{
    base.OnSourceInitialized(e);

    var isLightTheme = IsLightTheme();
    HwndSource source = (HwndSource)PresentationSource.FromVisual(this);
    UseImmersiveDarkMode(source.Handle, !isLightTheme);
}

Title bar in light theme

Title bar in dark theme

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?