Template Binding with Attached Properties
Asked Answered
L

2

30

I have a standard style for my buttons but I want certain parts of the style to be configurable. e.g. I have a border appear when MouseOver is triggered for the button and I want the border colour to be configurable.

Following this article: http://www.thomaslevesque.com/2011/10/01/wpf-creating-parameterized-styles-with-attached-properties/ I thought I could use attached properties and TemplateBinding to achieve this.

I created the following attached property:

public static class ThemeProperties
{
    public static Brush GetButtonBorderColour(DependencyObject obj)
    {
        return (Brush)obj.GetValue(ButtonBorderColourProperty);
    }

    public static void SetButtonBorderColour(DependencyObject obj, Brush value)
    {
        obj.SetValue(ButtonBorderColourProperty, value);
    }

    public static readonly DependencyProperty ButtonBorderColourProperty =
        DependencyProperty.RegisterAttached(
            "ButtonBorderColour",
            typeof(Brush),
            typeof(ThemeProperties),
            new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.Inherits));
}

I set the property like so:

<Button Style="{StaticResource RedButton}" local:ThemeProperties.ButtonBorderColour="#B20000"/>

And my style looks like this:

<Window.Resources>
    <Style x:Key="RedButton" TargetType="Button">
        <Setter Property="OverridesDefaultStyle" Value="True"/>
        <Setter Property="Margin" Value="2"/>
        <Setter Property="FontFamily" Value="Tahoma"/>
        <Setter Property="FontSize" Value="11px"/>
        <Setter Property="FontWeight" Value="Bold"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="MinHeight" Value="25" />

        <Setter Property="FocusVisualStyle" Value="{StaticResource MyFocusVisual}" />
        <Setter Property="Background" >
            <Setter.Value>
                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" >
                    <GradientStop Color="#FECCBF" Offset="0.2"/>
                    <GradientStop Color="Red" Offset="0.85"/>
                    <GradientStop Color="#FECCBF" Offset="1"/>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border Name="border" BorderThickness="1" Padding="4,2" BorderBrush="Transparent" CornerRadius="3" Background="{TemplateBinding Background}">
                        <Grid >
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Name="content"/>
                        </Grid>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="border" Property="BorderBrush" Value="{TemplateBinding local:ThemeProperties.ButtonBorderColour}" />
                            <Setter Property="Foreground" Value="#B20000" />
                        </Trigger>
                        <Trigger Property="IsPressed" Value="True">
                            <Setter Property="Background" >
                                <Setter.Value>
                                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" >
                                        <GradientStop Color="#FECCBF" Offset="0.35"/>
                                        <GradientStop Color="Red" Offset="0.95"/>
                                        <GradientStop Color="#FECCBF" Offset="1"/>
                                    </LinearGradientBrush>
                                </Setter.Value>
                            </Setter>
                            <Setter TargetName="content" Property="RenderTransform" >
                                <Setter.Value>
                                    <TranslateTransform Y="1.0" />
                                </Setter.Value>
                            </Setter>
                        </Trigger>
                        <Trigger Property="IsDefaulted" Value="True">
                            <Setter TargetName="border" Property="BorderBrush" Value="#B20000" />
                        </Trigger>
                        <Trigger Property="IsFocused" Value="True">
                            <Setter TargetName="border" Property="BorderBrush" Value="#B20000" />
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter TargetName="border" Property="Opacity" Value="0.7" />
                            <Setter Property="Foreground" Value="Gray" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

Where the key line is

<Trigger Property="IsMouseOver" Value="True">
    <Setter TargetName="border" Property="BorderBrush" Value="{TemplateBinding local:ThemeProperties.ButtonBorderColour}" />
    <Setter Property="Foreground" Value="#B20000" />
</Trigger>

As far as I can see this should work but I get the following error during runtime on the above line:

Cannot convert the value in attribute 'Value' to object of type ''. Error at object 'System.Windows.Setter' in markup file

Have I done something incorrect here? I'm brand new to WPF and can't figure what's going wrong as the Type of the attached property is a Brush which is what I would expect the BorderBrush property of a Border to want.

Leena answered 10/2, 2012 at 17:59 Comment(0)
F
61

I think TemplateBinding is evaluated at compile time so you can't dynamically set a TemplateBinding in your Setter, try using Binding instead (see below).

<Setter TargetName="border" Property="BorderBrush" 
        Value="{Binding Path=(local:ThemeProperties.ButtonBorderColour),
                        RelativeSource={RelativeSource TemplatedParent}}"/>

Hope this helps.

Feinstein answered 10/2, 2012 at 18:32 Comment(4)
Hey I edited my answer, it should work now. It was a syntax mistake, sorry about that.Feinstein
Thanks XiaoChuan, that has worked perfectly. I can also see the difference between my code and the example on the linked webpage as the example is not using the TemplateBinding in a setter which I didn't appreciate before. Thanks again!Leena
I don't understand why my identical case doesn't work in design time (but it does in runtime) - #41615176Quadripartite
You might as well use Value={TemplateBinding local:ThemeProperties.ButtonBorderColour}. Note the missing parantheses around the Binding-Expression.Bare
R
-3

Try this:

<Setter TargetName="border" Property="BorderBrush" Value="{TemplateBinding Path=(local:ThemeProperties.ButtonBorderColour)}" />

The difference being that parentheses around the property indicate that it is an attached property.

Ruwenzori answered 10/2, 2012 at 18:36 Comment(6)
Unfortunately that doesn't seem to be recognised as valid syntax and it just returns an error of Type '(local:ThemeProperties' was not found.Leena
Sorry, you need Path= in front of it. It's a quirk in the XAML parser. I'll update my answer.Ruwenzori
Still no joy, still flagged as invalid syntax with the error "The property 'Path' was not found in type 'TemplateBindingExtension'".Leena
Change TemplateBinding to Binding as per XiaoChuan's suggestion.Ruwenzori
Thanks for your help Kent, the parentheses are valid syntax when (following your suggestion to use XiaoChuan's solution) combined with a Binding rather than a TemplateBinding.Leena
This answer is not valid, it's still suggesting TemplateBinding. I suggest updating it.Brawl

© 2022 - 2024 — McMap. All rights reserved.