Home > Software engineering >  .NET WPF Global dynamic styles configuration
.NET WPF Global dynamic styles configuration

Time:11-18

For several days now, I have been trying to do something like a global style configuration. How it should work?

I want to have one class to control component visual styles. For example.:

In settings page: I'm selecting my own color, and all components, that have binding to a specific variable that will store that color, change the background color or the border to it. I want to have global styles and be able to control even the color of the buttons when hovering over the mouse.

I was trying with global singleton class, and static class, but it didn't work. I also don't want to make copy of the components, because that's not the point.

Here's how I tried to do it (look at the background in style).

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:config="clr-namespace:Manager.Data.Configuration">
    
    <Style x:Key="FocusVisual">
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate>
                    <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
    <SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/>
    <SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/>
    <SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/>
    <SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/>
    <SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/>
    <SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/>
    <SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/>
    <SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/>
    <SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/>

    <ControlTemplate x:Key="ButtonControlTemplate" TargetType="{x:Type Button}">
        <Border
            x:Name="border"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            Background="{TemplateBinding Background}"
            SnapsToDevicePixels="true">
            <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="IsDefaulted" Value="true">
                <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
            </Trigger>
            <Trigger Property="IsMouseOver" Value="true">
                <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/>
                <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/>
            </Trigger>
            <Trigger Property="IsPressed" Value="true">
                <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/>
                <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/>
            </Trigger>
            <Trigger Property="IsEnabled" Value="false">
                <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/>
                <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/>
                <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
        <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
        <Setter Property="Background" Value="{Binding StylesConfig.ButtonBackgroundColor}"/>
        <Setter Property="BorderBrush" Value="Red"/>
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Padding" Value="1"/>
        <Setter Property="Template" Value="{StaticResource ButtonControlTemplate}"/>
    </Style>

</ResourceDictionary>

The StylesConfig class is just simple static class with static Brush value. I also tried to make singleton (then I typed <Setter Property="Background" Value="{Binding StylesConfig.Instance.ButtonBackgroundColor}"/>), and the class looks like:

public class StylesConfig : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private static StylesConfig instance;
    private Brush buttonBackgroundBrush;

    public static StylesConfig Instance
    {
        get
        {
            if (instance == null)
                instance = new StylesConfig();

            return instance;
        }
    }

    public Brush ButtonBackgroundColor
    {
        get
        {
            return buttonBackgroundBrush;
        }
        set
        {
            buttonBackgroundBrush = value;
            OnPropertyChanged(nameof(ButtonBackgroundColor));
        }
    }

    private StylesConfig()
    {
        ButtonBackgroundColor = new SolidColorBrush(Colors.Red);
    }

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

}

Can it be done very easily and not generate a lot of code?

CodePudding user response:

You could try keeping StylesConfig as a singleton, and at the root of your xaml declaring it with a key:

xmlns:myNamespace="clr-namespace:namespace.of.stylesconfig"

<x:Static x:Key="StylesConfig" Member="myNamespace:StylesConfig.Instance" />

Then on any button elsewhere in your xaml:

<Button Background="{Binding ButtonBackgroundColor, Source={StaticResource StylesConfig}, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />

If this does not work, it is worth looking into using DynamicResource, and switching the value under a certain resource from a resource manager class using Application.Current.FindResource("Dynamic_Resource_Key"). A simple example of this is:

<SolidColorBrush Key="My_Resource" Color="Yellow" />

<Button Background="{DynamicResource My_Resource}" />

Then, from elsewhere in your code at any time:

if(Application.Current.TryFindResource("My_Resource") is SolidColorBrush brush) 
{ 
    Application.Current.Resources["My_Resource"] = new SolidColorBrush() { Color = Colors.Red }; 
}
  • Related