5.7. Control Templates
If we take a closer look at our current tic-tac-toe game, we'll
see that the Button objects aren't quite doing the job for us. What
tic-tac-toe board has rounded inset corners (Figure
5-14)?
What we really want here is to be able to keep the behavior of
the buttoni.e., holding content and firing click eventsbut we want to take over
the look of the
button. WPF allows this kind of thing because the intrinsic
controls are built to be looklessi.e., they provide behavior, but the
look can be swapped out completely by the client of the control.
Remember how we used data templates to provide the look of a
non-visual object? We can do the same to a control using a
control template, which is a set of storyboards, triggers, and, most
importantly, elements that provide the look of a control.
To fix our buttons' looks, we'll build ourselves a
control-template resource. Let's start things off in
Example 5-31 with a simple rectangle and worry about showing the actual
button content later.
Example 5-31. A minimal control template
<Window.Resources>
<ControlTemplate x:Key="ButtonTemplate">
<Rectangle />
</ControlTemplate>
...
<!-- let's just try one button for now... -->
<Button Template="{StaticResource ButtonTemplate}" ... />
...
</Window.Resources>
Figure 5-15 shows
the results of setting a single button's Template property.
Notice that no vestiges of what the button used to look like
remain in Figure 5-15.
Unfortunately, no vestige of our rectangles can be seen, either. The problem is
that, without a fill explicitly set, the rectangle's fill defaults to
transparent, showing the grid's black background. Let's set it to our other
favorite Halloween color instead:
<ControlTemplate x:Key="ButtonTemplate">
<Rectangle Fill="Orange" />
</ControlTemplate>
Now we're getting somewhere, as
Figure 5-16 shows.
Notice how square the corners are? Also, if you click, you won't
get the depression that normally happens with a button (and I don't mean "a sad
feeling").
5.7.1. Control Templates and Styles
Now that we're making some progress on the control template,
let's replicate it to the other buttons. We can do so by setting each button's Template
property by hand or, as is most common, we can bundle the control template with
the button's style, as in Example
5-32.
Example 5-32. Putting a control template into a style
<Window.Resources>
<Style TargetType="{x:Type Button}">
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Fill="Orange" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...
</Window.Resources>
...
<!-- No need to set the Template property for each button -->
<Button ... x:Name="cell00" />
...
As Example 5-32
shows, the Template property is the same as any other and can be set
with a style. Figure 5-17 shows
the results.
Still, the orange is kind of jarring, especially since the
settings on the style call for a white background. We can solve this problem
with template bindings.
5.7.2. Template Binding
To get back to our white buttons, we could hardcode the
rectangle's fill to be white, but what happens when a style wants to change it
(such as in the animation we've now broken)? Instead of hardcoding the fill of
the rectangle, we can reach out of the template into the properties of the
control using template binding, as in
Example 5-33.
Example 5-33. Template binding to the Background
property
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="White" />
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Key="ButtonTemplate">
<Rectangle Fill="{TemplateBinding Property=Background}" />
</ControlTemplate>
</Setter.Value>
</Setter>
...
</Style>
A template binding
is like a data binding, except that the properties to bind come from the
control whose template you're replacing (called the templated
parent ). In our case, things like Background,
HorizontalContentAlignment, and so on, are fair game for template
binding from the parent. And, like data binding, template bindings are smart
enough to keep the properties of the items inside the template up to date with
changing properties on the outside, as set by styles, animations, etc. For
example, Figure 5-18 shows
the effect of aliasing the rectangle's Fill property to the button's Background
property with our click animation and mouse-over behavior still in place.
We're not quite through yet, however. If we're going to change
the paint swatch that Figure 5-18
has become into a playable game, we have to show the moves. To do so, we'll
need a content presenter.
5.7.3. Content Presenters
If you've ever driven by a billboard or a bench at a bus stop
that says "Your advertisement here!" then that's all you need to know to
understand content presenters. A content presenter
is the WPF equivalent of "your content here" that allows content held by a ContentContainer
control to be plugged in at runtime.
In our case, the content is the visualization of our PlayerMove
object. Instead of reproducing all of that work inside of the button's new
control template, we'd just like to drop it in at the right spot. The job of
the content presenter is to take the content provided by the templated parent
and do all of the things necessary to get it to show up properly, including
styles, triggers, etc. The content presenter itself can be dropped into your
template wherever you'd like to see it (including multiple times, if it tickles
your fancye.g., to produce a drop shadow). In our case, we'll compose a content
presenter in Example 5-34 with
the rectangle inside a grid using techniques from
Chapter 2.
Example 5-34. A content presenter
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="White" />
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Rectangle Fill="{TemplateBinding Property=Background}" />
<ContentPresenter
Content="{TemplateBinding Property=ContentControl.Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
...
</Style>
In Example 5-34,
the content presenter's Content property is bound to the ContentControl.Content
property so that content comes through. As with styles, we can avoid prefixing
template binding property names with classes by setting the TargetType
attribute on the ContentTemplate element:
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Rectangle Fill="{TemplateBinding Property=Background}" />
<ContentPresenter
Content="{TemplateBinding Property=Content}" />
</Grid>
</ControlTemplate>
Further, with the TargetType property in place, you can
drop the explicit template binding on the Content property altogether,
as it's now set automatically:
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Rectangle Fill="{TemplateBinding Property=Background}" />
<!-- with TargetType set, the template binding for the -->
<!-- Content property is no longer required -->
<ContentPresenter />
</Grid>
</ControlTemplate>
The content presenter is all we need to get our game back to
being functional, as shown in Figure
5-19.
5.7.4. The Real Work
The last little bit of work is getting the padding right. Since
the content presenter doesn't have its own Padding property, we can't
bind the Padding property directly (it doesn't have a Background
property, either, which is why we used Rectangle and its Fill
property). For properties that don't have a match on the content presenter, you
have to find mappings or compose the elements that provide the functionality
that you're looking for. For example, Padding is an amount of space
inside of a control. Margin, on the other hand, is the amount of space
around the outside of a control. Since they're both of the same type, System.Windows.Thickness,
if we could map the Padding from the inside of our button to the
outside of the content control, our game would look very nice:
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="White" />
<Setter Property="Padding" Value="10,5" />
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Rectangle Fill="{TemplateBinding Property=Background}" />
<ContentPresenter
Content="{TemplateBinding Property=Content}"
Margin="{TemplateBinding Property=Padding}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
...
</Style>
Figure 5-20 shows
our completed tic-tac-toe variation.
Like the mapping between Padding and Margin,
building up the elements that give you the look you want and binding the
appropriate properties from the templated parent is going to be a lot of the
work of creating your own control templates.
 |