Er. alokpandey's Blog

Using Templates to Customize WPF Controls

Posted in ASP.NET (C# & VB), C#, VB by Alok Kumar Pandey on March 29, 2010
With the release of Windows Vista™ and the Microsoft® .NET Framework 3.0 come a host of new technologies for developers to learn about, discuss, and use. New tools, libraries, and paradigms will change how you build managed apps, offering powerful possibilities. We’re introducing this new monthly column to cover the underlying technologies you will use for developing your applications. Industry experts you are already familiar with will take turns delving into the Windows® Presentation Foundation, Windows Communication Foundation, and Windows Workflow Foundation. Let’s get started.
Traditionally, customizing an existing control in Windows has been a four-step process. It begins with inspiration. Then comes the research and exploration. This inevitably leads to weeping and gnashing of teeth. And finally it concludes with a complete rewrite. Controls are often resistant to customization due to deeply inaccessible code that links the visuals of the control with its functionality. This code is vital to the control so it must be either completely accepted or completely bypassed and replaced.
The pain of control customization has evidently been felt by the developers of the Windows Presentation Foundation, available as part of the .NET Framework 3.0. They’ve come up with an exciting and powerful solution known as the “template.”
The Windows Presentation Foundation template is at the same time so simple and so powerful that it actually took me a while to wrap my head around the concept. I quickly understood Windows Presentation Foundation styles (which are often confused with templates), but templates needed time to sink in.
Every predefined control in the Windows Presentation Foundation that has a visual appearance also has a template that entirely defines that appearance. This template is an object of type ControlTemplate that is set to the Template property defined by the Control class.
When you use a Windows Presentation Foundation control in your application, you can replace that default template with one of your own design. You retain the basic functionality of the control-including all the keyboard and mouse handling-but you can give it an entirely different look. This is what is meant when Windows Presentation Foundation controls are described (rather inelegantly) as “lookless.” A control has a default look, but this look is not intrinsic to the inner workings of the control.
Writing templates in code is rather awkward. It is much easier to use Extensible Application Markup Language (XAML). Because templates can be represented entirely in XAML, visual design tools will be available to help. If you’re thinking about programming a custom control that will work like an existing Windows Presentation Foundation control but with a different look, stop now! You might very well be able to do it with a template.
The downloadable source code consists of seven standalone XAML files that I’ll discuss throughout this column. No C# code was compiled during the making of this column! If you have the .NET Framework 3.0 SDK installed, you can edit the files using XAMLPad or XAML Cruncher, a similar program from my book, Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation.
The Windows Presentation Foundation supports other types of templates for displaying control content, but in this column I’ll only discuss objects of type ControlTemplate.
Dumping the Defaults
Every predefined control in the Windows Presentation Foundation that has a visual appearance has a default template. If you are interested in writing custom templates, study these defaults.
The DumpControlTemplate from Chapter 25 of my book displays a control’s default Template property in convenient XAML format. (You can download the source code.) But if you’d rather write a template dumper yourself, here’s the code that performs the crucial step:

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = new string(' ', 4);
settings.NewLineOnAttributes = true;
StringBuilder strbuild = new StringBuilder();
XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings);
XamlWriter.Save(ctrl.Template, xmlwrite);
This code assumes that ctrl is an instance of a class that derives from Control and that ctrl has been rendered on the screen. (It is my experience that the Template property will be null otherwise.) The XamlWriter.Save call raises an exception if the Template property is null, as it will be for some controls that don’t have a visual appearance. At the end of this code, calling ToString on strbuild provides a complete XAML document that contains the template.

The XAML in this default template may be a little wordier than the XAML you might write. For example, you might write this:

<Trigger Property="IsEnabled" Value="False">
But in the XAML file generated by XamlWriter.Save, you’ll see markup that looks like this:

<Trigger Property="UIElement.IsEnabled">
    <Trigger.Value>
        <s:Boolean>False</s:Boolean>
    </Trigger.Value>
