22

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
  • 700,868
  • 160
  • 1,392
  • 1,356
user1868880
  • 221
  • 1
  • 2
  • 3
  • 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?](http://stackoverflow.com/questions/3560890/vista-7-how-to-get-glass-color) – BoltClock Dec 01 '12 at 17:51
  • Thank you very much for the information. I used the registry trick and it works great. – user1868880 Dec 01 '12 at 20:47
  • There is a better and supported (in the meaning of not relying on undocumented APIs) answer here: https://stackoverflow.com/questions/50840395/c-sharp-console-get-windows-10-accent-color – Xam Jul 14 '20 at 22:58

2 Answers2

16

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.

Community
  • 1
  • 1
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • 2
    And this is my first Windows 8 answer posted from... Windows 8. Not too shabby... – BoltClock Dec 02 '12 at 14:34
  • 2
    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: https://code.msdn.microsoft.com/windowsdesktop/How-to-get-Accent-brush-6652403f – Cody Gray - on strike Jun 12 '16 at 17:01
16

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();
        }
    }
}
Bill Menees
  • 2,124
  • 24
  • 25
  • The SystemParameters.WindowGlassColor property in the .NET 4.5 framework just wraps DwmGetColorizationColor – Giles Bathgate Dec 11 '14 at 20:18
  • 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? – Nicke Manarin Feb 29 '16 at 18:34
  • 1
    Sadly, SystemParameters.WindowGlassBrush will not return the correct accent color, at least it is different on my machine. I use this code: https://gist.github.com/paulcbetts/3c6aedc9f0cd39a77c37 – Robert Muehsig Apr 26 '16 at 21:13
  • As @RobertMuehsig suggests, this will not get the correct colour if "Automatically pick an accent color from my background" is enabled – Aidan Fitzpatrick May 29 '21 at 08:18