1. Problem
Every control has an out-of-the-box user interface.
You want to replace this default UI with a custom one without having to
write a new control.
.2. Solution
Design a custom control template to express the new UI for the control, and apply it to the control using the Template property or through a Style in your application's XAML.
3. How It Works
Every Silverlight control that renders itself
visually at runtime needs its UI defined as a part of the control
writing process. The preferred mode of defining this UI is by designing a
self-contained block of XAML and associating it with the control so
that it can be loaded and rendered by the control code. This block of
XAML is what forms the default control template for that control.
3.1. Control Template Syntax
A control template always starts with the XAML element <ControlTemplate>. The TargetType attribute must supply the CLR type of the control to which the template can be applied. Here is a sample declaration:
<UserControl.Resources>
<ControlTemplate x:Key="ctCustomRadioButton" TargetType="RadioButton">
<!--Template Definition Here -->
</ControlTemplate>
</UserControl.Resources>
Inside the <ControlTemplate> tag, you
can have any XAML as long as it is renderable. The template is typically
defined as a stand-alone resource in a resource dictionary, where the x:Key
attribute specifies the unique key for the template by which it can be
referenced when applied to the control.
3.2. Setting the Template
The Control base class exposes a Template property that can be set on any control to replace its template, as shown here:
<RadioButton Template="{StaticResource ctCustomRadioButton}"/>
You can also use a style to apply a template to a control, like so:
<Style TargetType="RadioButton" x:Name="styleGelRadioButton">
<Setter Property="Template" Value="{StaticResource ctCustomRadioButton}"/>
<!--Other setters here -->
</Style>
<!--apply the Style and hence the template-->
<RadioButton Style="{StaticResource styleGelRadioButton}"/>
In the previous examples, you define the control
templates as stand-alone resources; then, you reference them in a style
or apply them using the Template property. Note that control
templates can also be defined inline without having to declare them as a
separate resource. The following XAML code demonstrates this:
<!-- defined in place in a Style -->
<Style TargetType="RadioButton" x:Name="styleGelRadioButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<!-- rest of the template -->
</ControlTemplate>
</Setter.Value>
</Setter>
<!-- rest of the setters -->
</Style>
<!-- defined in place in a control declaration -->
<RadioButton>
<RadioButton.Template>
<ControlTemplate TargetType="RadioButton">
<!-- rest of the template -->
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
3.3. Using Expression Blend to Design a Template
Expression Blend offers excellent support for
designing Silverlight user interfaces, including designing custom
templates for controls. For a general introduction to Expression Blend
usage and to UI design. This recipe discusses Expression Blend 3 features that apply to control template design.
Once you have the control added to your scene in the
Expression Blend designer window, you can right-click the control to
bring up its context menu, as shown in Figure 1.
You have the option of either creating an empty
control template or having Expression Blend generate a copy of the
default template. If the modifications you want to make are minor, it is
often helpful to start with a copy. A copy also gives you a good look
into the intentions of the original designers of the control. Note that
when you choose to edit a copy, Expression Blend actually creates a
style with the control template defined within that style using a setter
for the Template property.
Once you specify a key for the new control template for the RadioButton as shown in Figure 2
(or the encapsulating style, in the event you decide to edit a copy),
Expression Blend switches the designer over to the control template for
the RadioButton. If you chose to create an empty template, Expression Blend creates a mostly empty visual tree for the control contained in a Grid
for layout. If you chose to edit a copy, Expression Blend creates a
style that contains a copy of the entire visual tree as supplied by the
default template, which you can then modify. Figure 3 shows the differences.
From here on, designing the template is similar to
designing any other XAML-based UI in Expression Blend. Some additional
features are discussed next.
3.4. Template Bindings
When you are designing a control template, you have
the option of setting values for the properties of the various elements
that make up that template. In many cases, it may make sense to derive
those values from the corresponding property settings on the control at
the point of its use in an application. For example, you may want the Background property of an element inside the control template of a control to assume whatever value is set on the Background
property on the control itself when it is being used. However, when you
are designing the control template, you have no way of knowing what
those values might be. Therefore, you need a mechanism to indicate that a
certain property value on an element in the template will be bound to a
matching property value of the control at the point of use. The TemplateBinding construct allows you to do just that:
<ControlTemplate x:Key="ctGelRadioButton" TargetType="RadioButton">
<Grid MaxHeight="{TemplateBinding MaxHeight}"
MaxWidth="{TemplateBinding MaxWidth}"
Background="{TemplateBinding Background}">
<Ellipse Margin="0,0,0,0" x:Name="OuterRing"
Stroke="{TemplateBinding Foreground}" StrokeThickness="2">
</Ellipse>
</Grid>
</ControlTemplate>
For the RadioButton control template shown here, you have the MaxHeight, MaxWidth, and Background properties of the top-level Grid and the Foreground property of the Ellipse template bound. These template bindings will cause whatever values are supplied to these properties in a RadioButton
declaration to be applied to these elements in the template. Template
bindings are useful when you need the control consumer to be able to
affect properties of the parts of the control template without having
direct access to the parts themselves. However, it is not mandatory that
template bindings be used in every control template definition.
If you are designing the template in Expression Blend
3, the context menu for each property (accessible by clicking the
little rectangle on the right of the property editor) offers the choices
of parent properties that you can bind to (see Figure 4).
3.5. Content Model and Presenter Controls
Controls often present content to the user, as well as interactivity and event functionality. For example, in Figure 5,
the Option 1 is the content being displayed by the radio button. The
part of the control design that specifies how it displays content is
called the content model of the control.
To better understand this, let's consider the System.Windows.Controls.ContentControl type. The ContentControl has a dependency property called Content that can be set or bound to any content, which the ContentControl
instance then displays. In case there is no built-in knowledge of how
to display this content, you can also associate a data template through
the ContentTemplate property to facilitate the display of the content. More than being useful in and of itself, the ContentControl serves as a base class for many other controls in Silverlight, such as Label, Button, or the RadioButton shown earlier.
The following code shows the XAML declaration for the RadioButton in Figure 5:
<RadioButton Content="Option 1" />
Figure 6
shows a radio button with slightly more complex content, including a
text caption and an image displayed with the help of a data template.
This XAML code shows the RadioButton declaration:
<RadioButton Content="{Binding}" x:Name="rbtn">
<RadioButton.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Caption}" Grid.Column="0"
TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Image Source="{Binding Icon}" Grid.Column="1" Stretch="Fill"
idth="24" Height="24" Margin="3,0,0,0"/>
</Grid>
</DataTemplate>
</RadioButton.ContentTemplate>
</RadioButton>
The content for the RadioButton can be set by setting its DataContext property to some instance of a CLR type that exposes two properties: Caption and Icon.
When you modify the control template for a control,
you should be aware of the intended content model for that control. To
be fair to the control author's intentions, try to retain the same
content model in your custom template. To facilitate this, the
Silverlight control framework provides a specific category of controls
called presenters. A presenter's purpose is to create a placeholder for
content inside a control template. Through appropriate template
bindings, content gets passed on to the presenter, which then displays
the content in the rest of the visual tree of the template. You can also
associate a data template (again, preferably through a template
binding), which is then used by the presenter to display the content.
Several types of content models and corresponding
presenters are supplied in the framework. For this sample, you need to understand the most
fundamental of them all: the ContentPresenter control.
This XAML shows a ContentPresenter in action in a template for a RadioButton:
<ControlTemplate x:Key="ctCustomRadioButton"
TargetType="RadioButton">
<Grid>
<!-- rest of the template -->
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Grid.Column="1" Margin="2,0,0,2"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
</Grid>
</ControlTemplate>
Notice the template bindings for the Content and the ContentTemplate properties of the ContentPresenter, which allow the values set for these properties on any instance of the RadioButton to be passed into the ContentPresenter for display. As you saw in Figures 5-6 and 5-7,
the content (a string in the first case and some XAML with a specific
data binding for the image in the second) is passed in using this
mechanism. If those template bindings were absent or if you did not have
a ContentPresenter, setting the Content or the ContentTemplate property on the RadioButton would have no effect, since there would be no placeholder inside the control's template to display that content.
3.6. Visual State Management
Controls often change their visual state as users interact with them. A check mark that appears in a Checkbox or a Button
when it is clicked is an example of a visual state change. The
Silverlight control framework includes a Visual State Manager (VSM)
component that can be used to manage these state transitions.
The various possible visual states for a control are
defined by the control author and further logically grouped into state
groups. Each state managed by the VSM is implemented as a StoryBoard
that can contain one or more animations that define the visual
representation of moving from one state to another. When designing a
control template using Expression Blend, you can see the various state
groups and the specific states in the States editor, as shown in Figure 7.
When you select a specific state, Expression Blend transitions into a storyboard recording mode for that state (see Figure 8).
Recording a state change works just like recording a
regular storyboard in Expression Blend, including the use of the
storyboard time line editor to define a timeline for a specific keyframe
animation in the state storyboard.
In addition to defining each individual state as a
storyboard, you can also optionally define the time duration of the
transition from one state to another. Clicking the state transition icon
displays all the possible state transitions involving that state. Figure 9 shows the possible transitions to and from the MouseOver state for a control template, with * indicating any state.
In Figure 9, the transition duration from another state to the MouseOver
state has been defined as a quarter of a second. The following XAML
shows a sample set of states and some of the possible transitions
defined:
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualStateGroup.Transitions>
<vsm:VisualTransition GeneratedDuration="00:00:00.2500000" To="MouseOver"/>
<vsm:VisualTransition GeneratedDuration="00:00:00.2500000" From="MouseOver"/>
</vsm:VisualStateGroup.Transitions>
<vsm:VisualState x:Name="Disabled"/>
<vsm:VisualState x:Name="Normal">
<Storyboard/>
</vsm:VisualState>
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.0010000"
Storyboard.TargetName="OuterRing"
Storyboard.TargetProperty=
"(Shape.Stroke).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#FF144EEA"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.0010000"
Storyboard.TargetName="InnerCore"
Storyboard.TargetProperty=
"(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#FF144EEA"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Pressed"/>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="FocusStates">
<vsm:VisualState x:Name="Unfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="FocusIndicator"
Storyboard.TargetProperty="Visibility"
Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="FocusIndicator"
Storyboard.TargetProperty="Visibility"
Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="ContentFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="FocusIndicator"
Storyboard.TargetProperty="Visibility"
Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="CheckStates">
<vsm:VisualStateGroup.Transitions>
<vsm:VisualTransition GeneratedDuration="00:00:00.2500000" To="Checked"/>
<vsm:VisualTransition GeneratedDuration="00:00:00.2500000" From="Checked"/>
</vsm:VisualStateGroup.Transitions>
<vsm:VisualState x:Name="Unchecked">
<Storyboard/>
</vsm:VisualState>
<vsm:VisualState x:Name="Checked">
<Storyboard>
<ColorAnimationUsingKeyFrames
BeginTime="00:00:00"
Duration="00:00:00.0010000"
Storyboard.TargetName="InnerCore"
Storyboard.TargetProperty=
"(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#FF144EEA"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Indeterminate"/>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
Each visual state for the control is declared as a <vsm:VisualState>
element, and each state has an associated storyboard, which can include
one or more animations that all get executed by the runtime when that
visual state is reached. These animations typically animate various
properties of different parts of the control template to give the
necessary visual cue indicating the state change. As an example, in the MouseOver state storyboard, you have two animations defined within the storyboard. The first one animates the Stroke property on an element named OuterRing to a different solid color. The second one animates the Fill property of another element named InnerCore
to a different gradient. You can also have an empty storyboard if you
do not want to define any particular visual change for the control upon
reaching that state. The control's code determines when a specific
visual state is reached.
You should also note the <vsm:VisualStateGroup> declarations that group visual states together. The VisualStateManager
mandates that each state be contained in a group (even if that is the
only state in it) and that each state be defined in exactly one group.
You can also see <vsm:VisualTransition> elements declared
inside a state group. Each defined visual transition is a way to
specify a time duration over which a transition from one state to
another in a group should happen. In the previous example, transition
from any state to the MouseOver state or the reverse is specified to happen over a quarter of a second, as it is for the Checked state.
Note that you are not required to define an explicit
storyboard for each state. For example, it is common to not define
anything explicit for the Normal state, since the default
visual representation of the control template can be considered its
normal state. However, that does not mean that you can leave out the
state definition completely. In the case of the Normal state, for example, the empty storyboard causes the RadioButton to revert to its default look when none of the other defined visual states are applicable; thus, the Normal
state is reached. If you left out that state definition, the control
would never revert to the default look once it transitions out of
another state.
4. The Code
The code sample in this recipe replaces the default control template of a RadioButton with a custom template. Listing 1 shows the full XAML for the page.
Listing 1. Defining and applying a custom RadioButton control template
<UserControl x:Class="Recipe5_2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
<UserControl.Resources>
<!-- The Custom Control Template targeting a RadioButton -->
<ControlTemplate x:Key="ctCustomRadioButton"
TargetType="RadioButton">
<Grid Background="{TemplateBinding Background}"
MinHeight="{TemplateBinding MinHeight}"
MinWidth="{TemplateBinding MinWidth}"
MaxWidth="{TemplateBinding MaxWidth}"
MaxHeight="{TemplateBinding MaxHeight}">
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualStateGroup.Transitions>
<vsm:VisualTransition GeneratedDuration="00:00:00.2500000"
To="MouseOver"/>
<vsm:VisualTransition GeneratedDuration="00:00:00.2500000"
From="MouseOver"/>
</vsm:VisualStateGroup.Transitions>
<vsm:VisualState x:Name="Disabled"/>
<vsm:VisualState x:Name="Normal">
<Storyboard/>
</vsm:VisualState>
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.0010000"
Storyboard.TargetName="OuterRing"
Storyboard.TargetProperty=
"(Shape.Stroke).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#FF144EEA"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.0010000"
Storyboard.TargetName="InnerCore"
Storyboard.TargetProperty=
"(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#FF144EEA"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Pressed"/>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="FocusStates">
<vsm:VisualState x:Name="Unfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="FocusIndicator"
Storyboard.TargetProperty="Visibility"
Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="FocusIndicator"
Storyboard.TargetProperty="Visibility"
Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="ContentFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="FocusIndicator"
Storyboard.TargetProperty="Visibility"
Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="CheckStates">
<vsm:VisualStateGroup.Transitions>
<vsm:VisualTransition GeneratedDuration="00:00:00.2500000"
To="Checked"/>
<vsm:VisualTransition GeneratedDuration="00:00:00.2500000"
From="Checked"/>
</vsm:VisualStateGroup.Transitions>
<vsm:VisualState x:Name="Unchecked">
<Storyboard/>
</vsm:VisualState>
<vsm:VisualState x:Name="Checked">
<Storyboard>
<ColorAnimationUsingKeyFrames
BeginTime="00:00:00"
Duration="00:00:00.0010000"
Storyboard.TargetName="InnerCore"
Storyboard.TargetProperty=
"(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#FF144EEA"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Indeterminate"/>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.20*"/>
<ColumnDefinition Width="0.80*"/>
</Grid.ColumnDefinitions>
<Ellipse Margin="0,0,0,0" x:Name="OuterRing"
Stroke="#00000000" StrokeThickness="2">
<Ellipse.Fill>
<LinearGradientBrush
EndPoint="1.13300001621246,1.13999998569489"
StartPoint="−0.0640000030398369,−0.0560000017285347">
<GradientStop Color="#FF000000"/>
<GradientStop Color="#FFADADAD" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Grid Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width="0.6*"/>
<ColumnDefinition Width="0.2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="0.6*"/>
<RowDefinition Height="0.2*"/>
</Grid.RowDefinitions>
<Ellipse x:Name="InnerRing"
Fill="#FF000000"
Grid.Column="1" Grid.Row="1"/>
<Ellipse Grid.Row="1" Grid.Column="1"
x:Name="InnerCore" Margin="0,0,0,0">
<Ellipse.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFFFFF"/>
<GradientStop Color="#FF000000" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Grid.Column="1" Margin="2,0,0,2"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
<Rectangle Stroke="Black" x:Name="FocusIndicator" Grid.Column="1"
StrokeThickness="0.5" Height="1"
HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
Margin="2,0,0,0" />
</Grid>
</ControlTemplate>
<!-- A style targeting the RadioButton referencing the control template -->
<Style TargetType="RadioButton" x:Name="styleGelRadioButton">
<Setter Property="Template" Value="{StaticResource ctCustomRadioButton}"/>
<Setter Property="Height" Value="20" />
<Setter Property="Width" Value="100" />
<Setter Property="Background" Value="Transparent" />
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" Margin="20,20,20,20">
<Grid.RowDefinitions>
<RowDefinition Height="0.5*"/>
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
<!-- A RadioButton with default look & feel -->
<RadioButton HorizontalAlignment="Left" VerticalAlignment="Top"
Content="RadioButton" GroupName="Test" Grid.Row="0"/>
<!-- A RadioButton with the style (and hence the custom template) applied -->
<RadioButton HorizontalAlignment="Left" VerticalAlignment="Top"
Content="RadioButton"
Style="{StaticResource styleGelRadioButton}"
GroupName="Test" Grid.Row="1"/>
</Grid>
</UserControl>
|
In Listing 1, the RadioButton control template, named ctCustomRadioButton, is primarily made up of three Ellipses (two Ellipses named InnerRing and InnerCore, situated in a Grid within the outer Ellipse named OuterRing). There is also a ContentPresenter to display any bound content, as well as a Rectangle (with Height set to 1 so that it appears as an underscore below the content) serving as a focus indicator, which has its Visibility initially set to Collapsed.
Figure 10 shows the Normal state comparisons between the custom template RadioButton and the default template.
The MouseOver state is defined using a storyboard that changes the Stroke color of OuterRing and the Fill color of InnerRing. The result in comparison to the default RadioButton template is shown in Figure 11.
The Focused and ContentFocused state storyboards make the FocusIndicator rectangle visible, while the Unfocused state storyboard hides it. The Checked state storyboard modifies the Fill color of the ellipse InnerCore. Figure 12 shows the Checked state of the RadioButton with focus on it.
You also declare a style named styleGelRadioButton. You reference ctCustomRadioButton using a setter for the Template
property and set a few other defaults for some of the other properties
in the control template. And last, for the page's UI, you declare two RadioButtons—one using just the default look and feel defined by the framework and the other with the style styleGelRadioButton applied to it so that the custom template gets applied to it as well—to help you compare them visually.
Another important thing to note is the presence of
specific elements in the control template definition with predetermined
names. When the original control
author designs the control, there may be dependencies in the control's
code or in the definition of the default state change storyboards that
require specific names for different parts of the control template. If
you decide to leave those elements out of your new control template or
name them differently, certain parts of the control's feature set or its
visual representation may be rendered unavailable.
An example in Listing 1 is the Rectangle named FocusIndicator. The RadioButton's default implementation includes state definitions for the Focused, Unfocused, and ContentFocused states that toggles the visibility of this Rectangle based on whether focus is on the control. If you leave out or rename this Rectangle in your new template, you need to reauthor the state storyboards appropriately for the focus visual cue to function.
Control authors are advised to write controls
defensively so that a name dependency does not crash an application;
instead, the control just silently shuts down the dependent feature.
However, depending on the importance of the named element in the
control's overall functionality, leaving or renaming certain elements
may render the control useless. We suggest that you look at the
definition of the default control template in the documentation on the
MSDN website. This knowledge can help you make an informed decision
about any modifications.
However, if the dependency is in XAML through some
state storyboard reference, you have the ability to modify the
storyboard if you modify the element.