Get the active color of Windows 8 automatic color theme

10,856

Solution 1

Yes, it's possible. However be warned: this encompasses quite a bit of Win32 interop (this means P/Invokes into native DLLs from managed code), and is only doable with certain undocumented APIs. Although, the only undocumented features involved are for obtaining the window color scheme (or as the DWM calls it, the window colorization color), which is covered in this other question:

Vista/7: How to get glass color?

In my own project, I make use of a call to DwmGetColorizationParameters():

internal static class NativeMethods
{
    [DllImport("dwmapi.dll", EntryPoint="#127")]
    internal static extern void DwmGetColorizationParameters(ref DWMCOLORIZATIONPARAMS params);
}

public struct DWMCOLORIZATIONPARAMS
{
    public uint ColorizationColor, 
        ColorizationAfterglow, 
        ColorizationColorBalance, 
        ColorizationAfterglowBalance, 
        ColorizationBlurBalance, 
        ColorizationGlassReflectionIntensity, 
        ColorizationOpaqueBlend;
}

I've tested it and it works great with Windows 8 and its automatic window colorization feature. As suggested in the link above, you can look in the registry for the color values as an alternative to a P/Invoke, but I haven't tested that method, and as stated these are undocumented and not guaranteed to be stable.

Once you obtain the color for drawing your gradient brushes, the brushes won't update when the window color scheme changes, whether manually or automatically by Windows. Thankfully, Windows broadcasts the WM_DWMCOLORIZATIONCOLORCHANGED window message whenever that happens, so you simply need to listen for that message and update your colors whenever it's sent. You do this by hooking onto the window procedure (WndProc()).

The value of WM_DWMCOLORIZATIONCOLORCHANGED is 0x320; you'll want to define that as a constant somewhere so you can use it in code.

Also, unlike WinForms, WPF windows don't have a virtual WndProc() method to override, so you have to create and hook one in as a delegate to their associated window handles (HWNDs).

Taking some example code from these answers of mine:

We have:

const int WM_DWMCOLORIZATIONCOLORCHANGED = 0x320;

private IntPtr hwnd;
private HwndSource hsource;

private void Window_SourceInitialized(object sender, EventArgs e)
{
    if ((hwnd = new WindowInteropHelper(this).Handle) == IntPtr.Zero)
    {
        throw new InvalidOperationException("Could not get window handle.");
    }

    hsource = HwndSource.FromHwnd(hwnd);
    hsource.AddHook(WndProc);
}

private static Color GetWindowColorizationColor(bool opaque)
{
    var params = NativeMethods.DwmGetColorizationParameters();

    return Color.FromArgb(
        (byte)(opaque ? 255 : params.ColorizationColor >> 24), 
        (byte)(params.ColorizationColor >> 16), 
        (byte)(params.ColorizationColor >> 8), 
        (byte) params.ColorizationColor
    );
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case WM_DWMCOLORIZATIONCOLORCHANGED:

            /* 
             * Update gradient brushes with new color information from
             * NativeMethods.DwmGetColorizationParams() or the registry.
             */

            return IntPtr.Zero;

        default:
            return IntPtr.Zero;
    }
}

When Windows transitions the color change, WM_DWMCOLORIZATIONCOLORCHANGED is dispatched at every keyframe in the transition, so you'll receive numerous messages at a short burst during the color change. This is normal; just update your gradient brushes as usual and you'll notice that when Windows transitions the window color scheme, your gradients will transition smoothly along with the rest of the window frames as well.

Remember that you may need to account for situations where the DWM isn't available, such as when running on Windows XP, or when running on Windows Vista or later with desktop composition disabled. You'll also want to ensure you don't overuse this, or you may incur a significant performance hit and slow down your app.

Solution 2

This can be done in .NET 4.5 and later without P/Invokes. The SystemParameters class now has static WindowGlassBrush and WindowGlassColor properties along with a StaticPropertyChanged event.

From XAML, you can bind to the WindowGlassBrush property like:

<Grid Background="{x:Static SystemParameters.WindowGlassBrush}">

However, with this assignment the Background color won't get updated automatically when Windows changes its colors. Unfortunately, SystemParameters does not provide WindowGlassBrushKey or WindowGlassColorKey properties to use as ResourceKeys with DynamicResource, so getting change notifications requires code behind to handle the StaticPropertyChanged event.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        this.InitializeComponent();
        SystemParameters.StaticPropertyChanged += this.SystemParameters_StaticPropertyChanged;

        // Call this if you haven't set Background in XAML.
        this.SetBackgroundColor();
    }

    protected override void OnClosed(EventArgs e)
    {
        SystemParameters.StaticPropertyChanged -= this.SystemParameters_StaticPropertyChanged;
        base.OnClosed(e);
    }

    private void SetBackgroundColor()
    {
        this.Background = SystemParameters.WindowGlassBrush;
    }

    private void SystemParameters_StaticPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "WindowGlassBrush")
        {
            this.SetBackgroundColor();
        }
    }
}
Share:
10,856

Related videos on Youtube

user1868880
Author by

user1868880

Updated on June 19, 2022

Comments

  • user1868880
    user1868880 almost 2 years

    In Windows 8, I have set the color scheme to automatic and configured my wallpaper to change after x minutes. The color scheme changes according to the active wallpaper.

    I'm developing a WPF application and would like to have my gradients change when Windows changes the color scheme to match the current wallpaper.

    Is there a way get the current/actual color scheme and be notified of the change in C#?

    • BoltClock
      BoltClock over 11 years
      This encompasses quite a bit of interop and is only doable with certain undocumented APIs. In fact, I've done this with my own project for Windows Vista/7, and tested it successfully on Windows 8. I can try to put an answer together based on that, but I'll need a while. See also: Vista/7: How to get glass color?
    • user1868880
      user1868880 over 11 years
      Thank you very much for the information. I used the registry trick and it works great.
    • Xam
      Xam almost 4 years
      There is a better and supported (in the meaning of not relying on undocumented APIs) answer here: stackoverflow.com/questions/50840395/…
  • BoltClock
    BoltClock over 11 years
    And this is my first Windows 8 answer posted from... Windows 8. Not too shabby...
  • Giles Bathgate
    Giles Bathgate over 9 years
    The SystemParameters.WindowGlassColor property in the .NET 4.5 framework just wraps DwmGetColorizationColor
  • Nicke Manarin
    Nicke Manarin about 8 years
    I'm using this property inside a template, is there any way to update the template to replace the old color with a new one?
  • Robert Muehsig
    Robert Muehsig almost 8 years
    Sadly, SystemParameters.WindowGlassBrush will not return the correct accent color, at least it is different on my machine. I use this code: gist.github.com/paulcbetts/3c6aedc9f0cd39a77c37
  • Cody Gray
    Cody Gray almost 8 years
    I haven't tested this code in particular, but the undocumented function DwmGetColorizationParameters from Windows Vista still works correctly on Windows 10 to retrieve the accent/colorization color. There are, however, more fun undocumented APIs that you can call on Windows 8: code.msdn.microsoft.com/windowsdesktop/…
  • Aidan Fitzpatrick
    Aidan Fitzpatrick almost 3 years
    As @RobertMuehsig suggests, this will not get the correct colour if "Automatically pick an accent color from my background" is enabled