
Using a ControlTemplate to Further Change the Appearance of a Control
Sometimes you want to customize the appearance of your application beyond what setting properties allows you to do. For example, you might want the buttons in your application to be shaped differently than the default. When you replace the appearance of an existing control without changing its functionality, you change the control's skin.
In Silverlight, the skin of a control is defined by its ControlTemplate. Because you create a ControlTemplate in XAML, you can change a control's appearance without writing any code. The Control class has a public Template property that can be set by application developers. The following example creates a custom ControlTemplate for the Button.
|
<StackPanel xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
<StackPanel.Resources>
<Style TargetType="Button" x:Key="newTemplate">
<!--Set the Background, Foreground, FontSize, Width,
Height, Margin, and Template properties for
the Button.-->
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="Navy"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="RootElement">
<vsm:VisualStateManager.VisualStateGroups>
<!--Define the states and transitions for the common states.
The states in the VisualStateGroup are mutually exclusive to
each other.-->
<vsm:VisualStateGroup x:Name="CommonStates">
<!--Define the VisualTransitions that can be used when the control
transitions between VisualStates that are defined in the
VisualStatGroup.-->
<vsm:VisualStateGroup.Transitions>
<vsm:VisualTransition To="MouseOver" Duration="0:0:1" />
<vsm:VisualTransition From="Pressed" To="MouseOver" Duration="0:0:0.01" />
<vsm:VisualTransition To="Pressed" Duration="0:0:0.01" />
<vsm:VisualTransition From="MouseOver" To="Normal" Duration="0:0:1.5">
<Storyboard>
<ColorAnimationUsingKeyFrames
Storyboard.TargetProperty="Color"
Storyboard.TargetName="BorderBrush"
FillBehavior="HoldEnd" >
<ColorAnimationUsingKeyFrames.KeyFrames>
<LinearColorKeyFrame Value="Blue" KeyTime="0:0:0.5" />
<LinearColorKeyFrame Value="Yellow" KeyTime="0:0:1" />
<LinearColorKeyFrame Value="#aac" KeyTime="0:0:1.5" />
</ColorAnimationUsingKeyFrames.KeyFrames>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualTransition>
</vsm:VisualStateGroup.Transitions>
<!--Define the VisualStetes in this VistualStateGroup.-->
<vsm:VisualState x:Name="Normal" />
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="BorderBrush"
Storyboard.TargetProperty="Color" To="Red" />
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Pressed">
<Storyboard >
<ColorAnimation Storyboard.TargetName="BorderBrush"
Storyboard.TargetProperty="Color" To="Orange"
/>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="DisabledState">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="DisabledRect"
Storyboard.TargetProperty="Opacity" To="1" Duration="0" />
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<!--Define the states and transitions for the focus states.
The states in the VisualStateGroup are mutually exclusive to
each other.-->
<vsm:VisualStateGroup x:Name="FocusStates">
<!--Define the VisualStetes in this VistualStateGroup.-->
<vsm:VisualState x:Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual"
Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Unfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual"
Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<!--Create a border that has a different color.-->
<Grid.Background>
<SolidColorBrush x:Name="BorderBrush" Color="#aac"/>
</Grid.Background>
<Grid Background="#dde" Margin="4">
<!--Create a Rectangle that indicates that the
Button has focus.-->
<Rectangle x:Name="FocusVisual"
Visibility="Collapsed" Margin="2"
Stroke="#60000000" StrokeThickness="1"
StrokeDashArray="1.5 1.5"/>
</Grid>
<!--Use a ContentPresenter to display the Content of
the Button.-->
<ContentPresenter
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Padding="{TemplateBinding Padding}"
TextAlignment="{TemplateBinding TextAlignment}"
TextDecorations="{TemplateBinding TextDecorations}"
TextWrapping="{TemplateBinding TextWrapping}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="4,5,4,4" />
<Rectangle x:Name="DisabledRect"
RadiusX="4" RadiusY="4"
Fill="#A5FFFFFF"
Opacity="0" IsHitTestVisible="false" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<Button Style="{StaticResource newTemplate}"
Content="Button1"/>
</StackPanel>
|
When you create a new template for a control, you redefine its visual structure and visual behavior. Is some cases, the code of a control might refer to parts of the control's ControlTemplate. For example, the code might call a method on an element that is in the template to perform some functionality. This means you must understand how the template and the code relate to each other. That relationship is described by the control contract, which is an agreement between the logical and visual parts of the control. The following example describes the control contract of the Button.
|
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Pressed", GroupName = "CommonStates")]
[TemplateVisualState(Name = "DisabledState", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusStates")]
public class Button : Control
{
public static readonly DependencyProperty BackgroundProperty;
public static readonly DependencyProperty BorderBrushProperty;
public static readonly DependencyProperty BorderThicknessProperty;
public static readonly DependencyProperty ContentProperty;
public static readonly DependencyProperty ContentTemplateProperty;
public static readonly DependencyProperty FontFamilyProperty;
public static readonly DependencyProperty FontSizeProperty;
public static readonly DependencyProperty FontStretchProperty;
public static readonly DependencyProperty FontStyleProperty;
public static readonly DependencyProperty FontWeightProperty;
public static readonly DependencyProperty ForegroundProperty;
public static readonly DependencyProperty HorizontalContentAlignmentProperty;
public static readonly DependencyProperty PaddingProperty;
public static readonly DependencyProperty TextAlignmentProperty;
public static readonly DependencyProperty TextDecorationsProperty;
public static readonly DependencyProperty TextWrappingProperty;
public static readonly DependencyProperty VerticalContentAlignmentProperty;
public Brush Background { get; set; }
public Brush BorderBrush { get; set; }
public Thickness BorderThickness { get; set; }
public object Content { get; set; }
public DataTemplate ContentTemplate { get; set; }
public FontFamily FontFamily { get; set; }
public double FontSize { get; set; }
public FontStretch FontStretch { get; set; }
public FontStyle FontStyle { get; set; }
public FontWeight FontWeight { get; set; }
public Brush Foreground { get; set; }
public HorizontalAlignment HorizontalContentAlignment { get; set; }
public Thickness Padding { get; set; }
public TextAlignment TextAlignment { get; set; }
public TextDecorationCollection TextDecorations { get; set; }
public TextWrapping TextWrapping { get; set; }
public VerticalAlignment VerticalContentAlignment { get; set; }
}
|
A control contract has three elements:
The public properties that visually affect the control. These are properties that you can set to change the appearance of the control without creating a new ControlTemplate.
The UIElement objects that the control's logic uses. For example, the code might handle an event of an element.
The VisualState objects that specify the appearance of the control when it is a certain state, and the VisualStateGroup each one belongs to.
The Button does not have UIElement parts in its control contract, but more complex controls do. When a control expects to find a particular UIElement in the ControlTemplate, it uses the TemplatePartAttribute to convey information about the element.
VisualState objects enable you to change the appearance of a control by specifying a Storyboard to apply when the control is in a certain state. The following example shows the VisualState that changes the appearance of a Button; when the mouse is over it the color of the BorderBrush changes.
|
<Button Style="{StaticResource newTemplate}"
Foreground="Green" Content="Button1"
FontSize="14"/>
<Button Style="{StaticResource newTemplate}" HorizontalContentAlignment="Left"
Foreground="Red" Content="Button2" IsEnabled="False"
FontSize="18"/>
|
|
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="BorderBrush"
Storyboard.TargetProperty="Color" To="Red" />
</Storyboard>
</vsm:VisualState>
|
The VisualState objects of a control can be mutually exclusive to some states, but not to others. For example, a Button can have focus when the mouse over it, or can have focus when the mouse is not over it. Whether the mouse is over the Button does not affect whether it has focus, so the MouseOver state and the Focused state are not mutually exclusive. To indicate which VisualState objects are mutually exclusive to each other, group them in a VisualStateGroup. The following example shows two VisualStateGroup objects for the Button.
|
<vsm:VisualStateManager.VisualStateGroups>
<!--Define the states and transitions for the common states.
The states in the VisualStateGroup are mutually exclusive to
each other.-->
<vsm:VisualStateGroup x:Name="CommonStates">
<!--Define the VisualTransitions that can be used when the control
transitions between VisualStates that are defined in the
VisualStatGroup.-->
<vsm:VisualStateGroup.Transitions>
<vsm:VisualTransition To="MouseOver" Duration="0:0:1" />
<vsm:VisualTransition From="Pressed" To="MouseOver" Duration="0:0:0.01" />
<vsm:VisualTransition To="Pressed" Duration="0:0:0.01" />
<vsm:VisualTransition From="MouseOver" To="Normal" Duration="0:0:1.5">
<Storyboard>
<ColorAnimationUsingKeyFrames
Storyboard.TargetProperty="Color"
Storyboard.TargetName="BorderBrush"
FillBehavior="HoldEnd" >
<ColorAnimationUsingKeyFrames.KeyFrames>
<LinearColorKeyFrame Value="Blue" KeyTime="0:0:0.5" />
<LinearColorKeyFrame Value="Yellow" KeyTime="0:0:1" />
<LinearColorKeyFrame Value="#aac" KeyTime="0:0:1.5" />
</ColorAnimationUsingKeyFrames.KeyFrames>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualTransition>
</vsm:VisualStateGroup.Transitions>
<!--Define the VisualStetes in this VistualStateGroup.-->
<vsm:VisualState x:Name="Normal" />
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="BorderBrush"
Storyboard.TargetProperty="Color" To="Red" />
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Pressed">
<Storyboard >
<ColorAnimation Storyboard.TargetName="BorderBrush"
Storyboard.TargetProperty="Color" To="Orange"
/>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="DisabledState">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="DisabledRect"
Storyboard.TargetProperty="Opacity" To="1" Duration="0" />
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<!--Define the states and transitions for the focus states.
The states in the VisualStateGroup are mutually exclusive to
each other.-->
<vsm:VisualStateGroup x:Name="FocusStates">
<!--Define the VisualStetes in this VistualStateGroup.-->
<vsm:VisualState x:Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual"
Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Unfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual"
Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
|
You can also specify how a control should transition between states. For example, you can set the Duration property to specify that the transition from one state to another should take a certain amount of time. You can also restrict when a VisualTransition can be applied by setting its To and From properties. The following example creates a transition that occurs only when the control goes from the MouseOver state to the Pressed state.
|
<vsm:VisualTransition From="Pressed" To="MouseOver" Duration="0:0:0.01" />
|
You can create a VisualTransition with different levels of restriction. The following table describes the levels of restriction from most restrictive to least restrictive.
Type of restriction
|
Value of From
|
Value of To
|
|---|
From a specified state to another specified state
|
The name of a VisualState |
The name of a VisualState |
From any state to a specified state
|
Not set
|
The name of a VisualState |
From a specified state to any state
|
The name of a VisualState |
Not set
|
From any state to any other state
|
Not set
|
Not set
|
You can have multiple VisualTransition objects in a VisualStateGroup that refer to the same state, but they will be used in the order that the table above specifies. In the following example, there are two VisualTransition objects. When the control transitions from the Pressed state to the MouseOver state, the VisualTransition that has both From and To set is used. When the control transitions from a state that is not Pressed to the MouseOver state, the other state is used.
|
<vsm:VisualTransition To="MouseOver" Duration="0:0:1" />
<vsm:VisualTransition From="Pressed" To="MouseOver" Duration="0:0:0.01" />
|
You can specify an animation to take place when the VisualTransition occurs. For example, you can specify that a certain animation occurs when the control transitions from the MouseOver state to the Normal State. The following example shows the VisualTransition that occurs when the user moves the mouse away from the button.
|
<vsm:VisualTransition From="MouseOver" To="Normal" Duration="0:0:1.5">
<Storyboard>
<ColorAnimationUsingKeyFrames
Storyboard.TargetProperty="Color"
Storyboard.TargetName="BorderBrush"
FillBehavior="HoldEnd" >
<ColorAnimationUsingKeyFrames.KeyFrames>
<LinearColorKeyFrame Value="Blue" KeyTime="0:0:0.5" />
<LinearColorKeyFrame Value="Yellow" KeyTime="0:0:1" />
<LinearColorKeyFrame Value="#aac" KeyTime="0:0:1.5" />
</ColorAnimationUsingKeyFrames.KeyFrames>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualTransition>
|
Preserving the Functionality of a Control's Properties by Using TemplateBinding
The control contract indicates that properties can be used to change the appearance of the control without changing the ControlTemplate. When you create a new ControlTemplate, you still might want to use the public properties to change the control's appearance. The TemplateBinding markup extension binds a property of an element that is in the ControlTemplate to a public property that is defined by the control. In the previous example, the Foreground and FontSize properties of the TextBlock, TextElement, are bound to the properties of MyButton that have the same name. You can create multiple buttons and use the same ControlTemplate, yet set the Foreground and FontSize properties to different values on each button. The following example creates two MyButton controls that use the style that was defined in the previous example. This example sets the Foreground and FontSize properties on each control to different values, so the text inside of each control has a different appearance.
|
<Button Style="{StaticResource newTemplate}"
Foreground="Green" Content="Button1"
FontSize="14"/>
<Button Style="{StaticResource newTemplate}" HorizontalContentAlignment="Left"
Foreground="Red" Content="Button2" IsEnabled="False"
FontSize="18"/>
|
|
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="BorderBrush"
Storyboard.TargetProperty="Color" To="Red" />
</Storyboard>
</vsm:VisualState>
|