Monday, 9 September 2013

ControlTemplate in WPF

WPF controls are logically divided into their Appearance and Functionality.

Appearance defines look and feel of the control. Each WPF control has default control template that defines its appearance.

Functionality of the control is defined using methods, events and so on. Simple example of functionality is click event of button control.

Hence appearance of control can be customized by creating new ControlTemplate for control without affecting the functionality of the control.

In this post, we would customize Button control by creating a new ControlTemplate.

Let’s start by adding a simple Button control.

XAML

<Button Width="100" Height="100" FontSize="16" Content="Home" Name="btnHome" Click="btnHome_Click"></Button>

Code behind

private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Home Button Clicked");
}

Output



What we see above is the output of default button control template.


 


Creating Inline ControlTemplate


Now we create an inline control template for Button control to change its appearance to circle.


XAML

<Button Width="100" Height="100" FontSize="16" Content="Home" Name="btnHome" Click="btnHome_Click">
<Button.Template>
<ControlTemplate>
<Grid>
<Ellipse Fill="LightBlue" Stroke="Blue"></Ellipse>
<TextBlock Text="Home" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>

Output


 


Important points are highlighted in yellow color in above code. Every Control has a Template property that is used to assign new control template to control. ControlTemplate is the class to create control template. Inside ControlTemplate, we have added a grid layout control with ellipse  and TextBlock containing text.


 


ControlTemplate defined as a resource


ControlTemplate created in above code is inline and hence applies to single button. To apply same template to multiple button controls, modify the code as below.


XAML

<Window x:Class="ControlTemplateSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<Window.Resources>
<ControlTemplate x:Key="EllipseButton" TargetType="{x:Type Button}">
<Grid>
<Ellipse Fill="{TemplateBinding Property=Background}" Stroke="{TemplateBinding Property=BorderBrush}"></Ellipse>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"></ContentPresenter>
</Grid>
</ControlTemplate>
</Window.Resources>
<StackPanel>
<Button Width="100" Height="100" FontSize="16" Content="Home" Name="btnHome" Margin="10" Click="btnHome_Click" Template ="{StaticResource ResourceKey=EllipseButton}" Background ="LightBlue" BorderBrush ="Blue">
</Button>
<Button Width="100" Height="100" FontSize="16" Content="About us" Name="btnAboutus" Margin="10" Click="btnAboutus_Click" Template ="{StaticResource ResourceKey=EllipseButton}" Background ="LightGreen" BorderBrush ="Green">
</Button>
</StackPanel>
</Window>

Output



What we have done here is added new ControlTemplate as a resource in resource dictionary, set its TargetType to Button and provide a unique key to the ControlTemplate. Then we apply new ControlTemplate to multiple button controls using Template property and StaticResource markup  extension.


 


TemplateBinding and ContentPresenter


If you have noticed same ControlTemplate is applied to both button controls, but their content, ellipse background and border color are different. If these values were set directly inside ControlTemplate, then they would have been same for all button controls which is not desired. Instead these values are actually derived from parent control to which template is applied. This is achieved using TemplateBinding.


TemplateBinding is a DataBinding Markup Extension that binds a property of a control inside ControlTemplate to the property of a templated control (Control to which this ControlTemplate is applied). This makes ControlTemplate flexible and reusable at different places.


TemplateBinding requires TargetType to be specified in ControlTemplate. TargetType=”{x:Type Button}” means this ControlTemplate is for Button control. If not specified, default TargetType is “Control” element which is base class for all WPF control.


If we do not specify TargetType and if we bind any property that is not present in “Control” class then compiler will throw error.


Ex: Consider below TextBlock added inside ControlTemplate

<TextBlock Text="{TemplateBinding Property=Content}"></TextBlock>

Here we bind Text property of TextBlock to Content property of templated control. If TargetType is not specified on ControlTemplate, then compiler takes default “TargetType” as Control. Now Content property is used in TemplateBinding but not present in Control class so Compiler with throw compile time error saying: “Cannot find the static member 'ContentProperty' on the type 'Control”.


ContentPresenter is the place holder for the content property of element and will be replaced by the actual content from the templated parent control at run time. ContentPresenter internally uses TemplateBinding and requires TargetType to be set. If we remove TargetType from ControlTemplate, then compile will not throw error but at runtime content would not be displayed. This is the mistake that sometimes people make and finds it difficult to debug.


Different between TemplateBinding & ContentPresenter


TemplateBinding is a DataBinding Markup Extension that can be used to bind any property of a control inside ControlTemplate to template control property of same type. While ContentPresenter is actually a Place Holder for Content Property of template control.


 


 Triggers


After creating a new ControlTemplate and running the application, we noticed that when we move mouse over the Button, there is no effect. Default button control template changes button background with some sort of blue gradient. Trigger would be useful to achieve this. ControlTemplate allows defining Trigger that can be used to change appearance of controls based on some user interaction.


XAML

<ControlTemplate x:Key="EllipseButton" TargetType="{x:Type Button}">
<Grid>
<Ellipse Name="ellipse" Fill="{TemplateBinding Property=Background}" Stroke="{TemplateBinding Property=BorderBrush}"></Ellipse>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"></ContentPresenter>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property ="IsMouseOver" Value ="True">
<Setter TargetName ="ellipse" Property ="Fill" Value ="Goldenrod"></Setter>
<Setter TargetName ="ellipse" Property ="Stroke" Value ="Brown"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

Output



Here we have given name to ellipse control so that ellipse can be used in Trigger section. When user moves mouse over Button control, we set Fill and stroke property of ellipse control to different values.


 


Additional information


While understanding TemplateBinding and ContentPresenter I explored 2 other ways in which we can derive values from parent/templated control. So I am mentioning them here. This is just for sake of knowledge and understanding. Mainly prefer to use TemplateBinding and ContentPresenter inside ControlTemplate.


Consider a TextBlock inside ControlTemplate. Text property of this TextBlock control should be derived from Content property of templated control. We can use RelativeResource DataBinding.



1.  RelativeResource - RelativeResource is a DataBinding markup extension that is used to reference a resource/control that is positioned relatively to current control.


<TextBlock Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}, Path=Content}"></TextBlock>


In above code, Binding will search for Ancestor control of Type Button up in the hierarchy and set the value of Content property of Button control to Text property of TextBlock control.


This syntax is not specific to templates. This is generic way of finding any control up in the hierarchy and binding its properties. Below is more template specific syntax.


2. TemplatedParent - RelativeResource provides Mode property which can be set to TemplatedParent. This means search for parent control to which this template is applied.

<TextBlock Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content}"></TextBlock>


TemplateBinding can be thought as an optimization to above TemplatedParent syntax and provides easier, better and preferred way of binding property of parent control.

No comments:

Post a Comment