Tuesday, March 17, 2009

Reusable Templates with WPF Attached properties

WPF attached properties are probably a very underused feature of WPF for beginners.

Most people come into contact with them only through Grid.Column or Canvas.X, and see them as related to layout. But there is so much more to them!

Attached properties become really powerful indeed when mixed with styles and templates or as “declarative behavior anchors”. This post is about the first case.

Today on Stack Overflow, someone asked how to make his UserControl work. His goal was to make a reusable “image button”, with an Image and a Caption. His problem was to still be able to bind an ICommand.

This is a very common reaction for anybody who has been brought up in the Windows Forms toolkit. When you want reusable, you create a UserControl. I’d argue that this is probably wrong most of the time in WPF. Second answer is usually “create a class that inherits from Button!”. No need for that.

The goal was just to change the appearance of a Button, which is a textbook case for styling and templating. But his goal was to make it

  • reusable across the application
  • as concise as possible to create a new “ImageButton” in his layout

Well, this is where Attached Properties, with some support from styles and templates, come into play. 

The attached properties give you the power of extending the information attached to an element. In our case, we can use them to store Caption as a string and Image as an ImageSource.

we can then use a style and some templating to use these properties and display what we want. The end result? We still have a button, that works like it should, but with a specific look that is easy to reuse across the board.

lets look at the properties first:

public class ImageButton
{

    public static ImageSource GetImage(DependencyObject obj)
    {
        return (ImageSource)obj.GetValue(ImageProperty);
    }

    public static void SetImage(DependencyObject obj, ImageSource value)
    {
        obj.SetValue(ImageProperty, value);
    }

    public static readonly DependencyProperty ImageProperty =
        DependencyProperty.RegisterAttached("Image", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));

    public static String GetCaption(DependencyObject obj)
    {
        return (String)obj.GetValue(CaptionProperty);
    }

    public static void SetCaption(DependencyObject obj, String value)
    {
        obj.SetValue(CaptionProperty, value);
    }

    public static readonly DependencyProperty CaptionProperty =
        DependencyProperty.RegisterAttached("Caption", typeof(String), typeof(ImageButton), new UIPropertyMetadata(null));
}


That’s all the code you need.

Now for the templating, trusty old xaml is here:


<Style TargetType="{x:Type Button}"
   x:Key="ImageButton">
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Image Source="{Binding Path=(local:ImageButton.Image),
                           RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
                    <TextBlock Text="{Binding Path=(local:ImageButton.Caption),
                           RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
                           Margin="2,0,0,0" />
                </Grid>
            </DataTemplate>
        </Setter.Value>
    </Setter>  
</Style>

With this, it becomes fairly easy to reuse your button anywhere you need it:

<Button Style="{DynamicResource ImageButton}"
                    local:ImageButton.Caption="Foo"  local:ImageButton.Image=”{StaticResource FooImage}” />

That’s it. No subclassing, almost no code, and you have a perfectly functioning button that reacts like any other button.

The Style used here assumes you want to keep the standard chrome (borders, mouseover states etc) of a button. If you want to completely replace the representation of the button, you can work on the Button.Template property instead, and use {TemplateBinding} to get the properties you need.

This is deceptively simple, but yet impressively powerful. And there is much more to Dependency Properties once you decide to use them to their full extent and encapsulate behavior in a declarative way, but that’s a story for another post.

As Carl Franklin would say: Attached Properties, Know ‘em, Learn ‘em, Love ‘em!

0 comments: