29

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.

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
thudbutt
  • 1,481
  • 1
  • 19
  • 32

2 Answers2

59

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.

ASh
  • 34,632
  • 9
  • 60
  • 82
XiaoChuan Yu
  • 3,951
  • 1
  • 32
  • 44
  • Hey I edited my answer, it should work now. It was a syntax mistake, sorry about that. – XiaoChuan Yu Feb 10 '12 at 22:34
  • 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! – thudbutt Feb 13 '12 at 10:14
  • I don't understand why my identical case doesn't work in design time (but it does in runtime) - http://stackoverflow.com/questions/41614676/binding-style-property-value-to-an-attached-property-causes-design-time-only-e/41617979 – hyankov Jan 13 '17 at 09:56
  • 1
    You might as well use `Value={TemplateBinding local:ThemeProperties.ButtonBorderColour}`. Note the missing parantheses around the Binding-Expression. – Leon Bohmann Jun 26 '22 at 16:22
-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.

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • Unfortunately that doesn't seem to be recognised as valid syntax and it just returns an error of Type '(local:ThemeProperties' was not found. – thudbutt Feb 10 '12 at 19:08
  • 2
    Sorry, you need `Path=` in front of it. It's a quirk in the XAML parser. I'll update my answer. – Kent Boogaart Feb 10 '12 at 19:40
  • Still no joy, still flagged as invalid syntax with the error "The property 'Path' was not found in type 'TemplateBindingExtension'". – thudbutt Feb 10 '12 at 21:07
  • 1
    Change TemplateBinding to Binding as per XiaoChuan's suggestion. – Kent Boogaart Feb 10 '12 at 23:16
  • 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. – thudbutt Feb 13 '12 at 10:16
  • 2
    This answer is not valid, it's still suggesting `TemplateBinding`. I suggest updating it. – ANeves Oct 26 '16 at 17:58