Chapter 9. Control Templates

Styles, as described in Chapter 8, are great if the changes you’d like to make to a control’s look can be adjusted by the control’s properties (according to your keen aesthetic sense), but what if the control author didn’t leave you enough knobs to get the job done? Rather than diving in to build a custom control, as other presentation libraries would have you do, WPF provides the ability to replace the complete look of the built-in controls while maintaining the existing behavior.

Beyond Styles

Recall from Chapter 8 that we built a nice little tic-tac-toe game. However, if we take a closer look at it, we’ll see that the Button isn’t quite doing the job for us. What tic-tac-toe board has rounded inset corners (Figure 9-1)?

Tic-tac-toe boards don’t have rounded insets!
Figure 9-1. Tic-tac-toe boards don’t have rounded insets!

What we really want here is to be able to keep the behavior (i.e., holding content and firing click events), but to take over the look of it. WPF allows this kind of thing because the intrinsic controls are built to be lookless (i.e., they provide behavior, but the control’s user can swap out the look completely). The default look comes from the system-provided template, as described in Chapter 12.

Remember from Chapter 6 and Chapter 8 how we used data templates to provide the look of a nonvisual object? We can do the same to a control using a control template—a set of triggers, resources, and most important, 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 with a simple rectangle (see Example 9-1).

Example 9-1. A minimal control template
<!-- let's just try one button for now... -->
<Button Margin="0,0,2,2" Grid.Row="0" Grid.Column="0" Name="cell00">
   <Button.Template>
      <ControlTemplate>
          <Grid>
               <Rectangle />
           </Grid>
      </ControlTemplate>
    </Button.Template>
</Button>

Figure 9-2 shows the results of setting a single button’s Template property.

Replacing the control template with something less visual than we’d like...
Figure 9-2. Replacing the control template with something less visual than we’d like...

Notice that no vestiges of how the button used to look remain in Figure 9-2. Unfortunately, we can see no vestige of our rectangle, either. The problem is that without a fill explicitly set, the rectangle defaults to no fill, showing the grid’s black background. Let’s set it to our other favorite Halloween color instead:

<ControlTemplate>
  <Rectangle Fill="Orange" />
</ControlTemplate>

Now we’re getting somewhere, as Figure 9-3 shows.

Replacing the button’s control template with an orange rectangle ()
Figure 9-3. Replacing the button’s control template with an orange rectangle (Figure F-13)

Notice how square the corners are now? Also, if you click, you won’t get the depression that normally happens with a button (and I don’t mean “a sad feeling”). We have taken complete control over the look of the button or, to paraphrase some ancient pop culture, “all your button are belong to us . . .”

Control Templates and Styles

Now that we’re making some progress on the control template, let’s replicate it to the other buttons. We could do that by setting each button’s Template property by hand, either to a copy of the control template or with a reference to a ControlTemplate element that’s been created in a Resource element. However, it’s often most convenient to bundle the control template with the button’s style, as Example 9-2 illustrates.

Example 9-2. 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 ... Name="cell00" />
...

As Example 9-2 shows, the Template property is the same as any other and can be set with a style. Figure 9-4 shows the results.

Spreading the orange ()
Figure 9-4. Spreading the orange (Figure F-14)

Here we have the classic crosshatch we’ve been aiming for, but the orange is kind of jarring. What if the Button object’s Background property was set to something more reasonable (maybe white?) and we’re ignoring it, favoring colors from scary holidays not known for their design sense? We can solve this problem with template bindings.

Template Binding

If we wanted white buttons, we could hardcode the rectangle’s fill to be white, but what happens when a style wants to change it (maybe somebody really wants an orange tic-tac-toe board)? Instead of hardcoding the fill of the rectangle, we can reach out of the template into the properties of the control by using template binding, as shown in Example 9-3.

Example 9-3. Template binding to the Background property
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="White" />
  ...
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate>
        <Rectangle Fill="{TemplateBinding Property=Background}"
 />
      </ControlTemplate>
    </Setter.Value>
</Style>

Template binding is like data binding, except that the properties to bind come from the control whose template you’re replacing (called the templated parent). In our case, any dependency property on the Button class is fair game as a template binding source. And like data binding, template binds 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, and so on.

If you need the expanded options provided by a full binding, you use a Binding object inside a template with a RelativeSource of TemplatedParent to indicate how to resolve the Path (see Example 9-4).

Example 9-4. Binding inside a template using a RelativeSource of TemplatedParent
<Style TargetType="{x:Type Button}">
  <Setter Property="Background" Value="White" />
  ...
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate>
        <Rectangle
          Fill="{Binding Path=Background,
                         RelativeSource={RelativeSource TemplatedParent}}" />
      </ControlTemplate>
    </Setter.Value>
</Style>

You should choose template binding over standard binding inside a template if it meets your needs, as template binding is optimized for just that use.

If you like, you can separate the control template from the style into a separate resource altogether:

<ControlTemplate x:Key="ButtonTemplate"
>
  <Grid>
    <Rectangle Fill="{TemplateBinding Property=Button.Background
}" />
  </Grid>
</ControlTemplate>
<Style TargetType="{x:Type Button}">
  ...
  <Setter
    Property="Template"
    Value="{StaticResource ButtonTemplate}" />
</Style>

As with styles, we can avoid prefixing template binding property names with classes by setting the TargetType attribute on the ControlTemplate element:

<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
  <Grid>
    <Rectangle Fill="{TemplateBinding Property=Background}" />
  </Grid>
</ControlTemplate>

We’re not quite through with our tic-tac-toe board yet, of course. If we’re going to change the study in pumpkin that Figure 9-4 has become into a playable game, we have to show the moves. To do that, we’ll need a content presenter.

