Search code examples
wpftemplatesresources

How to access Properties set by "coder" inside say an overriding (named) Button Template in a xaml Dictionary


I am tearing my hair out trying to create a Style for the "Button bar" buttons in an App I am working on, but I want the developer to be able to specify the colors used in Button gradient fills etc by adding xaml code in their Button declaration for the BackGround, Foreground and BorderBrush colors.

I have used the "Copy Template" trick of a totally undefined to get a copy of the full default Template for the control, but am totally confused by the way all the important colors are Hard coded internally in the style, being renamed with names such as "Button.Static.Background" which are all using HARD CODED COLORS attached to them eg:

<SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/>
<SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/>''

and these are then referenced later on in the template by these names. What I would really like to be able to do is to refer to the User defined properties for these items rather than hard coded values at this point in the template, but I cannot seem to find a way to do so.

As an example, here is what I am trying, but it doesn't work, although I do not get any errors as such. The first line is a standard declaration, the next 2 are the ones I want to allow the user to override when using this style to match their preferred background/foreground color schemes.

<SolidColorBrush x:Key="Button.Static.Border" Color="Black"/>
<SolidColorBrush x:Key="Button.MouseOver.Background" Color="{DynamicResource Background}"/>
<SolidColorBrush x:Key="Button.MouseOver.Border" Color="{DynamicResource Border}"/>

Later on in the template we come to :-

    <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>

''' and this is where I want the user defined colors (if supplied) to be used when the mouseover occurs, rather than any hard coded values ? . Am I trying to be too clever here, or is there a sensible way I can achieve this ?


Solution

  • You can make a templated control based on nearly any control, this case being a button, and extend the control. You can add properties to allow users to specify the colors when using the control in the XAML. Of course, you’ll have to set default colors - likely being the colors you’ve used in your question.

    Here’s an answer I wrote the other day on how to create a bindable control: https://stackoverflow.com/a/66394791/11590704

    • The important part in the answer is the Dependency Properties and the Template property in the control’s Style. You don’t need to create a new control to use TemplateBinding.

    You can use the same methods there and practically add any property you want. Foreground, MouseOverForeground, MouseOverBackground, etc.

    An alternative would be to create a global theme. All your controls should use this theme (you’d likely have to create customized styles for each control to use the theme).

    • You would declare the default theme colors in a resource dictionary like you did in your question.
    • The benefit of a theme is that end users can customize the theme at run-time, e.g. change a button’s background color through the UI (you’d have to code in the theme adjustments in your .cs code). Also, your control will be visually coherent.
    • This is a larger task though but I’d keep it in mind when creating your controls. I’ve had end users who need high contrasting colors so I created a theme and some code for theme adjustments.

    EDIT - More Information

    You don't have to create dependency properties for the base properties (although I suppose you can if you really wanted to). You can work around them in either the XAML or the callbacks/events in your control's code-behind.

    Example: Working around the base background property.

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
    
        //  Set the default background
        DefaultBackground = this.Background;
    }
    
    //  Store the default background color to revert back to
    private Brush DefaultBackground;
    
    //  Dependency property for mouse over background color
    public static readonly DependencyProperty MouseOverBackgroundProperty = DependencyProperty.Register(
        nameof(MouseOverBackground), typeof(Brush),
        typeof(NavButton), new PropertyMetadata(Brushes.Transparent));
    
    public Brush MouseOverBackground
    {
        get => (Brush)GetValue(MouseOverBackgroundProperty);
        set => SetValue(MouseOverBackgroundProperty, value);
    }
    
    // On Mouse Enter => Set Background
    protected override void OnMouseEnter(MouseEventArgs e)
    {
        base.OnMouseEnter(e);
    
        //  Set background color
        this.Background = MouseOverBackground;
    }
    
    // On Mouse Leave => Revert Background
    protected override void OnMouseLeave(MouseEventArgs e)
    {
        base.OnMouseLeave(e);
    
        //  Set background color back
        this.Background = DefaultBackground;
    }
    

    I used the events already in the control to handle the background color states. This is a basic example using overrides for the event handlers but you can subscribe to the events directly.

    And then your XAML will look like this:

    <local:NavButton Background="Transparent" MouseOverBackground="Red"/>       
    
    <!-- OR -->
    
    <SolidColorBrush x:Key="ButtonBackground" Color="#1AFFFFFF" />
    <SolidColorBrush x:Key="ButtonMouseOverBackground" Color="#00897B" />
    
    <local:NavButton Background="{DynamicResource ButtonBackground}" 
         MouseOverBackground="{DynamicResource ButtonMouseOverBackground}"/>        
    
    
    

    The triggers are handled in the code-behind.


    EDIT 2: Some more information

    I forgot to mention that triggers may not work unless you have default values set. You set the default values in the Style.

    <Style TargetType="{x:Type local:NavButton}">
        <!-- The default value -->
        <Setter Property="Background" Value="Grey" />
                
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <!-- The trigger value -->
                <Setter Property="Backround" Value="Red" />
            </Trigger>
        </Style.Triggers>
    </Style>