Поделиться через


WPF Control Templates - An Overview

In several of my discussions with customers around WPF in the recent past, I got asked a bunch of questions around Control Templating. Most of them can be summarized to one - "how do control templates work". Control Templating is fairly fundamental to WPF development, and unlike some niche features like WPF 3D, a good understanding of Control Templating goes a long way to sleek UI development.

There are many excellent writings on this topic - so before I write something on this, I should at least point a few out. Chances are that, you might get a much better idea from these works than my post here.

The best coverage I have read is in Chris Anderson's excellent book Essential Windows Presentation Foundation. If you are working with WPF, and need a deep understanding of not just the workings of the framework, but the design motivations behind why certain things are the way they are - I would urge you to read this book cover-to-cover.  You should also check out Adam Nathan's work WPF Unleashed, and Programming WPF by Chris Sells and Ian Griffiths - both excellent books as well. Now that you have that information, let me see if I can put things in a simple perspective.

To understand control templates, it is helpful to first understand two related concepts:

  • The WPF element tree - the "logical tree" of a control template vs. it's "visual tree"
  • The content model for WPF controls.

Logical Tree

Let's start with the "logical" tree. The simple XAML snippet below produces a ListBox with three items:

 

<ListBox Height="100" Width="100"> <ListBoxItem>Item 1</ListBoxItem> <ListBoxItem>Item 2</ListBoxItem> <ListBoxItem>Item 3</ListBoxItem> </ListBox> image

 

The element tree of the ListBox as depicted in the XAML hierarchy is shaped like 

 

Picture1

 

This is the logical tree for the ListBox. If we were to take an even simpler snippet of XAML like

 

<Button Height="40" Width="100" Content="Hello" /> image
  

the logical tree would have just one node - the Button element itself. The logical tree is the externally visible element hierarchy that a consumer of the control gets to program against.

Visual Tree

However, the logical tree provides no information as to the UI that the control displays. The fact that the ListBox displays itself with a bounding rectangle with a black border, or that the Button has a gradient background with an exterior chrome are all encapsulated somewhere else. 

In older UI Frameworks like Win32, Windows Forms or MFC, the control author implemented the UI in the control code, thus providing no facility to the control consumer to completely replace the control's UI - the traditional ways of offering customization capabilities have been to expose properties and methods that allow some customization of the constituent elements and consequently the control's look & feel. 

In WPF however, this is done somewhat differently. To understand how, let's first take a look at the element tree of the ListBox again. Type the XAML in XAMLPad (available with the Windows SDK), and XAMLPad renders the ListBox. Click the "Show/Hide Visual Tree" button (pointed to by the blue arrow) as shown below:

 

Picture2

 

Expanding the Visual Tree Explorer completely, if you look at the ListBox node and below,you can see a tree formed of a multitude of other controls and drawing primitives . This is the "visual tree" for the ListBox control - the complete element tree made of the actual drawing primitives and other controls used to implement the UI of the control. If you look down you will find a similarly detailed tree for each of the ListBoxItems situated within the ListBox.

image

 

The "visual tree" for a control is what contributes to the complete look & feel of the control - it is helpful to be able to see it like this. But what is even more helpful is an understanding of how this element tree is supplied to the control in order to generate the UI, and most importantly, how you as a consumer of the control might be able to change it.

Control Templates & the "Visual Tree"

As a control author you have the option of hard coding this "visual tree" in your control implementation i.e. somewhere in the control's creation and rendering lifecycle, you can instantiate the complete element tree in code, and use that to render the UI. Not that it's a bad way, and in fact there are several controls, that come with WPF, that take this approach, but it makes your control's UI pretty hard to customize.

Control Template's provide a more extensible way. You may have heard it said many times, that WPF controls are "lookless" in that they define the functionality behind the control, with a "default" UI which can be completely replaced by their consumer. This is what control templates enable.

Control Templates are XAML declarations of a "visual tree" for a control. As a control author you can declare and use them in your control implementation to provide the "default" UI for your control. Doing so also leaves the control open for templating by consumers - a consumer can then declare new templates that can be supplied to a control, either in code or in XAML itself, to replace the default look & feel.

That said let's take a look at how that might work.

Creating and Applying a Control Template 

A Control Template is declared inside a <ControlTemplate> element and is usually defined in a resource dictionary. As a requirement of being a part of a resource dictionary, the template is given a unique identifier using the x:Key attribute, and it also specifies the control type to which it can be applied using the TargetType property.

Let's start with a simple one that can be applied to the Button Control.

 <ControlTemplate x:Key="CTGelButton" TargetType="{x:Type Button}">  <Grid Width="100" Height="100">  <Ellipse Stroke="#FF000000" Fill="#FF1C46E7" x:Name="OuterEllipse">  <Ellipse.BitmapEffect>  <BevelBitmapEffect BevelWidth="8" EdgeProfile="BulgedUp" Smoothness="0.745"/>  </Ellipse.BitmapEffect>  </Ellipse>  <Ellipse Margin="8,8,8,8" x:Name="InnerEllipse">  <Ellipse.Fill>  <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0" SpreadMethod="Pad">  <GradientStop Color="#FF1C46E7" Offset="0"/>  <GradientStop Color="#FFFFFFFF" Offset="1"/>  </LinearGradientBrush>  </Ellipse.Fill>  </Ellipse>  <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" Content="{TemplateBinding Content}"/>  </Grid>
 </ControlTemplate>

 

Applying the template to a Button control is done so :

 <Button Content="Hello World" Template="{DynamicResource CTGelButton}"/>
image

 

As you can see applying the template has completely replaced the default Button UI. However, the essential functionality, as implemented by the Button type, has not changed at all. All the events are still raised the way they would by default, all the methods and properties are still available as prescribed by the Button's API - you would program against the Button instance just the way you would if it was displaying its default UI.

Control Content Model & Control Templates

Sometimes the primary purpose of a control is to display content that is supplied to the control. Take for example a ListBox or a ComboBox or a TreeView - these are all controls that have a default look and feel that can be further customized using control templates, but their primary purpose is to display a set of items somewhere within that look and feel. The part of the WPF control architecture that specifies how controls display content is called the Content Model.

To better understand the content model in WPF, let's consider the ContentControl type. The ContentControl itself has a very limited UI, but has a dependency property called Content, that can either be set or databound to any content, which then gets displayed. More than being useful in and of itself, the ContentControl serves as the base class for many other WPF content controls like Label or Button(in case of Button it is not the immediate ancestor - ButtonBase is), and consequently drives the content model for these controls. For example in the XAML below, we show two examples of setting the Content of the Button we have been working with before - once to a text string, and once again to another XAML element tree consisting of a Grid containing a Radiobutton and a Checkbox(a rather contrived example - but serves the purpose of illustration) that can be visually rendered:

 

Button Content Example 1: Text Content  
   
 <Button Content="Hello World" Template="{DynamicResource CTGelButton}"/>
image
   
Button Content Example 2: Visual Tree Content   
   
<Button RenderTransformOrigin="0.625,2.55" Grid.Row="1" Margin="50,21,128,92" Style="{DynamicResource StyleGelButton}"> <Button.Content> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition/> </Grid.RowDefinitions> <RadioButton Content="RB 1" Grid.Row="0" IsChecked="True"/> <CheckBox Content="CB 2" Grid.Row="1" IsChecked="True"/> </Grid> </Button.Content> </Button> image

The ContentControl defines a content model where it or its derivatives can display a single piece of content (it can be a whole tree of elements - but still rooted at a single element).

But why is the understanding of the Content Model important to you when considering control templating ?

When you design a template for a control you need to be aware of the intended content model for that control, and to be fair to the control's intended behavior, should try to retain the same content model in your custom template. Let's take a look at the first template we designed for our Button.

