Search code examples
c#wpfxamldata-bindingwindows-10

Setting Windows Accent Color as WPF Window Background through XAML and listen to Accent Color Change


I'm creating a WPF project which uses the windows 10 Accent color as background for my WPF Main Window. I was able to get the windows Accent Color using GetImmersiveUserColorSetPreference(), GetImmersiveColorTypeFromName() & GetImmersiveColorFromColorSetEx() and I was able to use it as my window background. But the problem is that i was not able to change the Background automatically when the Accent Color is Changed (I must restart to change the background).

Here is the code that I use:

AccentColors.cs

public static class AccentColors {

    private static Brush systemAccentBrush;

    static AccentColors() {
        InitializeBrushes();
    }

    public static void InitializeBrushes() {
        SystemAccentBrush = CreateBrush(GetColorByTypeName("ImmersiveSystemAccent"));
    }

    public static Color GetColorByTypeName(string name) {
        var colorSet = NativeMethods.GetImmersiveUserColorSetPreference(false, false);
        var colorType = NativeMethods.GetImmersiveColorTypeFromName(name);
        var rawColor = NativeMethods.GetImmersiveColorFromColorSetEx(colorSet, colorType, false, 0);

        var bytes = BitConverter.GetBytes(rawColor);
        return Color.FromArgb(bytes[3], bytes[0], bytes[1], bytes[2]);
    }

    private static Brush CreateBrush(Color color) {
        var brush = new SolidColorBrush(color);
        return brush;
    }

    #region Brushes
    public static Brush SystemAccentBrush {
        get {
            return systemAccentBrush;
        }
        private set {
            if (!object.Equals(systemAccentBrush, value)) {
                systemAccentBrush = value;
            }
        }
    }

    #endregion

The InitializeBrushes() function is called from WndProc WM_DWMCOLORIZATIONCOLORCHANGED which helps me to set SystemAccentBrush to the current system Accent Color and it works perfectly.But When i'm setting the SystemAccentBrush as background for a control it doesn't change based on the Accent Color Change (But the Brush Color is changing).

Here is the code that i used to set SystemAccentBrush as the background for a Grid:

<Grid x:Name="container" Background="{x:Static common:AccentColors.SystemAccentBrush}">
</Grid>

I think the problem is related to this :

{x:Static common:AccentColors.SystemAccentBrush}

So i tried setting it as Dynamic source like this:

{DynamicSource {x:Static common:AccentColors.SystemAccentBrush}}

Then the background disappears.

Is there any way to overcome this problem?


Solution

  • The x:Static markup extension is for static references and does not pick up any changes at runtime.

    References any static by-value code entity that is defined in a Common Language Specification (CLS)–compliant way.

    If you create static properties that change at runtime, you have to use a binding and implement a static equivalent of the PropertyChanged event similar to INotifyPropertyChanged. This feature is supported since WPF 4.5 and there are two different ways to implement it.

    Event Handler

    Change your property as below and create an event SystemAccentBrushChanged for your property. Raise the event in the setter of SystemAccentBrush with null and EventArgs.Empty.

    private static Brush systemAccentBrush;
    public static Brush SystemAccentBrush
    {
       get => systemAccentBrush;
       private set
       {
          if (Equals(systemAccentBrush, value))
             return;
    
          systemAccentBrush = value;
          SystemAccentBrushChanged?.Invoke(null, EventArgs.Empty);
       }
    }
    
    public static event EventHandler SystemAccentBrushChanged;
    

    Static Property Changed Event

    Change your property as below and create an event StaticPropertyChanged and raise it with the property name that was changed. This event can be used with different properties.

    private static Brush systemAccentBrush;
    
    public static Brush SystemAccentBrush
    {
       get => systemAccentBrush;
       private set
       {
          if (Equals(systemAccentBrush, value))
             return;
    
          systemAccentBrush = value;
          OnStaticPropertyChanged();
       }
    }
    
    public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
    
    private static void OnStaticPropertyChanged([CallerMemberName] string propertyName = null)
    {
       StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
    }
    

    Binding the Static Property

    In order to bind the static property to pick up the change notifications, you have to use a Binding with Path and parentheses to access the class member. If you omit Path, the XAML parser and the designer will throw and error.

    <Grid Background="{Binding Path=(common:AccentColors.SystemAccentBrush)}">
    

    A final note on this: Although static bindings with property changed notifications are supported this way, you might want to think about migrating your static class to a singleton. This way, you could make SystemAccentBrush a non-static property, implement INotifyPropertyChanged and bind the property as usual, without any special syntax or custom static events.