(X) Hide this
    • Login
    • Join
      • Say No Bots Generate New Image
        By clicking 'Register' you accept the terms of use .

Understanding control customization with templates: part #1

(6 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   91
Comments:   9
More Articles
0 comments   /   posted on Jul 12, 2010
Categories:   Controls , Design

This article is compatible with the latest version of Silverlight.

Every Silverlight developer - and more extensively each Windows developer - knows what I mean with the term "Control". The term is referred to a reusable component that let the developer to add a known behavior to the user interface. The Silverlight plugin, the SDK and the Control's Toolkit have plenty of these controls, and we are used to put them into our application without any fear. Unfortunately, often their appearance is ugly and we may be tempted to change it someway, to give our application a fresh and unique look & feel. With Silverlight there are various degree of personalization you can apply to them, from the simple markup attribute, to an implicit style, but often we need a more deep and drastic revision. In these cases Silverlight grant a unique capability of changing the very inner structure of every control to bend it to our will.

Behavior versus Appearance

Capture

Given that the middle control in figure is a ToggleButton, what they have in common the three controls? At a superficial exam it may appear that the sole thing they have in common is the text "Select this option...". From an user experience point of view, the meaning they have in an user interface is someway different. But if you analyze them, from a developer perspective, you may agree they are perfectly equals. All the controls infact can be switched on or off and everyone has a label. So, the very important difference is not about the behavior but is about the aspect they have. And If you investigate deeply using Visual Studio you will understand that CheckBox and RadioButton are both inherited from ToggleButton, that is the root of this kind of controls.

In Silverlight, there is a clear distinction between the behavior and the aspect of every control, at least if the control is developed with a set of rules in mind, that make it a Templated Control. This distinction separates the work of two key figures in the development process: the developer and the designer. Both these figures have different roles in the team and often the requirements of the first are totally opposite to the other's.

I'm a developer, and when I put a CheckBox in the interface I'm acquiring a behavior to my application. I simply need that someone can choose if the user needs to activate or not an option. Nothing else. At the opposite, when a designer looks at the same interface it does not matter what the control does, but his main concern is its appearance, to make the application not only beautiful, but also more easily understandable and usable.

Templated controls.

In one of the last sentences, I used the term "templated control". First of all you have to know that all the controls in the Silverlight core, in the SDK and in the Control's Toolkit are fundamentally Templated Controls. But what does this mean?

In every control you can detect some fixed parts, and some other parts that can be changed by the developer. In the CheckBox you have a Content property that let you change the label, but you can customize colors, borders, and many other properties. If you think at the inner structure of a the checkbox, made of simple shapes held toghether by some layout elements, you understand that changing a property of the control means changing one or more property of one or more shapes in this structure. This "structure" is called "ControlTemplate", because of the fact you can think at it as a placeholder for properties you can change and parts you can add. The template appearance will remain the same across different instances of the control but the label can change its content, the border its thickness and so on. Obviously you can create your own Templated Controls, but this is matter for the next part of the article. In this paragraphs you will learn how to customize the template and deeply change the appearance of the existing controls.

Until now I said that every control has a template, but I never tried to explain where this template is located. So, if you watch to the properties of a CheckBox you will easily find a Template property, and this is the location where an instance of ControlTemplate class retain the appearance of the control. Now, please point your browser to Google and type "silverlight control styles and templates" and hit "I'm feeling lucky". The first link of the search probably will take you to this page: http://msdn.microsoft.com/en-us/library/cc278075(VS.95).aspx. Here you can find a complete list of the Controls in Silverlight and a link to a page containig the original template of every control. Hit CheckBox Style and Templates and you will be revealed the whole template behind the CheckBox. Probably something like this:

 <ControlTemplate TargetType="CheckBox">
     <Grid>
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="16"/>
            <ColumnDefinition Width="*"/>
         </Grid.ColumnDefinitions>
         <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
             <Rectangle x:Name="Background" Width="14" Height="14" RadiusX="1" RadiusY="1" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="{TemplateBinding BorderThickness}" Fill="#FFFFFFFF" Margin="1"/>
             <Rectangle x:Name="BackgroundOverlay" Fill="#FFC4DBEE" Opacity="0" Width="14" Height="14" RadiusX="1" RadiusY="1" StrokeThickness="1" Margin="1" Stroke="#00000000"/>
             <Rectangle x:Name="BoxMiddleBackground" Width="10" Height="10" RadiusX="1" RadiusY="1" Fill="{TemplateBinding Background}" Stroke="#00000000" StrokeThickness="1"/>
             <Rectangle x:Name="BoxMiddle" Width="10" Height="10" RadiusX="1" RadiusY="1" StrokeThickness="1" >
                 <Rectangle.Stroke>
                     <LinearGradientBrush EndPoint=".5,1" StartPoint=".5,0">
                         <GradientStop Color="#FFFFFFFF" Offset="1"/>
                         <GradientStop Color="#FFFFFFFF" Offset="0"/>
                         <GradientStop Color="#FFFFFFFF" Offset="0.375"/>
                         <GradientStop Color="#FFFFFFFF" Offset="0.375"/>
                     </LinearGradientBrush>
                 </Rectangle.Stroke>
                 <Rectangle.Fill>
                     <LinearGradientBrush StartPoint="0.62,0.15" EndPoint="0.64,0.88">
                         <GradientStop Color="#FFFFFFFF" Offset="0.013" />
                         <GradientStop Color="#F9FFFFFF" Offset="0.375" />
                         <GradientStop Color="#EAFFFFFF" Offset="0.603" />
                         <GradientStop Color="#D8FFFFFF" Offset="1" />
                     </LinearGradientBrush>
                 </Rectangle.Fill>
             </Rectangle>
             <Rectangle x:Name="BoxMiddleLine" Width="10" Height="10" RadiusX="1" RadiusY="1" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1" Opacity=".2"/>
             <Path x:Name="CheckIcon" Margin="1,1,0,1.5" Fill="#FF333333" Stretch="Fill" Opacity="0" Width="10.5" Height="10" Data="M102.03442,598.79645 L105.22962,597.78918 L106.78825,600.42358 C106.78825,600.42358 108.51028,595.74304 110.21724,593.60419 C112.00967,591.35822 114.89314,591.42316 114.89314,591.42316 C114.89314,591.42316 112.67844,593.42645 111.93174,594.44464 C110.7449,596.06293 107.15683,604.13837 107.15683,604.13837 z" FlowDirection="LeftToRight"/>
             <Rectangle x:Name="IndeterminateIcon" Height="2" Fill="#FF333333" Opacity="0" Width="6"/>
             <Rectangle x:Name="DisabledVisualElement" RadiusX="1" RadiusY="1" Width="14" Height="14" Opacity="0" Fill="#FFFFFFFF"/>
             <Rectangle x:Name="ContentFocusVisualElement" RadiusX="2" RadiusY="2" Stroke="#FF6DBDD1" StrokeThickness="1" Opacity="0" IsHitTestVisible="false" Width="16" Height="16" />
  
             <Border x:Name="ValidationErrorElement" Margin="1" BorderThickness="1" CornerRadius="1" BorderBrush="#FFDB000C" Visibility="Collapsed"
                     ToolTipService.PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}">
                 <ToolTipService.ToolTip>
                     <ToolTip x:Name="validationTooltip" 
                         Template="{StaticResource ValidationToolTipTemplate}"
                         DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
                         Placement="Right" 
                         PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}">
                         <ToolTip.Triggers>
                             <EventTrigger RoutedEvent="Canvas.Loaded">
                                 <EventTrigger.Actions>
                                     <BeginStoryboard>
                                          <Storyboard>
                                             <ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsHitTestVisible">
                                                 <DiscreteObjectKeyFrame KeyTime="0" >
                                                     <DiscreteObjectKeyFrame.Value>
                                                         <sys:Boolean>true</sys:Boolean>
                                                     </DiscreteObjectKeyFrame.Value>
                                                 </DiscreteObjectKeyFrame>
                                             </ObjectAnimationUsingKeyFrames>
                                         </Storyboard>
                                     </BeginStoryboard>
                                 </EventTrigger.Actions>
                             </EventTrigger>
                         </ToolTip.Triggers>
                     </ToolTip>
                 </ToolTipService.ToolTip>
                 <Grid Width="10" Height="10" HorizontalAlignment="Right" Margin="0,-4,-4,0" VerticalAlignment="Top" Background="Transparent">
                     <Path Margin="0,3,0,0" Data="M 1,0 L5,0 A 2,2 90 0 1 7,2 L7,6 z" Fill="#FFDC000C"/>
                     <Path Margin="0,3,0,0" Data="M 0,0 L2,0 L 7,5 L7,7" Fill="#ffffff"/>
                 </Grid>
             </Border>
         </Grid>
         <ContentPresenter
             Grid.Column="1"
             x:Name="contentPresenter"
             Content="{TemplateBinding Content}"
             ContentTemplate="{TemplateBinding ContentTemplate}"
             HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
             VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
             Margin="{TemplateBinding Padding}"/>
     </Grid>
 </ControlTemplate>