<ControlTemplate x:Key="CTGelButton" TargetType="{x:Type Button}"> <Grid Width="100" Height="100"> <Ellipse Stroke="#FF000000" Fill="#FF1C46E7" x:Name="OuterEllipse"> <Ellipse.BitmapEffect> <BevelBitmapEffect BevelWidth="8" EdgeProfile="BulgedUp" Smoothness="0.745"/> </Ellipse.BitmapEffect> </Ellipse> <Ellipse Margin="8,8,8,8" x:Name="InnerEllipse"> <Ellipse.Fill> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0" SpreadMethod="Pad"> <GradientStop Color="#FF1C46E7" Offset="0"/> <GradientStop Color="#FFFFFFFF" Offset="1"/> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" Content="{TemplateBinding Content }"/> </Grid> </ControlTemplate>

Take a look at the line of XAML in bold above. The ContentPresenter control is essentially a placeholder that binds to the content specified at the declaration of the Button control via its Content property. The ContentPresenter contains all the rules that determine how the content is displayed. When you are defining a new template for the Button control, if you do not specify a ContentPresenter somewhere in your control template, the Button will not behave as intended i.e. no matter what is specified for the Content property , it will not be displayed within your newly templated Button.

There are several other content models supported by WPF - like having a header portion to the content or displaying a list of items. There are other similar base level control classes, like HeaderedContentControl, ItemsControl and HeaderedItemsControl that can help implement these alternate content models. Let's take a look at a ListBox for example, which has ItemsControl as a parent in its inheritance chain. If we were to specify a template for a ListBox like so:

 

<ControlTemplate x:Key="CTListBox" TargetType="{x:Type ListBox}"> <Border BorderBrush="#FFE62121" BorderThickness="4,4,4,4" CornerRadius="3,3,3,3" > <Border.Background> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FFBCDEDC" Offset="0"/> <GradientStop Color="#FFFFFFFF" Offset="1"/> </LinearGradientBrush> </Border.Background> <ItemsPresenter /> </Border> </ControlTemplate>

 

Again notice the ItemsPresenter in the template. This is what preserves the intended "multiple items" content model as prescribed by the ItemsControl, and automatically binds to and displays the content in the Items collection for the ListBox. The template when applied to a ListBox with a set of items, like below , produces the UI on the right.

<ListBox IsSynchronizedWithCurrentItem="True" HorizontalAlignment="Left" Margin="158,142,0,194" Width="96" Template="{DynamicResource CTListBox}"> <ListBoxItem Content="Item 1"/> <ListBoxItem Content="Item 2"/> <ListBoxItem Content="Item 3"/> <ListBoxItem Content="Item 4"/> </ListBox> image

 

So as you see, you should consider the intended content model for the control you are trying to template, and preserve that in your template. A complete discussion of all the various control content models is beyond the scope of this writing, but you can look in the .Net Framework SDK documentation and samples to find more details about the content model , its mechanics, and the set of rules that define how the WPF runtime resolves how to render arbitrary content (for example a straight piece of text vs. a XAML element tree vs. an instance of some CLR type etc.). 

 

Binding to Parent Properties 

More often than not there may be a desire, or even a need, to not hard code property values in a control template, but let the control consumer specify those values. The control consumer's primary access is however to the control itself - so there needs to be a way of allowing the consumer's desired property settings flow to the template applied to the control. For example in the ListBox control template in the previous section, we see a Border control being used in the template, with its BorderBrush and BorderThickness properties set to specific hard coded values. The ListBox control itself however exposes the same properties. However the way the template is specified above, applying values to those properties on the ListBox control will have no effect on the template, and the ListBox will always show up with a red border of thickness 4 - no matter what.

This is where the TemplateBinding markup extension comes to the rescue. TemplateBinding is a kind of specialized data binding, that binds to the property values in the TemplatedParent i.e. the control instance to which the template is being applied. Take a look at the slightly modifed ListBox template below:

 

<ControlTemplate x:Key="CTListBox" TargetType="{x:Type ListBox}"> <Border BorderBrush ="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"

CornerRadius="3,3,3,3" > <Border.Background> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FFBCDEDC" Offset="0"/> <GradientStop Color="#FFFFFFFF" Offset="1"/> </LinearGradientBrush> </Border.Background> <ItemsPresenter/> </Border> </ControlTemplate>

