DrawingBrush.GeometryDrawing with dynamic Brush property

1.1k Views Asked by At

I'm trying to dynamically change the Brush of a GeometryDrawing inside a DrawingBrush by setting a DynamicResource.

<DrawingBrush x:Key="Vector.Close" Stretch="Uniform" AlignmentX="Center" AlignmentY="Center">
    <DrawingBrush.Drawing>
        <DrawingGroup>
            <GeometryDrawing Brush="{DynamicResource Element.Glyph.Strong}" Geometry="F1 M 38.199,40 L 0,1.801 L 1.801,0 L 40,38.199 L 38.199,40 Z"/>
            <GeometryDrawing Brush="{DynamicResource Element.Glyph.Strong}" Geometry="F1 M 1.801,40 L 0,38.199 L 38.199,0 L 40,1.801 L 1.801,40 Z"/>
        </DrawingGroup>
    </DrawingBrush.Drawing>
</DrawingBrush>

When I change the color scheme of my app, I shift the ordering of my ResourceDictionary by Remove() and Insert() in the MergedDictionary:

public static void SelectTheme(string id = "Light")
{
    var theme = Application.Current.Resources.MergedDictionaries.FirstOrDefault(f => f.Source != null && f.Source.ToString().EndsWith($"Colors/{id}.xaml"));

    if (theme == null)
    {
        theme = Application.Current.Resources.MergedDictionaries.FirstOrDefault(f => f.Source != null && f.Source.ToString().EndsWith("Colors/Light.xaml"));
        UserSettings.All.MainTheme = AppTheme.Light;
    }

    Application.Current.Resources.MergedDictionaries.Remove(theme);
    Application.Current.Resources.MergedDictionaries.Add(theme);
}

For example, inside the Light.xaml, I have a SolidColorBrush:

<SolidColorBrush x:Key="Element.Glyph.Strong" Color="#FF231F20"/>

Inside the App.xaml, I import both my resource dictionaries (the order does not matter, since the resources will be shifted to the last position):

<!--Themes-->
<ResourceDictionary Source="/Themes/Colors/Dark.xaml"/>
<ResourceDictionary Source="/Themes/Colors/Light.xaml"/>

<ResourceDictionary Source="/Resources/Vectors.xaml"/>

<ResourceDictionary Source="/Themes/Button.xaml"/>

The vector is used as an icon on a custom Button. Inside the button there's a Border with a sizing and alignment, and the vector is used as the Background.

Button definition and usage (Icon is a Brush DependencyProperty):

public class ExtendedButton : Button
{
    public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(Brush), typeof(ExtendedButton));

    public Brush Icon
    {
        get => (Brush)GetValue(IconProperty);
        set => SetCurrentValue(IconProperty, value);
    }

    //Ctor and other stuff.
}

<n:ExtendedButton Style="{DynamicResource Style.Button.NoText}" 
                  Icon="{DynamicResource Vector.Close}" Width="30" Padding="6"/>

I was setting the Icon as a StaticResource, but I changed to DynamicResource before asking in here, as a test. But it still didn't work.

Here's the simplified Style for the button:

<!--Button • No Border • No Text-->
<Style TargetType="{x:Type n:ExtendedButton}" BasedOn="{StaticResource {x:Type Button}}" x:Key="Style.Button.NoText">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type n:ExtendedButton}">
                <Border x:Name="MainBorder" MinHeight="{TemplateBinding MinHeight}" Background="{TemplateBinding Background}">
                    <Grid x:Name="InnerGrid">
                        <Border Background="{TemplateBinding Icon}" Margin="{TemplateBinding Padding}" Opacity="{DynamicResource Element.Opacity}"
                                Height="14" Width="14" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>

    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" Value="{DynamicResource Brush.Button.Background.Hover}"/>
        </Trigger>
        <Trigger Property="IsPressed" Value="True">
            <Setter Property="Background" Value="{DynamicResource Brush.Button.Background.Pressed}"/>
        </Trigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="Opacity" Value="0.7"/>
        </Trigger>
    </Style.Triggers>
</Style>

The rest of the app works, when changing the theme, everything else changes. The Element.Glyph.Strong inside the GeometryDrawing does not update.

Only when closing the app and opening again will display the vector with the correct color, since it loads with the new theme.

I guessed that the Brush property, as a DependencyProperty would update when the resource updated too.

I wonder how could I set the Brush with a dynamic color, without having to work with code behind?

1

There are 1 best solutions below

4
Peter Boone On

I'm not sure exactly how you are changing themes, but if other components are changing then I am assuming you are doing that correctly.

I think the key is to make sure you are using Background="{DynamicResource Vector.Close}" instead of Background="{StaticResource Vector.Close}".

Dictionary1.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApp1">

    <SolidColorBrush x:Key="Element.Glyph.Strong" Color="Black" />

</ResourceDictionary>

Dictionary2.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApp1">

    <SolidColorBrush x:Key="Element.Glyph.Strong" Color="Yellow" />

</ResourceDictionary>

Dictionary3.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApp1">
    
    <DrawingBrush x:Key="Vector.Close" Stretch="Uniform" AlignmentX="Center" AlignmentY="Center">
        <DrawingBrush.Drawing>
            <DrawingGroup>
                <GeometryDrawing Brush="{DynamicResource Element.Glyph.Strong}" Geometry="F1 M 38.199,40 L 0,1.801 L 1.801,0 L 40,38.199 L 38.199,40 Z"/>
                <GeometryDrawing Brush="{DynamicResource Element.Glyph.Strong}" Geometry="F1 M 1.801,40 L 0,38.199 L 38.199,0 L 40,1.801 L 1.801,40 Z"/>
            </DrawingGroup>
        </DrawingBrush.Drawing>
    </DrawingBrush>
</ResourceDictionary>

MainWindow.xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Grid>
        <Button Background="{DynamicResource Vector.Close}" Width="40" Height="40" VerticalAlignment="Center" Click="ChangeTheme_Click" HorizontalAlignment="Center"/>
    </Grid>
</Window>

MainWindow.xaml.cs

// stuff up here...

public string currentDict = "Dictionary1.xaml";

private void ChangeTheme_Click(object sender, RoutedEventArgs e)
{
    if (currentDict == "Dictionary1.xaml")
    {
        currentDict = "Dictionary2.xaml";
    } else
    {
        currentDict = "Dictionary1.xaml";
    }
    var app = (App)Application.Current;
    app.ChangeTheme(new Uri(currentDict, UriKind.RelativeOrAbsolute));
}

App.xaml

<Application x:Class="WpfApp1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp1"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Dictionary1.xaml" />
                <ResourceDictionary Source="Dictionary3.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

App.xaml.cs

public void ChangeTheme(Uri uri)
{
    var dict = new ResourceDictionary() { Source = uri };
    var dict2 = new ResourceDictionary() { Source = new Uri("Dictionary3.xaml", UriKind.RelativeOrAbsolute) };
    Resources.MergedDictionaries.Clear();
    Resources.MergedDictionaries.Add(dict);
    Resources.MergedDictionaries.Add(dict2);
}

Obviously this is a little messy and hacky, but it works:

Button with black x

Button with yellow x


Edit:

I am not able to reproduce even when using the custom styling. Here it is after switching:

two buttons, both yellow