In the snippet, I've removed some parts of the template, related to the Visual State Manager, to shorten the code and concentrate our attention on the appearance of the control. If you are experienced in XAML you will understand the anatomy of the control, but the better is oversimplify it and change the control with our own template: Here is a shorter XAML snippet:

 <ControlTemplate>
     <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Rectangle Stroke="#FF666666" StrokeThickness="1" Grid.ColumnSpan="2" RadiusX="2" RadiusY="2" Fill="WhiteSmoke" />
        <Rectangle x:Name="CheckIcon" Fill="Orange" Margin="1,1,0,1"  Stretch="Fill" Opacity="1.0" RadiusX="1" RadiusY="1" />
        <ContentPresenter x:Name="contentPresenter" Grid.Column="1" Margin="3" />
     </Grid>
 </ControlTemplate>

In the first element I declare a ControlTemplate. This element can be inserted directly into the Template property of the control or better added to a Style that we can apply to multiple controls. The inner content is the root of the template of the control and it is easy to understand the new checkbox will appear as a rectangle with an orange section used as selection indicator. The important part is the ContentPresenter control. This kind of control is useful to indicate where the value of the Content property has to be put. It is a real placeholder that will be filled with the sentence "Select this option..." or with something else you can write in the markup.

A template may have some "parts", that are particular elements, referenced by the underlying class. The checkbox does not declare any parts, but often there is the need of attaching events to an element so, the developer give it a name, and the element becomes a "part". Watch the page "Slider Styles and Templates" for a real example of some parts. The slider has many parts, and as you can understand they are required to get the user interaction and change the values.

What you have to be concerned about the parts is that you have never to change its name and its underlying type. The pages of MSDN accurately describe the names and the types of the parts, so if you find a part is declared "Button" you can use only a Button, but if the same part is declared as ButtonBase you can use indifferently a Button or an HyperlinkButton because both derive from ButtonBase.

Once you defined the new appearance of the control there is another need you can have. In my example you may want to change the color and thickness of the border or some other properties without creating different templates for each combination. You probably expect to put the new color in the BorderBrush property of the CheckBox and have the border changed. To allow this you have to create a binding between the property of the control and one or more properties in the template. For this purpose you have to use the TemplateBinding markup extension:

 <ControlTemplate>
     <Grid>
        <Grid.ColumnDefinitions>
           <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Rectangle Stroke="{TemplateBinding BorderBrush}" Fill="{TemplateBinding Background}"
                   Grid.ColumnSpan="2" RadiusX="2" RadiusY="2" />
        <Rectangle x:Name="CheckIcon" Fill="Orange" Margin="1,1,0,1"  Stretch="Fill" Opacity="1.0" RadiusX="1" RadiusY="1" />
         <ContentPresenter x:Name="contentPresenter" Grid.Column="1" Margin="3" />
     </Grid>
 </ControlTemplate>

The TemplateBinding creates a sort of link between two properties. You only have to be sure both the properties have the same type. Also if this may appear similar to the Binding markup extension, the TemplateBinding is very limited in Silverlight. It has not any Converter property and cannot use anything else the a property name, but it is equally useful to spare time when reusing a template you created.

Managing states

If you run the project after assigning the template I wrote in the previous box you will remain deluded. The new CheckBox has a new appearance but it does not work anymore. It is become a shape on the plugin surface and is unable to react to the user interaction. The problem is that we have not defined what in the control have to change to react at the various states it might assume. With "state" I mean Checked or Unchecked, Hover or Normal, Focused or disabled. Each state changes some properties of the template and gives a feedback to the user. In the page "CheckBox Styles and Templates" you will find a table containing all the states of the CheckBox. They are managed by a component called VisualStateManger that is responsible of maintaining a number of state groups and track the current. You can put an entire section describing how a state must appear and every state is represented by one or more animations. The great thing is that you only have to be concerned about how an animation must change the appearance, and the VSM will interpolate all the transitions to the states. So, let me add a state to my checkbox template:

 <ControlTemplate>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <VisualStateManager.VisualStateGroups>
             <VisualStateGroup x:Name="CheckStates">
                 <VisualState x:Name="Checked">
                     <Storyboard>
                         <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="CheckIcon"/>
                     </Storyboard>
                 </VisualState>
                 <VisualState x:Name="Unchecked"/>
             </VisualStateGroup>
         </VisualStateManager.VisualStateGroups>
         <Rectangle Stroke="{TemplateBinding BorderBrush}" Fill="{TemplateBinding Background}"
                    Grid.ColumnSpan="2" RadiusX="2" RadiusY="2" />
         <Rectangle x:Name="CheckIcon" Fill="Orange" Margin="1,1,0,1"  Stretch="Fill" Opacity="0" RadiusX="1" RadiusY="1" />
         <ContentPresenter x:Name="contentPresenter" Grid.Column="1" Margin="3" />
     </Grid>
 </ControlTemplate>

As you can see I've added an animation, for the Checked state, that animates the Opacity property of the Orange rectangle in the middle of the checkbox . I have not added an animation to the Unchecked state and this mean the VSM will make the transition from Opacity = 1.0 to Opacity = 0.0 when the control move to the uncheked state. If you run the sample you will see the orange rectangle appear when you hit the control.

Good to know but...

What I've described in these rows is for sure very interesting for people that need a deep customization of controls appearance. But probably, you are now concerned about the complex syntax you have to use and learn. The good news is that Blend, since the release 3.0, is your friend when editing templates. When you are developing an application the better is having a tools that supports template editing. Blend is very powerful and let you manage the constituting elements of the templates but also the states in a very simple way. Learning about how to customize the templates manually is really interesting, but it is only the starting point for the next step: build your own templated controls.


Subscribe

Comments

No comments

Add Comment

Login to comment:
  *      *       

From this series