We are now setting the BorderBrush and BorderThickness as TemplateBindings to matching properties in the TemplatedParent.

 

As a result, the following two ListBox declarations will produce differing outputs :

 

<ListBox IsSynchronizedWithCurrentItem="True" HorizontalAlignment="Left" Margin="158,142,0,194"BorderBrush="LightBlue" BorderThickness="7" Background="Black" Width="96" Template="{DynamicResource CTListBox}"> <ListBoxItem Content="Item 1"/> <ListBoxItem Content="Item 2"/> <ListBoxItem Content="Item 3"/> <ListBoxItem Content="Item 4"/> </ListBox>
  
image
<ListBox IsSynchronizedWithCurrentItem="True" HorizontalAlignment="Left" Margin="158,142,0,194"BorderBrush="Gold" BorderThickness="2" Background="Blue" Width="96" Template="{DynamicResource CTListBox}"> <ListBoxItem Content="Item 1"/> <ListBoxItem Content="Item 2"/> <ListBoxItem Content="Item 3"/> <ListBoxItem Content="Item 4"/> </ListBox>    image

 

You may have noticed that in addition to setting the BorderBrush and BorderThickness properties on the ListBoxes, I also set the Background property value. But it does not have any impact on the ListBox, since in the template, the Background property for the Border element is hardcoded to a gradient.

 

If you look at the Button control template that we have been using in the sections above, you will find another example of TemplateBinding - the ContentPresenter uses TemplateBinding to bind to the Content property of the TemplatedParent (the Button instance in this case).

While in the examples above we have bound similar properties within the templates to the TemplatedParent i.e. BorderBrush to BorderBrush etc. there is no such rule mandating so. As long as the types match or there is a suitable TypeConverter, you can pretty much achieve any kind of binding, only limited by your sense of what is functionally sensible to do. 

 

Control Templates & Triggers

The Button control template that we have been working with before is pretty inert - when you apply it you do not see any of the usual animations that are associated with a button's default UI when a mouse moves over it, or it receives focus, or it is depressed. So let's add a trigger to the template above :

 <ControlTemplate x:Key="CTGelButton" TargetType="{x:Type Button}">  <Grid Width="100" Height="100"> 
 <!-- OMITTED FOR BREVITY -->

  </Grid>  <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True">  <Setter Property="BitmapEffect" TargetName="OuterEllipse">  <Setter.Value>  <BitmapEffectGroup>  <OuterGlowBitmapEffect GlowColor="#FFFF2300" GlowSize="17" Noise="0"/>  <BevelBitmapEffect BevelWidth="8" EdgeProfile="BulgedUp" Smoothness="0.7"/>  </BitmapEffectGroup>  </Setter.Value>  </Setter>  </Trigger>  </ControlTemplate.Triggers> </ControlTemplate>

 

A Trigger is a WPF construct that allows you to declaratively specify a single or a set of changes to be applied to a specific element within scope, when a certain action happens. These driving actions could either be changes in the values of one or a set of properties or be the firing of an event. In other words using Triggers you can achieve the same effect that you would achieve in non WPF applications like Windows Forms by possibly coding sizeable event handlers.

In this case we are using a Property Trigger that fires when the value of the IsMouseOver property changes to True. When that happens, the <Setter> element in the trigger instructs that the BitmapEffect property of the target element OuterEllipse be set as defined in the <Setter.Value>. Unlike what you would possibly do in an event handler, you do not have to do anything to explicitly set the UI back to its original state when the IsMouseOver property changes back to False - the Trigger takes care of that. The resulting UI would look like below when the mouse moves over the Button instance:

image

 

Control Templates & Styles

Styles are convenient ways of grouping property settings together and applying them to more than one element. Since we apply control templates to controls using the Template property, we can use a Style to actually apply the control template, instead of directly setting the Template property on the control instance element. So we could do something like below:

