Wednesday, July 15, 2009

[2009.07.15] WPF Centric XAML Markup Extensions [IV/IV]

The TemplateBinding Markup Extension

Without getting into too much details as it is a huge topic on its own, templates give you a way to change the look and feel of the WPF controls and well as to make your own from scratch. There are several types of templates such as ControlTemplate and DataTemplate while they have different names, slightly different implementations and uses, they all stem from the basic concept of what defines a template.

When you are creating a template, you have the choice to hard code all the properties and behaviors for the element you are making the template for or you can give the user of the template some sort of freedom to add their own values to some of the template properties. This is where the TemplateBinding markup extension comes into play.

This extension provides synchronization between two property values by linking the value of a property in a control template to be the value of some other exposed property on the templated control. This means you are actually giving the user the choice to customize the template based on values they set on the control the template is being applied to.

The TemplateBindingExtension class is in the PresentationFramework.dll assembly under the System.Windows namespace. It has an overloaded constructor that takes a DependencyProperty object as its argument. It also exposes the Property property. Therefore, in XAML, both positional and named parameters can be used as arguments to this markup extension.

This is best seen in an example. Here I am defining a template that changes the typical WPF Button into something else, and it uses the markup extension to link the customizable properties between the template and the control it is being applied to.

  1: <Window x:Class="MarkupExtDemo.Window1"
  2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4:         Title="ME Demo" Width="100" Height="100">
  5:     <Window.Resources>
  6:         <ControlTemplate TargetType="Button" x:Key="ct1">
  7:             <StackPanel>
  8:                 <Border CornerRadius="0,5,0,5"
  9:                         BorderThickness="{TemplateBinding Button.Margin}"
 10:                         BorderBrush="{TemplateBinding Property=Button.Background}">         
 11:                     <ContentPresenter HorizontalAlignment="Center" 
 12:                                       VerticalAlignment="Center"/>
 13:                 </Border>
 14:             </StackPanel>
 15:         </ControlTemplate>
 16:     </Window.Resources>
 17:    
 18:     <StackPanel>
 19:         <Button Content="Button 1" Margin="2"
 20:                 Background="Red" 
 21:                 Template="{StaticResource ResourceKey=ct1}" />
 22:         
 23:         <Button Content="Button 2" Margin="4"
 24:                 Background="Blue" 
 25:                 Template="{StaticResource ResourceKey=ct1}" />
 26:     </StackPanel>
 27: </Window>

Line 9 indicates the use of positional parameter syntax and Line 10, the more explicit named parameter. As you have come to notice, either way is acceptable as the results are identical. It is a matter of preference as to which to use.

The result of the application of the TemplateBinding is illustrated in Fig. 7.


[2009.07.15].07.template.binding
Fig.7 TemplateBinding in action

In Line 9, the BorderThickness and BorderBrush properties are linked to the Margin and Background properties of the Button control, respectively. This shows that by changing certain properties, the user has more control as to how the button will look in the end, rather than having the value hard coded in the template itself.


The RelativeSource Markup Extension

Recall I said previously that you can use the Binding markup extension for most of your binding needs, except in the case of the DynamicResource extension. As such, instead of using TemplateBinding you can instead use the RelativeSource extension which is exposed as a property (refer to Fig.1 in this series) within a Binding XAML markup extension. The syntax for replacing TemplateBinding with RelativeSource and Binding is a bit more arcane, but again, the choice to use it is entirely up to you.

RelativeSource can actually function both as a data object and a markup extension. This extension is used to indicate the source of the binding, relative to the position of the binding target. This means you can look at different levels of the element tree for the binding source. The simplest version of RelativeSource takes a RelativeSourceMode object as an argument to its constructor. This is actually an enumeration which can take the values:

  • TemplatedParent: Refers to the element to which the template is applied. This is saying to look at the element that this template is applied to and get the corresponding property that the binding refers to.
  • Self: Allows you to bind properties within the same element. For instance, in a Button control, you can bind the Margin and Padding together since the both return a Thickness object.
  • FindAncestor: Can use this to bind to an ancestor in the parent chain of the data-bound element of a specific type or its subclasses.
  • PreviousData: Allows you to bind the previous data item (not that control that contains the data item) in the list of data items being displayed.