Content Presenters

If you’ve ever driven by a billboard or a bus-stop bench that says “Your advertisement here!” 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 ContentControl 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 the button’s new control template, we’d just like to plug 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, and so on. You can drop the content presenter itself into your template wherever you’d like to see it. For this application, we’ll compose a content presenter with the rectangle inside a grid, using techniques from Chapter 3:

<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, we can drop the explicit template binding on the Content property altogether, as it can be 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>

Tip

I used the Grid here because it’s an obvious way to compose the Rectangle and the ContentPresenter together into one cell that takes up the entire available space. However, I also used it to illustrate a possible performance issue.

When you’re building control templates, you’ve got to keep in mind that they’re likely to be used in multiple places—sometimes hundreds of places. Every element you include will be used each time your control template is expanded, so you want to make sure to use the minimum number of elements.

For example, in our simple control template, there’s no reason to have a Rectangle to share the same cell in the Grid just to give the ContentPresenter a background color—instead, we can just use a Border, which has a background color and can contain our ContentPresenter. And because the Border is only one element, we don’t need to use the Grid to arrange it. An optimized version of this template looks like this:

<ControlTemplate TargetType="{x:Type Button}">
  <Border Background="{TemplateBinding Property=Background}">
    <ContentPresenter />
  </Border>
</ControlTemplate>

For the purposes of our example, the control template is expanded only nine times, so there’s no problem, but you should keep element count in mind when you’re composing your content templates.

The content presenter is all we need to get our game back to being functional, as shown in Figure 9-5.

Adding a content presenter to our control template ()
Figure 9-5. Adding a content presenter to our control template (Figure F-15)

The last little bit of work in our sample is to get the padding to work. Because 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 the 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 you’re looking for. For example, Padding is an amount of space inside a control. Margin, on the other hand, is the amount of space around the outside of a control. Because 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 presenter, [56] our game would look very nice:

<ControlTemplate TargetType="{x:Type Button}">
  <Grid>
    <Rectangle Fill="{TemplateBinding Property=Background}" />
    <ContentPresenter Margin="{TemplateBinding Property=Padding}"/>
  </Grid>
</ControlTemplate>
<Style TargetType="{x:Type Button}">
  <Setter Property="Background" Value="White" />
  <Setter Property="Padding" Value="8" />
  <Setter Property="Template" Value="{StaticResource ButtonTemplate}" />
  ...
</Style>

Figure 9-6 shows our completed tic-tac-toe variation.

Binding the Padding property to the Margin property
Figure 9-6. Binding the Padding property to the Margin property

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.

Template Triggers

Just like styles, control templates support triggers. These let us set up actions in the template itself, regardless of what other triggers the content of the control may or may not also have. For example, if we wanted to add a glow to our buttons as the user hovers, we can do so with a template trigger, as Example 9-5 illustrates.

Example 9-5. Control template triggers
<Style TargetType="{x:Type Button}">
  ...
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type Button}">
        <Grid>
          <Rectangle Fill="{TemplateBinding Property=Background}"
                     Name="rect" />
          <ContentPresenter Margin="{TemplateBinding Property=Padding}" />
        </Grid>
        <ControlTemplate.Triggers>
          <Trigger Property="IsMouseOver" Value="True">
            <Setter TargetName="rect" Property="BitmapEffect">
              <Setter.Value>
                <OuterGlowBitmapEffect GlowColor="Yellow" GlowSize="10" />
              </Setter.Value>
            </Setter>
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
  ...
</Style>

In Example 9-5, we’re setting a yellow glow whenever the mouse is hovered over the rectangle that fills the button. We’re using a property trigger, so the value we’re watching for is a property on the control itself (the IsMouseOver property, to be precise). However, we don’t want to set a property on the button; instead, we want to set the BitmapEffect property on some inner part of the template (the rectangle, in our case). This is a very common thing to want to do, and because of that, a Setter object inside a control template allows an extra property to be set that can’t be set in a style’s Setter: the TargetName property. The TargetName is the name of some element in the template on which we’d like to set a property (e.g., the element named rect in our example).

Figure 9-7 shows the effect in all its glory.

A control template trigger in action ()
Figure 9-7. A control template trigger in action (Figure F-16)

Extending Templates

Take another look at the glow effect in which we swaddled our buttons:

<OuterGlowBitmapEffect GlowColor="Yellow" GlowSize="10" />

Do you notice a problem we’ve run into before? That’s right—in the same way we were hardcoding the orange fill color a few pages ago, now we’re hardcoding the glow color and size. “Oh,” you think. “That’s no problem. I’ll just do what I did before and map the appropriate properties of the Button class to the GlowColor and GlowSize properties in the template.” And I applaud you in the application of your recent learnin', but there ain’t no properties on the Button that map to “glow.” In fact, it is often the case in building control templates that there are more variables you’d like to expose than there are properties on the control being “templated.”

Repurposing an existing property

One popular technique to let us default a custom property for use by the control template is to hijack an existing property for our purposes, as shown in Example 9-6.

Example 9-6. Extending a template by repurposing an existing property
<Style TargetType="{x:Type Button}">
  ...
<Setter Property="Tag">
  <Setter.Value>
    <OuterGlowBitmapEffect GlowColor="Yellow" GlowSize="10" />
 </Setter.Value>
</Setter>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type Button}">
        <Grid>
          ...
        </Grid>
        <ControlTemplate.Triggers>
          <Trigger Property="IsMouseOver" Value="True">
            <Setter TargetName="rect" Property="BitmapEffect"
                       Value="{Binding Path=Tag,
                         RelativeSource={RelativeSource TemplatedParent}}" />
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
...
<!-- use default bitmap effect set in the style -->
<Button Margin="2,0,2,2" Grid.Row="0" Grid.Column="1" Name="cell01" />
...
<!-- use custom bitmap effect, overriding the style's default -->
<Button Margin="2,2,2,2" Grid.Row="1" Grid.Column="1" Name="cell11">
  <Button.Tag>
    <BevelBitmapEffect BevelWidth="10" />
  </Button.Tag>
</Button>
...

In Example 9-6, our Button style uses the Tag property to pass in a bitmap effect object to use when the mouse is overhead. The control template’s trigger uses the value of the Tag property when the IsMouseOver property is True. Notice that we’re using normal binding (with the TemplatedParent RelativeSource) instead of template binding because the normal binding object has casting support at runtime, whereas template binding checks the types statically at compile time. The use of a normal binding enables us to pull a BitmapEffect out of the Tag property, which is of type Object.

When we create a button using this style, the Tag value acts as a default value, which we can override with any bitmap effect that tickles our fancy (as shown on the middle button in Example 9-6).

Defining a custom dependency property

The problem with repurposing any of the Button’s properties is that somebody might actually use the one you pick for something (e.g., the Tag property is generally a place to store app-specific data). If this is a worry, the safest thing to do (short of defining your own custom control type) is to take a page from Chapter 18 and define your own custom dependency property:

namespace TicTacToe {
  public class MouseOverEffectProperties {
    public static DependencyProperty MouseOverEffectProperty;
    static MouseOverEffectProperties(  ) {
      OuterGlowBitmapEffect defaultEffect = new OuterGlowBitmapEffect(  );
      defaultEffect.GlowColor = Colors.Yellow;
      defaultEffect.GlowSize = 10;
      MouseOverEffectProperty =
        DependencyProperty.RegisterAttached(
          "MouseOverEffect",
          typeof(BitmapEffect),
          typeof(MouseOverEffectProperties),
          new PropertyMetadata(defaultEffect));
    }
  }
    public static BitmapEffect
      GetMouseOverEffect(DependencyObject target) {

      return (BitmapEffect)target.GetValue(MouseOverEffectProperty);
    }

    public static void
      SetMouseOverEffect(DependencyObject target, BitmapEffect value) {

      target.SetValue(MouseOverEffectProperty, value);
    }
}

Notice that this dependency property is registered as an attached property that can be attached to any DependencyObject. Also notice that it has a built-in default, which simplifies our style, as it needs to list a value for our new property only if it wants to override the default. The static GetMouseOverEffect and SetMouseOverEffect methods allow us to set the property value on any dependency object, including our buttons. With this dependency property in place, we can write our control template trigger as shown in Example 9-7.

Example 9-7. Using the custom attached dependency property to pass extra info
<Window ... xmlns:local="clr-namespace:TicTacToe">
  ...
  <ControlTemplate.Triggers>
    <Trigger Property="IsMouseOver" Value="True">
      <Setter ...
        Property="BitmapEffect"
        Value="{Binding
 Path=(local:MouseOverEffectProperties.MouseOverEffect),
                 RelativeSource={RelativeSource TemplatedParent}}" />
    </Trigger>
  </ControlTemplate.Triggers>
  ...
  <Button ...
    Tag="howdy"
    local:MouseOverEffectProperties.MouseOverEffect="{x:Null}">
  ...
</window>

Notice that the Path expression in the Binding is surrounded by parentheses, which, as you’ll recall from Chapter 6, means an explicit dependency property reference. Notice also that we’ve defined an XML namespace pointing to the CLR namespace where the class lives and that we use this to specify the path to the dependency property. However, instead of using the name of the dependency property field (which has a “Property” suffix), we use the name we registered with the RegisterAttached method (which doesn’t have the “Property” suffix).

Also notice how Example 9-7 overrides the default value for the property by setting the attached property on an individual button (to null, in this example), while taking advantage of the newly available Tag property for a friendly Western U.S. greeting.

Tip

Instead of creating a new attached dependency property, you can use one of the existing ones, even if it has nothing to do with your control. However, it can be difficult to find an attached property that a) will never be used for anything else on your control; b) is of the correct type; and c) has a name that suggests some kind of semantic relationship with the use of that property in your control template. It does save code, though, if you can use an existing property.

The simple usage of your custom template properties will always be a custom control that has those properties built in, of course. I recommend checking out Chapter 18 for more information about that.

The Control Template Contract

We haven’t been explicit about this yet, but controls expect their templates to provide certain features. The exact set of features varies from one control to the next, but a contract is always in effect between the control and the template. The control’s side of the contract is essentially the set of properties and commands it offers. The template’s side of the contract is less obvious, and is sometimes implicit.

Remember that a control’s job is to provide behavior. The control template provides the visuals. A control may provide a default set of visuals, but it should allow these to be replaced in order to offer the same flexibility as the built-in controls. If you need to provide both custom behavior and custom visuals, build two components: a control, and an element designed to be incorporated into the control’s template. A control that conforms to this approach—where the visuals are separated from the control—is often described as lookless, because the control has no intrinsic appearance or “look.” All of the controls built into WPF are lookless.

Of course, it is not possible for the control to be entirely independent of its visuals. Any control will impose some requirements that the template must satisfy if the control is to operate correctly. The extent of these requirements varies from one control to another. Button has fairly simple requirements: it needs nothing more than a placeholder in which to inject the content. The slider controls have much more extensive requirements: the visuals must supply two buttons (increase and decrease), the “thumb,” and a track for the thumb to run in. Moreover, they need to be able to respond to clicks or drags on any of these elements, and to be able to position the thumb.

There is an implied contract between any control type and the style or template. The control allows its appearance to be customized by replacing the visual tree, but the tree must in turn provide certain features on behalf of the control. The nature of the contract will depend on the control—the built-in controls use several different techniques depending on how tightly they depend on the structure of their visuals. The following sections describe the various ways in which a control and its template can be related.

Property binding

The loosest form of contract between control and template is where the control simply defines public properties, and allows the template to decide which of these properties to make visible using the TemplateBinding markup extension. The control does not care what is in the template.

This is effectively a one-way contract: the control provides properties and demands nothing in return. Despite this, such a control can still respond to user input if necessary—event routing allows events to bubble up from the visuals to the control. The control can handle these events without needing to know anything about the nature of the visuals from which they originated.

Named parts

Sometimes it is necessary for a control to locate specific elements in the template. For example, if you write a template for a ProgressBar, the control will look for two parts: the element that it should resize to indicate progress, and a second so-called “track” element that represents the full extent of the control. The control modifies the progress indicator part to be a proportion of the size of the track, according to the current progress. When the bar’s Value property is equal to the Maximum property, the indicator will be the same size as the track; when the Value is at Minimum, the indicator’s size will be zero; and for values in between, the size is interpolated appropriately.

The ProgressBar locates these two template parts by name. It will expect the template to contain an element named PART_Indicator, and another element named PART_Track. Example 9-8 shows a very simple control template with these parts.

Example 9-8. Control template with named parts
<ProgressBar Width="100" Height="25" Value="4" Maximum="10">
  <ProgressBar.Template>
    <ControlTemplate TargetType="{x:Type ProgressBar}">
      <Grid>
        <Rectangle Name="PART_Track" Fill="LightGray" Stroke="Black" />
        <Rectangle Name="PART_Indicator" HorizontalAlignment="Left"
                      Margin="2" RadiusX="5" RadiusY="5"
                      Fill="White" Stroke="Blue" />
      </Grid>
    </ControlTemplate>
  </ProgressBar.Template>
</ProgressBar>

Figure 9-8 shows the results. As you can see, the rectangle with the rounded corners and the white fill has been sized in proportion to the control’s Value—it’s filling about 40 percent of the space provided by the track.

ProgressBar with template
Figure 9-8. ProgressBar with template

The intrinsic WPF controls mark their part usage with the TemplatePartAttribute, which makes it handy to figure out which controls have which parts (assuming you’re handy with the metadata API in .NET). Table 9-1 shows the current set of template parts and their expected type for each WPF control.

Table 9-1. Controls with template parts

Control

Template part name

Expected type

ComboBox

PART_Popup

Popup

 

PART_EditableTextBox

TextBox

DocumentViewer

PART_ContentHost

ScrollViewer

 

PART_FindToolBarHost

ContentControl

FlowDocumentPageViewer

PART_FindToolBarHost

Decorator

FlowDocumentReader

PART_ContentHost

Decorator

 

PART_FindToolBarHost

Decorator

FlowDocumentScrollViewer

PART_FindToolBarHost

Decorator

 

PART_ToolBarHost

Decorator

 

PART_ContentHost

ScrollViewer

Frame

PART_FrameCP

ContentPresenter

GridViewColumnHeader

PART_HeaderGripper

Thumb

 

PART_FloatingHeaderCanvas

Canvas

MenuItem

PART_Popup

Popup

NavigationWindow

PART_NavWinCP

ContentPresenter

PasswordBox

PART_ContentHost

FrameworkElement

ProgressBar

PART_Track

FrameworkElement

 

PART_Indicator

FrameworkElement

ScrollBar

PART_Track

Track

ScrollViewer

PART_HorizontalScrollBar

ScrollBar

 

PART_VerticalScrollBar

ScrollBar

 

PART_ScrollContentPresenter

ScrollContentPresenter

Slider

PART_Track

Track

 

PART_SelectionRange

FrameworkElement

StickyNoteControl

PART_CopyMenuItem

MenuItem

 

PART_CloseButton

Button

 

PART_ResizeBottomRightThumb

Thumb

 

PART_IconButton

Button

 

PART_ContentControl

ContentControl

 

PART_TitleThumb

Thumb

 

PART_PasteMenuItem

MenuItem

 

PART_InkMenuItem

MenuItem

 

PART_SelectMenuItem

MenuItem

 

PART_EraseMenuItem

MenuItem

TabControl

PART_SelectedContentHost

ContentPresenter

TextBoxBase

PART_ContentHost

FrameworkElement

ToolBar

PART_ToolBarPanel

ToolBarPanel

 

PART_ToolBarOverflowPanel

ToolBarOverflowPanel

TreeViewItem

PART_Header

FrameworkElement

Content placeholders

Some controls expect to find a placeholder element of a certain type in the template. Controls that support the content model by deriving from ContentControl use the element type approach. They expect to find a ContentPresenter element in the template, as you’ve already seen.

Tip

In practice, this is a loosely enforced contract. A ContentControl will not usually complain if there is no ContentPresenter in the template. The control doesn’t absolutely depend on the content being presented in order to function.

In fact, some controls may require more than one placeholder. For example, controls derived from HeaderedContentControl require two—one for the body and one for the header. In this case, we can simply be explicit about which property the ContentPresenter presents, as Example 9-9 shows.

Example 9-9. ContentPresenter and HeaderedContentControl
<ControlTemplate TargetType="{x:Type local:MyContentControl}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>

        <ContentPresenter Grid.Row="0" Content="{TemplateBinding Content}" />
        <ContentPresenter Grid.Row="1" Content="{TemplateBinding Header}" />
    </Grid>
</ControlTemplate>

WPF defines two more placeholder types:

  • ScrollContentPresenter indicates where the content hosted by a scroll viewer will go.

  • You can use ItemsPresenter in an ItemsControl to indicate where generated items should be added.

In addition, if you don’t want to replace the entire control template, ItemsControl lets you replace bits and pieces of itself, which it references in its default templates (and which you can reference in your own custom ItemsControl templates). In fact, there are two other options for templates on an ItemsControl. The first we’ve already seen in Chapter 7: you can supply a DataTemplate as the ItemTemplate property and this will customize the appearance of each individual item. The second alternative is that you can set the ItemsPanel property. This allows you to customize just the panel used to lay out the list contents. This uses another template class: ItemsPanelTemplate. Notice that neither the ItemTemplate property nor the ItemsPanel property is of the ControlTemplate type, but anyone customizing an ItemsControl will want to be familiar with all of the template types that WPF provides.

Tip

At this point, we’ve rounded out the different kinds of templates available in WPF: data templates, hierarchical data templates, control templates, and items panel templates. Fundamentally, they’re all about expanding a template as required (and they all derive from the FrameworkTemplate base class), but the specifics are different and you can’t mix and match them.

The ItemsPanelTemplate lets you change the default panel that lays out items in the list:

<ListBox ItemsSource="{Binding}">
 <ListBox.ItemsPanel>
   <ItemsPanelTemplate>
       <StackPanel Orientation="Horizontal" />
  </ItemsPanelTemplate>
 </ListBox.ItemsPanel>
</ListBox>
<ComboBox ItemsSource="{Binding}">
 <ComboBox.ItemsPanel>
  <ItemsPanelTemplate>
    <UniformGrid />
  </ItemsPanelTemplate>
 </ComboBox.ItemsPanel>
</ComboBox>

In this code, we’ve replaced the vertical StackPanel provided by default in a ListBox with a horizontal one. This code also uses a UniformGrid to perform a grid layout of the list items in a combo box. These two changes produce the results you see in Figure 9-9.

The items panel template in action
Figure 9-9. The items panel template in action

You can use any type derived from the Panel class as the panel template, including a custom panel if you’ve written such a thing to perform custom layout. The interesting thing about using a panel in this way is that although none of the panels supports data binding directly (e.g., none of them has an ItemsSource property like an ItemsControl), the ItemsControl knows how to manage items in a panel, so it effectively gives you data binding over the panel of your choice.

Placeholders indicated by properties

Some controls look for elements marked with a particular property. For example, controls derived from ItemsControl, such as ListBox and MenuItem, support templates containing an element with the Panel.IsItemsHost property set to true. This identifies the panel that will act as the host for the items in the control. ItemsControl uses an attached property instead of a placeholder to allow you to decide what type of panel to use to host the items. (ItemsControl also supports the use of the ItemsPresenter typed placeholder element. This is used when the template does not wish to impose a particular panel type, and wants to use whatever panel the ItemsPanelTemplate has specified in the ItemsPanel property.) Example 9-10 is a sample.

Example 9-10. Using IsItemsHost to indicate the items host
<ListBox ItemsSource="{StaticResource items}" Width="120" Height="67">
  <ListBox.Template>
    <ControlTemplate TargetType="{x:Type ListBox}">
      <Border BorderThickness="1" BorderBrush="Black" CornerRadius="10">
        <ScrollViewer>
          <ScrollViewer.Clip>
            <RectangleGeometry Rect="0, 0, 118, 65" />
          </ScrollViewer.Clip>
          <VirtualizingStackPanel IsItemsHost="True" />
        </ScrollViewer>
      </Border>
    </ControlTemplate>
  </ListBox.Template>
</ListBox>

Example 9-10 shows the use of a full control template replacing the entire set of visuals for a ListBox. Notice that we have provided a ScrollViewer; the default ListBox template supplies one of these, so we need to provide our own if we want scrolling to work. Notice also that we’ve provided a panel with the IsItemsHost property set to True. We could have used the ItemsPresenter instead, as we mentioned earlier, if we wanted the ItemsPanel property to work. Instead, this sample ignores the ItemsPanel and uses the IsItemsHost property, indicating to the ItemsControl to which panel it should add the list items. In this case, we’ve used a VirtualizingStackPanel, a special form of StackPanel optimized for a large number of items in the data source.[57] This is the same panel type that the default template for a ListBox uses. Figure 9-10 shows the results.

Setting the IsItemsHost property
Figure 9-10. Setting the IsItemsHost property

If your goal is to re-create the default look and tweak it, you will want to use something like the template in Example 9-10. (Here we’re just tweaking the template by supplying a Clip geometry in order to make the control an unusual shape.) However, if you want to radically change the appearance, the ScrollViewer is optional. The only hard requirement is that you supply a panel with the IsItemsHost property set to True or that you provide an ItemsPresenter.

The use of properties to indicate a content placeholder is effectively equivalent to the named parts approach described earlier. However, the named parts approach is far more common—few of the built-in controls use this property-based approach. We describe it here mainly for completeness.

Special-Purpose Elements

Some controls define custom element types designed for use as a part of their template, which does more than merely marking the place where content is to be injected. For example, the Slider control requires the template to contain elements to represent the draggable thumb, and the clickable track in which the thumb runs. The control cannot function unless the template conforms to the required structure. To enforce this, Slider requires that the template contain elements of the special-purpose Thumb and Track types.

Neither of these control types is designed for use in isolation. To emphasize this, both Thumb and Track are defined in the System.Windows.Controls.Primitives namespace. The only places you would normally use Track are in the templates for a Slider or a ScrollBar. Thumb is slightly more general-purpose—you can use it anywhere you require something draggable. But it’s still designed to be used as part of something else, and is not a control in its own right.

The Track control defines a fixed structure for part of a control template. It has three properties that contain nested controls. DecreaseRepeatButton and IncreaseRepeatButton must contain RepeatButton controls—these represent the clickable areas to either side of the thumb. The Thumb property contains the Thumb control itself. The Track manages the sizes and positions of all three controls, ensuring that they reflect the current properties of the control at all times.

Example 9-11 shows this technique in action. Notice that the slider uses the named part idiom as well as special-purpose element types.

Example 9-11. Slider template using special-purpose elements
<Slider Width="100" Height="20" Value="20" Maximum="100">
  <Slider.Template>
    <ControlTemplate TargetType="{x:Type Slider}">
      <Track x:Name="PART_Track">
        <Track.DecreaseRepeatButton>
          <RepeatButton Content="&lt;" />
        </Track.DecreaseRepeatButton>
        <Track.Thumb>
          <Thumb Width="10" />
        </Track.Thumb>
        <Track.IncreaseRepeatButton>
          <RepeatButton Content="&gt;" />
        </Track.IncreaseRepeatButton>
      </Track>
    </ControlTemplate>
  </Slider.Template>
</Slider>

Figure 9-11 shows the rather unadventurous results. In a real application, you would also provide templates for the two repeat buttons and the thumb.

Customized slider
Figure 9-11. Customized slider

The benefit of this approach is that it allows you to enforce relationships between different parts of the control template. Sliders and scroll bars use the Track element to keep the Thumb correctly positioned and sized in relation to the two clickable regions that form the track. In addition, this approach enforces the fact that the clickable regions are, in turn, represented by RepeatButtons. The downside is that it is more complex for the developers using the control because anyone wishing to define a template for the control must discover and understand the multiple element types involved.

Examining the Built-in Templates

A lot of the examples in this section talked about how one WPF template does one thing, while another WPF template does something else. If you’re curious what the intrinsic WPF templates do, you can check out the ShowMeTheTemplate sample provided with this book, as seen in Figure 9-12.

The ShowMeTheTemplate tool
Figure 9-12. The ShowMeTheTemplate tool

On the lefthand side of the template tool are all of the framework elements that have template properties of any type. When one of them is selected, the template properties are shown on the right. For example, in Figure 9-12, we can see the Template property (of type ControlTemplate) of the GridSplitter. The templates shown on the right are produced by the XAML serializer, so you should be able to copy and paste them into your own code as a starting place if you’d prefer to tweak an existing template instead of starting over from scratch.

Logical and Visual Trees

The existence of templates leads to an API design dilemma that the WPF architects had to resolve. If a developer wishes to access the elements in the UI, should she see the fully expanded tree, containing all the instantiated templates? Although this would put the developer in full control, it might be rather cumbersome; often a developer only really cares that there is a Button present, not about the structure of its appearance. On the other hand, to present the simple pre-expansion view would be unnecessarily limiting.

To solve this problem, WPF lets you work with either the logical tree or the visual tree. The visual tree contains most[58] of the elements originally specified (either in markup or in code) plus all the extra elements added as a result of template instantiation. The logical tree is a subset of the visual tree that omits the elements added as a result of control template instantiation. WPF provides two helper classes for working with these two trees: VisualTreeHelper and LogicalTreeHelper.

For example, consider the following snippet of XAML:

<WrapPanel Name="rootPanel">
  <Button>_Click me</Button>
</WrapPanel>

Walking this logical tree at runtime using the LogicalTreeHelper looks like Example 9-12.

Example 9-12. Dumping the logical tree
public Window1(  ) {
  InitializeComponent(  );

  // Can dump the logical tree anytime after InitComp
  DumpLogicalTree(rootPanel, 0);
}

void DumpLogicalTree(object parent, int level) {
  string typeName = parent.GetType(  ).Name;
  string name = null;
  DependencyObject doParent = parent as DependencyObject;
  // Not everything in the logical tree is a dependency object
  if( doParent != null ) {
    name = (string)(doParent.GetValue(FrameworkElement.NameProperty) ?? "");
  }
  else {
    name = parent.ToString(  );
  }

  Debug.Write("                ".Substring(0, level * 2));
  Debug.WriteLine(string.Format("{0}: {1}", typeName, name));
  if( doParent == null ) { return; }

  foreach( object child in LogicalTreeHelper.GetChildren(doParent) ) {
    DumpLogicalTree(child, level + 1);
  }
}

Notice that we’re watching for objects that aren’t instances of the DependencyObject class (almost the lowest level in the WPF type hierarchy—only the DispatcherObject is lower). Not everything in the logical tree is part of the WPF type hierarchy (e.g., the string we pass in as the text content of the Button is going to stay a string when we examine it):

WrapPanel: rootPanel
  Button:
    String: _Click me

The code to walk the instantiated objects with the VisualTreeHelper is simpler because everything it encounters is at least a DependencyObject (see Example 9-13).

Example 9-13. Dumping the visual tree
protected override void OnContentRendered(EventArgs e) {
  base.OnContentRendered(e);

  // Need to wait for layout before visual tree is ready
  Debug.WriteLine("Visual tree:");
  DumpVisualTree(rootPanel, 0);
}

void DumpVisualTree(DependencyObject parent, int level) {
  string typeName = parent.GetType(  ).Name;
  string name = (string)(parent.GetValue(FrameworkElement.NameProperty) ?? "");
  Debug.Write("                ".Substring(0, level * 2));
  Debug.WriteLine(string.Format("{0}: {1}", typeName, name));

  for( int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i ) {
    DependencyObject child = VisualTreeHelper.GetChild(parent, i);
    DumpVisualTree(child, level + 1);
  }
}

In Example 9-13, you’ll notice that we’re careful to only walk the visual tree in the OnContentRendered event, which guarantees that at least a portion of the visual tree has been rendered. This is important, because the visual tree isn’t expanded until it needs to be. However, once it’s instantiated, the visual tree is considerably more verbose than the logical tree:

WrapPanel: rootPanel
  Button:
    ButtonChrome: Chrome
      ContentPresenter:
        AccessText:
          TextBlock:

The difference, of course, is that the button control template was instantiated. If you’d like to explore the visual tree produced by a bit of XAML interactively, I suggest the XamlPad tool that comes with the Windows Platform SDK. XamlPad lets you type in XAML and shows you the results as soon as you’ve entered valid XAML. It also has a button that will show you the visual tree of the XAML you’ve typed in. Figure 9-13 shows the visual tree for our sample XAML in a slightly nicer way.

Showing the visual tree inside XamlPad
Figure 9-13. Showing the visual tree inside XamlPad

Data-Driven UI

Templates enable a certain kind of UI programming sometimes called data-centric UI and sometimes called data-driven UI. The idea is that the data is the most important thing in our application, and through the use of declarative UI techniques (as enabled by XAML) we shape the data into something suitable for presentation to the user. Ideally, we do this without changing the underlying data at all, but by instead transforming the data on its way to the user as appropriate.

For example, getting back to our tic-tac-toe program, we haven’t been very data-driven at all, instead bundling up the manipulation of the data with the UI code (a.k.a. “putting your logic in the click handler”). A better way to start is to move our game logic into a class of its own that has nothing at all to do with the UI:[59]

class TicTacThree : INotifyPropertyChanged {
  public TicTacThree(int dimension) {...}
  public void NewGame(  ) {...}
  public void Move(int cellNumber) {...}
  public IEnumerable<Cell> Cells { get {...} }
  public string CurrentPlayer { get {...} }
  public bool HasWinner { get {...} }
}

class Cell : INotifyPropertyChanged {
  public Cell(int cellNumber) {...}
  public int CellNumber { get {...} }
  public PlayerMove Move { get {...} set {...} }
}

class PlayerMove : INotifyPropertyChanged {
  public string PlayerName { get {...} set {...} }
  public int MoveNumber { get {...} set {...} }
  public bool IsPartOfWin { get {...} set {...} }
  public PlayerMove(string playerName, int moveNumber) {...}
}

With this change, the code to hook up our UI to the new game logic is reduced significantly:

class Window1 : Window {
  TicTacThree game = new TicTacThree(3); // 3x3 grid
  public Window1(  ) {
    InitializeComponent(  );
    DataContext = game;
  }

  void cell_Click(object sender, RoutedEventArgs e) {
    Button cell = (Button)sender;
  int cellNumber = int.Parse(cell.Tag.ToString(  ));
  game.Move(cellNumber);
  if( game.HasWinner ) {
    MessageBox.Show("Winner!");
    game.NewGame(  );
 }
}
}

However, in addition to the styles and templates we’ve built up over the course of our discussion, our XAML is augmented with data binding to show the player moves and the current player:

<Window ...>
  ... <!— styles and templates as before —>
  <Button ... Tag="0" Content="{Binding Cells[0].Move}" Click="cell_Click" />
  <Button ... Tag="1" Content="{Binding Cells[1].Move}" Click="cell_Click" />
  ...
    <TextBlock ...>
      It's your move, <TextBlock Text="{Binding CurrentPlayer}" />
    </TextBlock>
</Window>

At this point, we’ve moved further toward data-driven UI, as we’ve separated the data manipulation out of the UI code and are relying on our styles, templates, and data binding to translate the data appropriately for user interaction. However, we have hardcoded knowledge about how much data we are going to get: nine cells. That works great until we add an option to create a tic-tac-toe game of 4 × 4 or 5 × 5 cells, at which point it’s clear we’re not as data-driven as we’d like to be. Luckily, it’s not hard to update our code-behind file:

class Window1 : Window {
  TicTacThree game = new TicTacThree(3); // 3x3 grid
  public Window1(  ) {
    ...
   threeByThreeMenuItem.Click += gameDimensionMenuItem_Click;
   fourByFourMenuItem.Click += gameDimensionMenuItem_Click;
   fiveByFiveMenuItem.Click += gameDimensionMenuItem_Click;
  }

  void gameDimensionMenuItem_Click(object sender, RoutedEventArgs e) {
  if( sender == threeByThreeMenuItem ) { game = new TicTacThree(3); }
  if( sender == fourByFourMenuItem ) { game = new TicTacThree(4); }
  if( sender == fiveByFiveMenuItem ) { game = new TicTacThree(5); }
  DataContext = game;
}

  ...
}

Updating the XAML is a bit trickier, however, as we need to handle a variable number of cells. In our case, the UniformGrid—which arranges things in even rows and columns—is exactly what we need, but the other panels will be useful for other kinds of data-driven layouts:

<Window ...>
  ...
  <Menu ...>
    <MenuItem Header="_Game">
      <MenuItem Header="_3x3 Game" Name="threeByThreeMenuItem" />
      <MenuItem Header="_4x4 Game" Name="fourByFourMenuItem" />
      <MenuItem Header="_5x5 Game" Name="fiveByFiveMenuItem" />
    </MenuItem>
  </Menu>
  ...
  <ItemsControl ... ItemsSource="{Binding Cells}">
    <ItemsControl.ItemsPanel>
     <ItemsPanelTemplate>
      <UniformGrid />
     </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemTemplate>
     <DataTemplate>
      <Button Content="{Binding Move}" Tag="{Binding CellNumber}"
           Margin="2" Click="cell_Click" />
     </DataTemplate>
   </ItemsControl.ItemTemplate>
  </ItemsControl>
  ...