The s prefix is defined with an xmlns namespace declaration for the .NET System namespace.
The Visual Tree
Let’s begin by looking at a template for a fairly simple but non-trivial control: the CheckBox. Say you want something that looks a bit more dramatic than the standard CheckBox. Perhaps you want the user’s selection to be displayed as a big green checkmark or a big red X that appears over the entire content of the CheckBox. The file BigCheckCheckBox.xaml shows a template for such a CheckBox.
In a XAML file, a template is an element of type ControlTemplate. In a small Windows Presentation Foundation program, ControlTemplate elements are generally defined in a Resources section in the root element of a XAML file. For larger applications, or when defining templates that are shared among applications, ControlTemplate elements are in their own XAML files with root elements of ResourceDictionary.
In either case, the ControlTemplate element generally has three parts. It begins with an optional Resources section that can define styles or brushes used by the template. (The template in BigCheckCheckBox.xaml has no Resources section.) The template then provides the definition of the template’s visual tree. This tree describes the desired appearance of the control as a layout of elements and perhaps other controls. The template concludes with a Triggers section, which indicates how elements of the visual tree change in response to changes in the control’s properties. Figure 1 and Figure 2 show most of the ControlTemplate elements for the custom CheckBox. The code in the first figure shows the visual tree and the second figure continues the ControlTemplate element with the Triggers section.
Figure 2 The Triggers Section
<ControlTemplate.Triggers>
    <Trigger Property="IsChecked" Value="True">
        <Setter TargetName="path"
                Property="Data"
                Value="M 0 5 L 3 10 10 0" />

         <Setter TargetName="path"
                Property="Stroke"
                Value="Green" />
    </Trigger>

    <Trigger Property="IsChecked" Value="{x:Null}">
         <Setter TargetName="path"
                Property="Data"
                Value="M 0 2.5 A 5 2.5 0 1 1 5 5 
                       L 5 8 M 5 10 L 5 10" />

        <Setter TargetName="path"
                Property="Stroke"
                Value="Blue" />
    </Trigger>

    <Trigger Property="IsEnabled" Value="False">
        <Setter Property="Foreground" 
                Value="{DynamicResource
                   {x:Static SystemColors.GrayTextBrushKey}}" />
    </Trigger>
    ...
</ControlTemplate.Triggers>

Figure 1 Visual Tree that Describes a CheckBox
<ControlTemplate x:Key="templateBigCheck" 
                 TargetType="{x:Type CheckBox}">

    <Border BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            Background="{TemplateBinding Background}">

        <Grid>
            <!-- ContentPresenter displays content of CheckBox -->
            <ContentPresenter
                Content="{TemplateBinding Content}"
                ContentTemplate="{TemplateBinding ContentTemplate}"
                Margin="{TemplateBinding Padding}"
                HorizontalAlignment="{TemplateBinding 
                                        HorizontalContentAlignment}"
                VerticalAlignment="{TemplateBinding
                                        VerticalContentAlignment}" />

            <!-- This Border displays a semi-transparent red X -->
            <Border>
                <Border.Background>
                    <VisualBrush Opacity="0.5">
                        <VisualBrush.Visual>
                            <Path Name="path"
                                  Data="M 0 0 L 10 10 M 10 0 L 0 10"
                                  Stroke="Red" 
                                  StrokeStartLineCap="Round"
                                  StrokeEndLineCap="Round"
                                  StrokeLineJoin="Round" />
                        </VisualBrush.Visual>
                    </VisualBrush>
                </Border.Background>
            </Border>
        </Grid>
   </Border>

The excerpt from BigCheckBox.xaml shown in Figure 1 illustrates the use of the ControlTemplate start tag and the visual tree. Because this template is a resource, it must contain an x:Key attribute with its resource name. You’ll notice the TargetType attribute is used-this is not strictly required but it makes the rest of the template simpler by eliminating the need to preface every property with a class name.
The top-level element of this visual tree is a Border, an element that can play parent to a single child. In this sample, three properties of this Border are assigned with the TemplateBinding markup extension. TemplateBinding is used to bind properties of elements in the visual tree to properties of the control.
The TemplateBinding expressions are more interesting in the ContentPresenter element, which formats content for buttons and other controls that derive from ContentControl. It is ContentPresenter that allows just about anything to be displayed inside a Button or CheckBox. Notice that a TemplateBinding is used to set the Margin property of the ContentPresenter to the Padding property of the CheckBox control being templated. Margin is extra space outside an element; padding is space inside a control not occupied by its content, so you can see the rationale behind this binding.
The ContentPresenter appears inside a single-cell Grid panel along with another Border. Grid cells are often used to host multiple children that must appear in layers. The second Border therefore appears on top of the ContentPresenter. This Border is given a background that consists of a 50 percent opaque brush that contains a red X mark. Notice that the Path element that renders the red X mark is assigned a name of path. This name is referenced in the Triggers section of the template.
Template Triggers
The last section of a ControlTemplate is generally dedicated to Trigger elements. These change properties of the elements that make up the visual tree based on changes in properties of the control. Figure 2 shows a large portion of the Triggers section for the custom CheckBox template.
By default, the IsChecked property of the CheckBox is false and the CheckBox displays a red X mark. The first Trigger element indicates that when IsChecked becomes true, the Data property and the Stroke property of the element named path should be set to display a green check mark. The next Trigger element accommodates a null value of IsChecked (this can occur when the CheckBox is set for tri-state operation) by displaying a blue question mark. The last Trigger element changes the Foreground property of the control to a gray brush when the IsEnabled property is false. (The BigCheckCheckBox.xaml file has an additional Trigger element that displays a dotted line around the control’s content when the control has input focus.)
The Triggers section of a template can be quite extensive. A Trigger element for the IsMouseOver property is often included so the control reacts when the mouse passes over the control.
You can then reference the template in an element that creates a CheckBox, like so:

<CheckBox Template="{StaticResource templateBigCheck}" ...


Making Progress
You might be skeptical of how well the concept of templates can be adapted to more complex controls. After all, some controls have moving parts and more involved interaction with the user. To dissuade you from such skepticism, I'll spend the rest of this column looking at templates for the three controls that derive from RangeBase. These are ProgressBar, ScrollBar, and Slider.
To work properly, templates for more complex controls require certain types of elements with specific names. For example, a visual tree for a ProgressBar must have two elements of type FrameworkElement (or that derive from FrameworkElement) named PART_Track and PART_Indicator. These two elements are known as "named parts" of the template. The names are shown in the SDK documentation of the ProgressBar class as TemplatePart attributes. If your template does not include two elements with these names, the control won't work correctly.
If the ProgressBar is displayed in its default horizontal orientation, then the internal logic of the control sets the Width property of the element named PART_Indicator to a fraction of the ActualWidth property of the element named PART_Track. That fraction is based on the Minimum, Maximum, and Value properties of the ProgressBar. If the orientation of the ProgressBar is vertical, then the Height and ActualHeight properties of the two elements are used instead.
The ProgressBar control actually has two default templates for the two orientations. (This is true of ScrollBar and Slider, as well.) If you want your new ProgressBar to support both orientations, you should write two separate templates and select them in the Triggers section of a Style element that you also define for the ProgressBar.
Figure 4 is an excerpt from the BareBonesProgressBar.xaml file that shows a complete ProgressBar element with the ControlTemplate object as a property of that element. The file also includes a ScrollBar used to test the ProgressBar through a binding. As you manipulate the ScrollBar, the blue rectangle (the PART_Indicator element) varies in width from zero to the width of the red rectangle (the PART_Track element). Note that even though BareBonesProgressBar.xaml gives the PART_Track rectangle an explicit width, you should actually avoid explicit dimensions whenever possible.
Figure 4 Barebones ProgressBar Template
<ProgressBar Margin="50" HorizontalAlignment="Center" 
             Value="{Binding ElementName=scroll, Path=Value}">
    <ProgressBar.Template>
        <ControlTemplate>
            <StackPanel>
                <Rectangle Name="PART_Track"
                           Height="20" Width="300" Fill="Red" />

                <Rectangle Name="PART_Indicator"
                           Height="20" Fill="Blue" />
            </StackPanel>
        </ControlTemplate>
    </ProgressBar.Template>
</ProgressBar>

It's more common for the indicator element to be inside the track element, and you'll probably consider using a Border element for the track. But beware: if you also give that Border a non-zero BorderThickness, then that thickness will be part of the Border element's total width, and the width inside the border will be slightly less. If you want to use a Border with a non-zero border thickness for display purposes, make the track another Border with a zero border thickness inside that first border, and put something else inside that second Border for the indicator. (And if you're using a Border element without making use of its border or background properties, consider using a Decorator, which is the borderless, background-less class from which Border descends.)
Figure 5 shows a rather extensive ControlTemplate object from the ThermometerProgressBar.xaml file. This template has a Resources section but no Triggers section. Although the visual tree makes use of some explicit coordinates for borders and corners, the overall dimensions of the ProgressBar are not defined. That responsibility is left to any markup that defines a ProgressBar using a template like this, for example:

<ProgressBar 
       Template="{StaticResource
       templateThermometer}" 
       Orientation="Vertical" Minimum="0" 
       Maximum="100"
       Width="50" Height="350" ...
Figure 5 ControlTemplate for Thermometer ProgressBar
<ControlTemplate x:Key="templateThermometer"
                 TargetType="{x:Type ProgressBar}">

    <!-- Define two brushes for the thermometer liquid -->
    <ControlTemplate.Resources>
        <LinearGradientBrush x:Key="brushStem"
                             StartPoint="0 0" EndPoint="1 0">
            <GradientStop Offset="0" Color="Red" />
            <GradientStop Offset="0.3" Color="Pink" />
            <GradientStop Offset="1" Color="Red" />
        </LinearGradientBrush>

        <RadialGradientBrush x:Key="brushBowl"
                             GradientOrigin="0.3 0.3">
            <GradientStop Offset="0" Color="Pink" />
            <GradientStop Offset="1" Color="Red" />                        
        </RadialGradientBrush>
   </ControlTemplate.Resources>

   <!-- Two-row Grid divides thermometer into stem and bowl -->
   <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!-- Second grid divides stem area in three columns -->
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="25*" />
                <ColumnDefinition Width="50*" />
                <ColumnDefinition Width="25*" />
            </Grid.ColumnDefinitions>

            <!-- This border displays the stem -->
            <Border Grid.Column="1" BorderBrush="SteelBlue" 
                    BorderThickness="3 3 3 0"
                    CornerRadius="6 6 0 0" >

                <!-- Track and Indicator elements -->
                <Decorator Name="PART_Track">
                    <Border Name="PART_Indicator"
                            CornerRadius="6 6 0 0"
                            VerticalAlignment="Bottom"
                            Background="{StaticResource brushStem}" />
                </Decorator>
            </Border>
        </Grid>

        <!-- The bowl outline goes in the main Grid second row -->
        <Ellipse Grid.Row="1"
                 Width="{TemplateBinding Width}"
                 Height="{TemplateBinding Width}"
                 Stroke="SteelBlue" StrokeThickness="3" />

        <!-- Another grid goes in the same cell -->
        <Grid Grid.Row="1" >
            <Grid.RowDefinitions>
                <RowDefinition Height="50*" />
                <RowDefinition Height="50*" />
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="25*" />
                <ColumnDefinition Width="50*" />
                <ColumnDefinition Width="25*" />
            </Grid.ColumnDefinitions>

            <!-- This is to close up the gap between bowl and stem -->
            <Border Grid.Row="0" Grid.Column="1"
                    BorderBrush="SteelBlue"
                    BorderThickness="3 0 3 0"
                    Background="{StaticResource brushStem}" />
        </Grid>

        <!-- Another ellipse to fill up the bowl -->
        <Ellipse Grid.Row="1"
                 Width="{TemplateBinding Width}"
                 Height="{TemplateBinding Width}"
                 Stroke="Transparent" StrokeThickness="6"
                 Fill="{StaticResource brushBowl}" />
    </Grid>
</ControlTemplate>

The resulting ProgressBar is shown in Figure 6. Notice that the Orientation property of this ProgressBar must be set to Vertical-otherwise it won't work properly. You can either remember to set the Orientation whenever you use this template, or you can define a Style for the ProgressBar that sets the Orientation and also references the template. (I'll show an example of this technique in a moment.)

Figure 6

Leave a comment