<Style x:Key="StyleGelButton" TargetType="{x:Type Button}"><!-- Other Property Setters ommitted for brevity --> <Setter Property="Template" Value="{DynamicResource CTGelButton}"/> </Style><Button Content="Hello World" RenderTransformOrigin="0.625,2.55" Grid.Row="1" Margin="50,21,128,92" Style="{DynamicResource StyleGelButton}"/>

 

A style can be universally applied to all instances of a type. If you skip setting the x:Key property on the style above, and consequently skip setting the Style property on the Button, the style will be applied to all Buttons within scope. For control templating purposes, this is handy when you want to apply a certain template to all controls of a specific type.

Styles also come in handy when you need to apply templates to a control conditionally - say depending on the outcome of a property trigger. The snippet below defines a style that applies two different templates to the Button control depending on whether the Button is depressed or not.

<ControlTemplate x:Key="CTGelButton" TargetType="{x:Type Button}"> <Grid Width="100" Height="100"> <!-- OMITTED FOR BREVITY -->

  </

ControlTemplate>

  <

ControlTemplate x:Key="CTGelButtonPressed" TargetType="{x:Type Button}"> <Grid Width="100" Height="100"> <Ellipse Stroke="#FF000000" Fill="#FF1C46E7" x:Name="ellipse" StrokeThickness="5"/> <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" Content="{TemplateBinding Content}"/> </Grid> </ControlTemplate>

  <

Style x:Key="StyleGelButton" TargetType="{x:Type Button}"> <Setter Property="Template" Value="{DynamicResource CTGelButton}"/> <Style.Triggers> <Trigger Property="IsPressed" Value="True"> <Setter Property="Template" Value="{DynamicResource CTGelButtonPressed}"/> </Trigger> </Style.Triggers> </Style>

 
 

Applying the style produces a different look & feel for when the button is depressed

<Button Content="Hello World" RenderTransformOrigin="0.625,2.55" Grid.Row="1" Margin="50,21,128,92" Style="{DynamicResource StyleGelButton}"/> image

 

In Conclusion

While the examples above are fairly simplistic, I hope for those of you who have been trying to gain an initial understanding of control templates, the information above will provide a good starting point. In future posts I will talk about some interesting ways to template the controls that come with WPF, as well as authoring your own controls to be templatable.