Given that the RelativeSource is the super simple version of using a full blown Binding markup extension, there may be cases when having the power of the latter is more relevant to what you want to accomplish.

Here is an extended example to illustrate some of these features.

  1: <Window x:Class="MarkupExtDemo.Window1"
  2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4:         Title="ME Demo" Width="170" Height="250">
  5:     <Window.Resources>
  6:         <ControlTemplate TargetType="Button" x:Key="ct1">
  7:             <StackPanel>
  8:                 <Border CornerRadius="0,5,0,5"
  9:                         BorderThickness="{TemplateBinding Button.Margin}"
 10:                         BorderBrush="{TemplateBinding Property=Button.Background}">         
 11:                     <ContentPresenter HorizontalAlignment="Center" 
 12:                                       VerticalAlignment="Center"/>
 13:                 </Border>
 14:             </StackPanel>
 15:         </ControlTemplate>
 16:         
 17:         <ControlTemplate TargetType="Button" x:Key="ct2">
 18:             <StackPanel>
 19:                 <Border BorderThickness="2" CornerRadius="0,5,0,5"
 20:                         BorderBrush="{Binding Path=Background, RelativeSource={RelativeSource Mode=TemplatedParent}}">
 21:                     <ContentPresenter 
 22:                         HorizontalAlignment="Center" VerticalAlignment="Center"/>
 23:                 </Border>
 24:             </StackPanel>
 25:         </ControlTemplate>
 26:     </Window.Resources>
 27:    
 28:     <StackPanel Margin="10" >
 29:         <Button Content="Button 1" Margin="2"
 30:                 Background="Red" 
 31:                 Template="{StaticResource ResourceKey=ct1}" />
 32:         
 33:         <Button Content="Button 2" Margin="4"
 34:                 Background="Blue" 
 35:                 Template="{StaticResource ResourceKey=ct1}" />
 36:         
 37:         <Button Content="Button 3"  Margin="5" 
 38:                 Background="Red" Template="{StaticResource ResourceKey=ct2}" />
 39:         
 40:         <Button Content="Button 4"
 41:                 Margin="6" Background="BlanchedAlmond"
 42:                 Padding="{Binding Path=Margin, RelativeSource={RelativeSource Mode=Self}}" />
 43:         
 44:         <Button Content="Button 5"
 45:                 Margin="{Binding Path=Margin, RelativeSource={RelativeSource AncestorType={x:Type StackPanel},Mode=FindAncestor}}" />
 46:     </StackPanel>
 47: </Window>

The result of running the above code is given in Fig. 8.


[2009.07.15].08.relative.source.binding
Fig.8 RelativeSource markup extension in action

I have added to the code listing from the TemplateBinding listing. Line 20 of the second template defintion shows the use of the RelativeSource markup extension. As you see, both Button 1 and Button 3 looks exactly alike. As mentioned before, the TemplateBinding used in Button 1 is the simplified version the RelativeSource markup extension used in Button 3.

Line 20 and Button 4 show the use of the Self enumeration. In this case, the Padding is the target which is bound to the source property, Margin, all within the same element.

Finally, Line 45 / Button 5 looks like a binding that comes in nightmares! Okay, it is pretty involved but once you get past the long winded syntax, you can see that the button's Margin property is bound to the Margin property of its ancestor (which may or may not be a direct parent) but it will only look at an ancestor of a specific type, in this case, StackPanel. Although the use of the {x:Type } markup extension was not absolutely necessary, I used it to bring more clarity to the expression.

Well this brings me to the end of WPF centric markup extensions. Of course, you can always refer to the MSDN documentation for more on the topic.

No comments: