Friday, 15 August 2008

Exposing new properties for control templates using attached properties

There are many instances where you want to write a groovy looking control template to apply to your WPF controls only to find that the control you want to template falls short in the properties it provides - eg: Try specifying the over state gradient brush for a button using only it's exposed properties. In these situations you need some custom properties on the control to be able to tell your template what to do.

In this situation there are several options open to you - you could hijack a property - bad idea when it comes to maintainability. Alternatively you could inherit the control and introduce the new properties - again, not the most ideal situation under WPF's composition model.

By far the most flexible and easiest way to achieve this behaviour is with attached dependency properties.

In the following example, we want to extend all of our buttons to provide not just a single line of text as you'd find on normal buttons, but we also want to have customisable sub-title line. To achieve this we start with the custom dependency property;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ButtonPropertyExtender : DependencyObject
{
  public static readonly DependencyProperty SubTextProperty =
    DependencyProperty.RegisterAttached("SubText",
      typeof(String), typeof(ButtonPropertyExtender),
      new FrameworkPropertyMetadata(null,
        FrameworkPropertyMetadataOptions.AffectsMeasure |
        FrameworkPropertyMetadataOptions.AffectsArrange));

  public static void SetSubText(UIElement element, object o)
  {
    element.SetValue(SubTextProperty, o);
  }

  public static string GetSubText(UIElement element)
  {
    return (string)element.GetValue(SubTextProperty);
  }
}


Quite simply this registers a custom attached property with WPF and allows it to be attached to any element. The next thing we do is use the new property on our button thus;

1
<Button xc:ButtonPropertyExtender.SubText="Subtitle text">
Click Me!
</
Button>

And finally, to use our new property in the control template, we bind to it - notice however how we cannot use the {TemplateBinding} shortcut - instead we must use the full binding expression to get to the new custom property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<ControlTemplate TargetType="Button">
  <Border Background="{TemplateBinding Background}"
      BorderBrush="Black"
      BorderThickness="2"
      CornerRadius="10" >

    <StackPanel HorizontalAlignment="Center">
      <ContentPresenter Content="{TemplateBinding Content}"/>
      <TextBlock
        Text="{Binding
RelativeSource={RelativeSource TemplatedParent},
         Path=(xc:ButtonPropertyExtender.SubText)}"
/>
    </StackPanel>
  </Border>
</ControlTemplate>