Comments

  • Anonymous
    December 27, 2007
    In several of my discussions with customers around WPF in the recent past, I got asked a bunch of questions

  • Anonymous
    December 27, 2007
    Excellent summary, I really like the way you state why the content model should be considered in thinking about control templates. A little more of the haziness is gone now!

  • Anonymous
    December 28, 2007
    Thanks a ton for such an informative post.

  • Anonymous
    December 30, 2007
    AJAX ASP.NET AJAX Control Toolkit - Basic Sample For DynamicPopulate Control [Via: alikl ] ASP.NET ...

  • Anonymous
    December 30, 2007
    Link Listing - December 30, 2007

  • Anonymous
    January 09, 2008
    This is was a great explanation at exactly the right time :). Thank you very much!

  • Anonymous
    May 15, 2008
    Excellent. Thanks. Theres in not papers like this on internet. Thank you very much.

  • Anonymous
    May 24, 2008
    I got an email the other day from a friend who was having some trouble getting the WPF TreeView to do

  • Anonymous
    May 24, 2008
    I got an email the other day from a friend who was having some trouble getting the WPF TreeView to do

  • Anonymous
    June 15, 2008
    I got an email the other day from a friend who was having some trouble getting the WPF TreeView to do

  • Anonymous
    July 12, 2008
    hi, i can i set the Label.Target=ContentPreseneter...where i am loading the contentpresener at runtime..pls help me.

  • Anonymous
    September 03, 2008
    A nice explanation of Control Templates. Use of control template and which element has template property wasn't clear to me before.

  • Anonymous
    October 29, 2008
    Katanya, software developer itu engga suka yang standar-standar untuk urusan control. Katanya pengen

  • Anonymous
    November 18, 2008
    Excellent inro to WPF control templating and triggers. Things are so clear to me now.

  • Anonymous
    December 03, 2008
    Hi jit thank you sooo much for such an excellent explanation on wpf control templates. That was realy helpful for me.. :-)

  • Anonymous
    February 03, 2009
    Is it possible to reference a control element defined within a controltemplate (i.e. through the code behind)? For instance, you give the ellispe in the CTGelButtonPressed control template an x:Name property of "ellipse". How do you access this ellipse through a button using that control template?

  • Anonymous
    February 03, 2009
    Kyle: Yes you can. Check this out. http://msdn.microsoft.com/en-us/library/bb613586.aspx

  • Anonymous
    February 27, 2009
    Application wide, I want to to let the designer's specify if data entry is mandatory at design time... I intent to use an attached property on the label called "IsMandatory". When IsMandatory=True, I would like a red asterisk to appear directly beside the label. I am trying to update my Label.XAML control template to have a ControlTemplate Trigger. I essentially, need to replace the entire template when this value is set at runtime. Here is a sample of my XAML.  This causes an exception at runtime.   I was hoping to use a combination of AttachedProperties and ControlTemplat. Is this possible? <Style x:Key="{x:Type Label}" TargetType="Label">    <Setter Property="HorizontalContentAlignment" Value="Left"/>    <Setter Property="VerticalContentAlignment" Value="Top"/>    <Setter Property="Template">      <Setter.Value>        <ControlTemplate TargetType="Label">          <Border>            <ContentPresenter              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"              RecognizesAccessKey="True"/>          </Border>          <ControlTemplate.Triggers>            <Trigger Property="IsEnabled" Value="false">                    <!--<Setter TargetName="Border" Property="Background" Value="{x:Null}"/>-->                    <!--<Setter TargetName="Border" Property="BorderBrush" Value="{x:Null}"/>-->                    <Setter  Property="Control.Template">                        <Setter.Value>                            <ControlTemplate>                                <DockPanel LastChildFill="True">                                    <TextBlock DockPanel.Dock="Right"                                        Foreground="Red"                                        FontSize="11pt"                                        FontWeight="Bold">*                                    </TextBlock>                                    <Border BorderBrush="Red" BorderThickness="1">                                        <AdornedElementPlaceholder Name="myControl"/>                                    </Border>                                </DockPanel>                            </ControlTemplate>                        </Setter.Value>                    </Setter>                </Trigger>          </ControlTemplate.Triggers>        </ControlTemplate>      </Setter.Value>    </Setter>  </Style>

  • Anonymous
    March 02, 2009
    Hi Tracy: What you are trying to do is achievable but not the way you are trying it. Instead of defining a Trigger within the control template, you can define a trigger inside the style (styles can have triggers too), and switch out the control template by resetting the "template" propery in that trigger.

  • Jit
  • Anonymous
    March 02, 2009
    Great post on WPF Control Templates by my teammate Jit Ghosh.

  • Anonymous
    April 08, 2009
    Thanks for the nice article. It's really helpful...

  • Anonymous
    April 22, 2009
    The comment has been removed

  • Anonymous
    June 16, 2009
    this is a great article. I'm rather new and practically unadvanced in WPF but it was easy to understand how control templates work and I think I know how to use them now. Thanx a lot!

  • Anonymous
    July 11, 2009
    really helpful thanks. I have my WPF MCTS exam in two days and am trying to go over my weaker areas, thanks to this article I feel more confident - about Control Templates at least ;-)

  • Anonymous
    April 14, 2010
    Wonderful article. Very informative. I really appreciate the owner for taking time and posting it.

  • Anonymous
    September 23, 2010
    This is a awesome article which gives me lot many idea about template control & triggers. Thanks a lot.

  • Anonymous
    November 10, 2010
    The explanation is very simple and can easily be understood. Thanks.

  • Anonymous
    September 07, 2011
    Article with simplest explanation of control template. Thank you for such a nice article.

  • Anonymous
    October 05, 2011
    Soundest explanation of how TemplateBinding should be used. Rest of the article is top noth too. Thanks