</Window>

Here, we’ve added the menu so that the user can control the dimensions of the game, and we’ve bound the cells in the game as the data source to drive our items control. (We’re using an ItemsControl instead of a ListBox or a ListView because we don’t want the selection behavior those controls provide.) For the panel, we’re using a uniform grid, which will take however many items we provide and turn them into an even number of rows and columns. Likewise, for each item, we’re expanding a data template into a button to show the current cell’s move (or nothing, if no move has yet been made). We’re also binding the Tag property of each Button to the CellNumber property of the cell so that we can use the same button Click event handler. However, as we saw in Chapter 7, we could just as easily use the DataContext of the sender, as it will point to the Cell that was used to instantiate the data template.

Figure 9-14 shows the results of our data-driven UI work.

A data-driven game of tic-tac-toe
Figure 9-14. A data-driven game of tic-tac-toe

The interesting thing to notice is that once we’ve structured our XAML to be free of the hardcoded amount of data to expect, the XAML itself doesn’t get any more complicated, although it does have more functionality as the data changes, as Figure 9-14 shows.

If you decide you need even more control, you are of course free to take over the control templates as well:

<ItemsControl ... ItemsSource="{Binding Cells}">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <UniformGrid />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>

  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Button Margin="2"
        Content="{Binding Move}"
              Tag="{Binding CellNumber}"
              Click="cell_Click" />
    </DataTemplate>
  </ItemsControl.ItemTemplate>

  <ItemsControl.Template>
    <ControlTemplate TargetType="{x:Type ItemsControl}">
      <Grid>
        <Rectangle Fill="Green" RadiusX="20" RadiusY="20" />
        <ItemsPresenter Margin="20" />
     </Grid>
  </ControlTemplate>
</ItemsControl.Template>

</ItemsControl>

Here, we’re replacing the control template with a border containing an ItemsPresenter, which, as you’ll recall from earlier in this chapter, will display the items according to the templates set by the ItemsPanel and ItemsTemplate properties, as Figure 9-15 shows.

Replacing the control template in a data-driven application ()
Figure 9-15. Replacing the control template in a data-driven application (Figure F-17)

This use of the control template isn’t particularly fancy (although it does give our game a nice felt-tabletop look), but it does illustrate just how much control we have over the UI using templates, even when our data is completely UI-agnostic.

Ultimately, driving your UI by the data means making a choice from a range of possibilities. We started by dropping data objects into buttons and using styles and templates to present those objects to the user. Moving to data binding and away from mingling the data manipulation code in with UI code moved us farther along the continuum, as did binding the cells to an items control hosting a uniform grid. However, there are always going to be trade-offs. For example, our menu supports only three dimension choices, no matter how many our game object might support. Further, we haven’t supported the case where the game object might arrange the cells in nonuniform rows and columns. Like all software design choices, how “data-driven” you want your UI to be depends on the goals of your application.

However, whatever trade-offs you make, it is definitely the case that the various templates that WPF provides—data, control, and panel—all give us far more options than we’ve had in desktop presentation frameworks available before now. In fact, the reason I make a point of data-driven UI design is so that you don’t approach WPF programming using the same techniques you used to program user interfaces of old.

Tip

As an example of where other UI frameworks don’t quite provide the kind of data-driven capabilities we’d like, consider another UI framework that both authors are big fans of: Windows Forms. In Windows Forms, you absolutely have the ability to bind data to your controls. In fact, Windows Forms has more controls than WPF has, including the GridView, which has no equivalent in WPF v1.0.

However, although Windows Forms has more controls, each control has more limited support for what it can display. For example, although I can hand the Windows Forms ListBox a list of strings, I can’t hand it a list of CheckBox objects or PlayerMove objects and have it do anything more than call each object’s ToString method. If I want a container to meaningfully bind to a list of PlayerMove objects in Windows Forms, I have to build the custom PlayerMove container.

With WPF, on the other hand, I can plug custom data types and custom UI frameworks into existing containers by providing data and control templates. This turns out to be considerably less work for the same functionality. WPF is not your father’s UI framework . . .

Where Are We?

If the control author didn’t give you the right properties to tailor a control’s look to your liking, you can replace the look completely with a control template (assuming the control author has allowed such a thing). To this end, all of the built-in controls are “lookless,” picking up their default look from the system-wide theme, but leaving you to take it over completely, while keeping the existing behavior intact. If you want to plug into the existing behavior properly, however, some controls have more requirements, which we called the control template “contract.”

If a custom template still doesn’t give you enough control—perhaps you’d like customized behavior—you’ll want to think about building a custom control, which is described in Chapter 18, along with how to support custom control templates on your own custom controls.



[56] * You might be wondering whether we also need to bind our Margin property into the control template. It’s a special case: WPF implements Margin for all elements as part of the layout process, so it’s not something our template needs to worry about.

[57] * The VirtualizingStackPanel supports item virtualization, which is the ability to contain a large number of logical children, but instantiating UI elements only for the ones currently visible.

[58] * As one example, no FrameworkContentElement objects, as described in Chapter 14, will show up in the visual tree even though they’re in the logical tree.

[59] * In fact, this has nothing whatever to do with data-driven UI; as a good coding practice, you should separate your data and your logic from your UI.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset