A DataTemplate allows you to customize the display of content in a ContentControl. The ControlTemplate—which you can set to the Template property of any Control—allows you to customize the appearance of the control itself—what’s commonly referred to as the control “chrome.” These two different purposes are summarized in the following table:
Property | Property Type | Purpose |
---|
Template | ControlTemplate | customizes display of control “chrome” |
ContentTemplate | DataTemplate | customizes display of content |
Keep in mind that the ContentTemplate property is defined by ContentControl and is only something you’ll find only in classes that derive from ContentControl. But the Template property is defined by Control, and it’s presence is perhaps the primary distinction between controls and FrameworkElement derivatives like TextBlockImage. and
Whenever you think you need a
custom control, you should ask yourself if it is truly a new control you
need, or if it’s merely a new look for an existing control. For
example, suppose you need a control that has a particular appearance,
and when you tap it, it changes appearance, and then you tap it again,
it goes back to the original appearance. This is a ToggleButton with just different visuals—a different ControlTemplate.
As with styles, very often templates are defined as resources. Also as with Style, ControlTemplate requires a TargetType:
<ControlTemplate x:Key="btnTemplate" TargetType="Button">
. . .
</ControlTemplate>
It is very common to see a Template defined as part of a Style:
<Style x:Key="btnStyle" TargetType="Button">
<Setter Property="Margin" Value="6" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
. . .
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Notice that property-element syntax is used for the Setter that sets the Template property to an object of type ControlTemplate.
Defining a template as part of a style is a very common approach
because generally you want to set some properties of the control to make
them more conducive with the template you’re building. These Setter
tags effectively redefine the default property values for the styled
and templated control, but they can still be overridden with local
settings on the actual control.
Let’s create a custom Button. This new Button will retain the full functionality of the familiar Button except that you (the programmer) will have total control over its appearance. Of course, to keep it simple, the new Button won’t look all that different from the normal Button! But it will show you the concepts involved.
Here’s a standard Button with text content and alignment set so it takes up only as much space as it needs to display that content:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
</Button>
To experiment with the ControlTemplate with greatest ease, let’s not define the ControlTemplate as a resource but break out the Template property as a property-element of the Button and set that to a ControlTemplate:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button.Template>
<ControlTemplate TargetType="Button">
</ControlTemplate>
</Button.Template>
</Button>
As soon as you set the Template property to an empty ControlTemplate, the button itself disappears. A visual tree defining the appearance of the control no longer exists. That visual tree is what you’ll be putting in the ControlTemplate. Just to make sure that we haven’t done anything serious damaging, let’s stick a TextBlock in the ControlTemplate:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button.Template>
<ControlTemplate TargetType="Button">
<TextBlock Text="temporary" />
</ControlTemplate>
</Button.Template>
</Button>
Now the Button
consists solely of the word “temporary.” It doesn’t have any visual
feedback when you touch it, but otherwise it’s a fully functional
button. It’s seriously flawed, of course, because the Button should really be displaying “Click me!” but that will be fixed soon.
You can put a Border around the TextBlock:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{StaticResource PhoneAccentBrush}"
BorderThickness="6">
<TextBlock Text="temporary" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
Here’s what it looks like:
But it’s really not a good idea to hard-code property
values like this in the template, particularly if you’re going to be
sharing that template in multiple controls. It particularly doesn’t make
sense to hard-code BorderBrush and BorderThickness in the template because the Control class itself defines BorderBrush and BorderThickness properties, and if we really want a border around the button, we should set those properties in the Button
rather than the template because we might want to share this template
among multiple buttons and set different border brushes and border
thickness.
So, let’s move those properties from the template to the button itself:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center"
BorderBrush="{StaticResource PhoneAccentBrush}"
BorderThickness="6">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border>
<TextBlock Text="temporary" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
Unfortunately, now we’ve lost the properties in the visual
tree of the template, so the border has now disappeared and it doesn’t
seem like much of an improvement. The border in the template in not
automatically inheriting the properties of BorderBrush and BorderThickness set on the button. Those are not inheritable properties.
What we need is a binding so the properties of the Border in the template get set from properties in the Button. It’s a special type of binding that has its own markup extension. It’s called a TemplateBinding:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center"
BorderBrush="{StaticResource PhoneAccentBrush}"
BorderThickness="6">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock Text="temporary" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
What the TemplateBinding means is that the properties of this particular element in the visual tree of the template—specifically, the BorderBrush and BorderThickness properties of the Border element—are bound to values set on properties in the control itself. The Button now has a border colored with the accent brush:
The TemplateBinding
is syntactically very simple. It always targets a dependency property
in the visual tree of the template. It always references a property of
the control on which the template is applied. There is nothing else that
can go in the TemplateBinding markup extension. TemplateBinding is only found in visual trees defined within a ControlTemplate.
On the other hand, there is nothing all that special about TemplateBinding. It is actually a shortcut, and when you see the longer version, you’ll be glad it exists. The attribute setting
BorderBrush="{TemplateBinding BorderBrush}"
is a short-cut for
BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=BorderBrush}"
This binding is on a Border, and the RelativeSource syntax refers to another element in the tree relative to this Border. The TemplatedParent is the Button on which the template is applied, so the binding is referencing the BorderBrush of that Button. (Makes sense, no?) You’ll want to use this alternative to TemplateBinding if you need to establish a two-way binding on a property in the template because TemplateBinding is one-way only and doesn’t allow a Mode setting.
Back to the template at hand: Now that we’ve established a TemplateBinding on the BorderBrush and BorderThickness, another issue has arisen. Perhaps we’ve decided that we want this Button to have a border that is 6 pixels wide colored with the accent brush, but you’ll only get those values if the Button contains explicit settings of the BorderBrush and BorderThickness properties. It would be nice if these properties did not need to be set on the Button. In other words, we want the Button to have default values of these properties that might be overridden by local settings.
This can be done by setting the desired default properties in a Style. For convenience, I’ve defined such a Style directly on the Button:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button.Style>
<Style TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource PhoneAccentBrush}" />
<Setter Property="BorderThickness" Value="6" />
</Style>
</Button.Style>
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock Text="temporary" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
Now the template picks up some default values from the Style
but those settings can be overridden by setting them locally on the
button. (If you don’t want the properties to be overridden by local
settings—if you want the properties to always have specific values—then
by all means hard-code them directly in the template.)
It is very common to define the Template property as part of a Style, like so:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button.Style>
<Style TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource PhoneAccentBrush}" />
<Setter Property="BorderThickness" Value="6" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock Text="temporary" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
Now the Style sets default values for properties also used by the template. Let’s add a Background property to the Border and give it a default value as well:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button.Style>
<Style TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource PhoneAccentBrush}" />
<Setter Property="BorderThickness" Value="6" />
<Setter Property="Background" Value="{StaticResource PhoneChromeBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<TextBlock Text="temporary" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
On the other hand, perhaps we want our newly designed button to have rounded corners on the Border. We know that the Button does not define a CornerRadius property, so it can be set to an explicit value right in the template:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button.Style>
<Style TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource PhoneAccentBrush}" />
<Setter Property="BorderThickness" Value="6" />
<Setter Property="Background" Value="{StaticResource PhoneChromeBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="12">
<TextBlock Text="temporary" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
Here’s what we’re up to so far:
The button still displays the text “temporary” and it should really be displaying the text “Click me!” You might be tempted to put a TextBlock in there and set its Text property to a TemplateBinding of the Content property of the Button:
<TextBlock Text="{TemplateBinding Content}" />
This actually works in this example, but it’s very, very wrong. The problem is that the Content property of the Button is of type object. We can set it to anything—an Image, a Panel, a Shape, a RadialGradientBrush, and then the TextBlock would have a little problem.
Fortunately, there is a class in Silverlight that exists specifically to display content in a ContentControl derivative. That class is called ContentPresenter. It has a property named Content of type object, and ContentPresenter displays that object regardless whether it’s a text string or any other element:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button.Style>
<Style TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource PhoneAccentBrush}" />
<Setter Property="BorderThickness" Value="6" />
<Setter Property="Background" Value="{StaticResource PhoneChromeBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="12">
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
Notice how the Content property of the ContentPresenter is bound to the Content property of the Button. The ContentPresenter has the distinct advantage of working for any kind of object. The ContentPresenter might create its own visual tree; for example, if the Content is of type string, then the ContentPresenter creates a TextBlock to display that string. The ContentPresenter is also entrusted with the job of building a visual tree to display content based on a DataTemplate set to the Control. For this purpose, the ContentPresenter has its own ContentTemplate property that you can bind to the ContentTemplate of the control:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button.Style>
<Style TargetType="Button">
<Setter Property="BorderBrush" Value="{StaticResource PhoneAccentBrush}" />
<Setter Property="BorderThickness" Value="6" />
<Setter Property="Background" Value="{StaticResource PhoneChromeBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="12">
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
These two TemplateBinding settings on ContentPresenter
are so standard that they are not required to be explicitly set! They
will be set for you. I feel more comfortable seeing them explicitly set,
however.
You may recall that the Control class defines a property named Padding that is intended to provide a little breathing room around the control’s content. Try setting the Padding property of the Button:
<Button Content="Click me!"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Padding="24">
. . .
</Button>
Nothing happens. The visual tree in the template needs to accommodate this Padding property. It needs to leave some space between the Border and the ContentPresenter. How can this be done? One solution is to use a TemplateBinding on the Padding property of the Border. But if there’s some other stuff in the Border besides the ContentPresenter that’s not going to work right. The standard approach is to set a TemplateBinding on the Margin property of the ContentPresenter:
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}" />
You don’t need to set a Padding value on the Button for this to have an effect. The theme Style for the Button defines a Padding value that seems to work well with this Button, even with the rounded Border corners.
Now try setting the HorizontalAlignment and VerticalAlignment properties of the Button to Stretch. These work fine, so that’s something you don’t have to worry about in the template. Similarly, you can set the Margin property of the Button and that’s still recognized by the layout system.
But when you set the HorizontalAlignment and VerticalAlignment properties of the Button to Stretch, you’ll discover that the content of the Button is at the upper-left corner:
Control defines two properties named HorizontalContentAlignment and VerticalContentAlignment that are supposed to govern how the content is aligned within the ContentControl. If you set these properties on the button, you’ll discover that they don’t work.
This tells us that we need to add something to the template to handle these properties. We have to align the ContentPresenter within the Border based on the HorizontalContentAlignment and VerticalContentAlignment properties. This is accomplished by providing TemplateBinding markup targeting the HorizontalAlignment and VerticalAlignment properties of the ContentPresenter:
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
Again, this is very standard markup for a ContentPresenter. It’s copy-and-paste stuff.
If you set the font-related properties or the Foreground property on the Button, you’ll find that the text changes accordingly. These properties are inherited through the visual tree of the template and you don’t need to do anything in the template to accommodate them. (However the theme Style for the Button explicitly sets the Foreground, FontFamily, and FontSize properties, so the Button itself cannot inherit these properties through the visual tree, and there is apparently nothing you can do in a custom Style to change this behavior.)