Walkthrough: Customizing the Appearance of a Button by Using a ControlTemplate

Microsoft Silverlight will reach end of support after October 2021. Learn more.

You can customize the appearance of a control by giving it a new ControlTemplate. When you create a ControlTemplate, you replace the appearance of an existing control without changing its functionality. This walkthrough creates a ControlTemplate for a Button, but the procedure for creating a custom appearance for any other control would be similar.

In this walkthrough, you create a button that has the following properties.

  • The button is rectangular with a solid background and a thick black border.

  • When you move the mouse pointer over the button, the border fades to red.

  • When you move the mouse pointer away from the button, the border fades to blue, yellow, and then finally to black.

  • When you click the button, the border fades to transparent.

The following illustration shows two examples of this button.

Button with a custom ControlTemplate

To try this button, click the following link.

Run this sample

Download this Sample

NoteNote:

The steps in this walkthrough use Visual Studio, but you can also use Microsoft Expression Blend. Expression Blend provides a graphical user interface for creating control templates. For more information, see Create a Reusable Template for a System Control.

Prerequisites

You need the following components to complete this walkthrough:

  • Silverlight 4.

  • Silverlight Tools for Visual Studio 2010.

  • Visual Studio 2010.

All of the Silverlight software is available from the Silverlight download site.

Creating a Silverlight Project in Visual Studio

The first step is to create a Silverlight project.

To create a Silverlight project

  • Create a new Silverlight Application project named ButtonSkin in Visual Basic or Visual C#. Do not select the Host the Silverlight application in a new Web site check box. For more information, see How to: Create a New Silverlight Project.

Adding the ControlTemplate in a Separate Assembly

In this walkthrough, the ControlTemplate of the Button is created in a separate assembly from the control itself.

To add the ControlTemplate in separate assembly

  1. Add a new Silverlight Class Library project named ButtonControlLibrary to the ButtonSkin solution.

  2. Add a folder named themes to the ButtonControlLibrary project.

  3. Add a Text File item named generic.xaml to the themes folder.

  4. Add the following XAML to generic.xaml.

    <ResourceDictionary     xmlns="https://schemas.microsoft.com/client/2007"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:ButtonControlLibrary;assembly=ButtonControlLibrary"
        xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
        <!-- The ControlTemplate goes here. -->
    </ResourceDictionary>
    
  5. Rename Class1.vb or Class1.cs to MyButton.vb or MyButton.cs.

  6. Open MyButton.vb or MyButton.cs.

  7. Add a class named MyButton that inherits from the Button control and assign the DefaultStyleKey in the constructor.

    Public Class MyButton
        Inherits Button
        Public Sub New()
            DefaultStyleKey = GetType(MyButton)
        End Sub
    End Class
    
    public class MyButton : Button
    {
        public MyButton()
        {
            DefaultStyleKey = typeof(MyButton);
        }
    }
    

Creating the Initial ControlTemplate

When you create a ControlTemplate, it is often easiest to begin with an existing ControlTemplate and make changes to it. You can find the default styles and templates of Silverlight controls in Control Styles and Templates.

To create the initial ControlTemplate

  1. Open the Button Styles and Templates.

  2. In the "Default Style and Template" section, copy the default style and template of the Button control.

  3. In generic.xaml, paste the default style and template within ResourceDictionary element.

  4. Delete the Setter elements up to, but not including <Setter Property="Template">.

  5. Delete the first Grid element, but not the elements within it.

  6. Delete all of the Storyboard elements, including the elements within them.

  7. Delete the Border element named Background and the elements within it.

  8. Delete the ContentPresenter element.

  9. Delete the Rectangle elements named DisabledVisualElement and FocusVisualElement.

    The Border, ContentPresenter, and Rectangle elements make up the structure of the default button control.

  10. In the Style element, change the TargetType property to src:MyButton.

    <Style TargetType="src:MyButton">
    
  11. In the ControlTemplate element, change the TargetType property to src:MyButton.

    <ControlTemplate TargetType="src:MyButton">
    

    At this point, the control template should look similar to the following XAML. In the next section, you will create your own button structure.

    <ResourceDictionary     xmlns="https://schemas.microsoft.com/client/2007"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:ButtonControlLibrary;assembly=ButtonControlLibrary"
        xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
        <Style TargetType="src:MyButton">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="src:MyButton">
                        <vsm:VisualStateManager.VisualStateGroups>
                            <!--Define the states for the common states. The states in a 
                                        VisualStateGroup are mutually exclusive to each other.-->
                            <vsm:VisualStateGroup x:Name="CommonStates">
                                <!--Define the VisualStates in this VistualStateGroup.-->
                                <vsm:VisualState x:Name="Normal"/>
                                <vsm:VisualState x:Name="MouseOver" />
                                <vsm:VisualState x:Name="Pressed" />
                                <vsm:VisualState x:Name="Disabled" />
                            </vsm:VisualStateGroup>
                            <!--Define the states for the focus states. The states in a 
                                        VisualStateGroup are mutually exclusive to each other.-->
                            <vsm:VisualStateGroup x:Name="FocusStates">
                                <!--Define the VisualStates in this VistualStateGroup.-->
                                <vsm:VisualState x:Name="Focused" />
                                <vsm:VisualState x:Name="Unfocused" />
                            </vsm:VisualStateGroup>
                        </vsm:VisualStateManager.VisualStateGroups>
                        <!--The parts of the button control will be defined here.-->
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>
    

Creating the Visual Structure

The button control created in this section will have the following parts.

  • A Border named RootElement.

  • A Grid that is the child of RootElement.

  • A Rectangle that indicates if the button has focus.

  • A ContentPresenter that displays the button's content.

  • A Rectangle that causes the button to be grayed out when it is disabled.

To create the visual structure

  1. In generic.xaml, immediately after the <ControlTemplate TargetType="src:MyButton">, add a Border named RootElement.

    <Border x:Name="RootElement">
    
  2. Immediately before the </ControlTemplate>, add the closing tag for Border.

    </Border>
    

    This serves as the template's root FrameworkElement.

  3. Immediately after the </vsm:VisualStateManager.VisualStateGroups> end tag, add the following XAML.

    This XAML creates the button background, border and ContentPresenter. It also adds a Rectangle that causes the button to be grayed out when it is disabled.

        <!--Create the SolidColorBrush for the Background 
    as an object elemment and give it a name so 
    it can be referred to elsewhere in the control template.-->
        <Border.Background>
            <SolidColorBrush x:Name="BorderBrush" Color="Black"/>
        </Border.Background>
    
        <!--Create a border that has a different color by adding smaller grid.
    The background of this grid is specified by the button's Background
    property.-->
        <Grid Background="{TemplateBinding Background}" Margin="4">
    
            <!--Create a Rectangle that indicates that the
      Button has focus.-->
            <Rectangle Name="FocusVisual" 
            Visibility="Collapsed" Margin="2" 
            Stroke="{TemplateBinding Foreground}" StrokeThickness="1" 
            StrokeDashArray="1.5 1.5"/>
    
            <!--Use a ContentPresenter to display the Content of
      the Button.-->
            <ContentPresenter
    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
    Margin="4,5,4,4" />
    
            <!--Create a rectangle that causes the button to appear
    grayed out when it is disabled.-->
            <Rectangle x:Name="DisabledRect" 
           Fill="#A5FFFFFF"
           Opacity="0" IsHitTestVisible="false" />
        </Grid>
    
  4. Immediately after <Style TargetType="src:MyButton">, set the default properties for the button by adding the following XAML.

    <!--Set the Background, Foreground, FontSize, Width, 
        Height, Margin, and Template properties for
        the Button.-->
    <Setter Property="Background" Value="Navy"/>
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="FontSize" Value="14"/>
    <Setter Property="Width" Value="100"/>
    <Setter Property="Height" Value="40"/>
    <Setter Property="Margin" Value="10"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    
  5. Build the solution.

Defining the Appearance Depending on its State

You use the VisualState objects to specify the appearance of a control when it is in a certain state. A VisualState contains a Storyboard that changes the appearance of the elements that are in the ControlTemplate. In this section, you add XAML for visual states.

To define the appearance depending on its state

  1. In generic.xaml, replace the existing MouseOver state with the following XAML.

    This sets the button border to red when the mouse pointer is over the button.

            <!--Change the border of the button to red when the
    mouse is over the button.-->
            <vsm:VisualState x:Name="MouseOver">
                <Storyboard>
                    <ColorAnimation Storyboard.TargetName="BorderBrush" 
                    Storyboard.TargetProperty="Color" To="Red" />
    
                </Storyboard>
            </vsm:VisualState>
    
  2. Replace the existing Pressed state with the following XAML.

    This sets the button border to transparent when the button is in Pressed state.

            <!--Change the border of the button to Transparent when the
    button is pressed.-->
            <vsm:VisualState x:Name="Pressed">
                <Storyboard >
                    <ColorAnimation Storyboard.TargetName="BorderBrush" 
                    Storyboard.TargetProperty="Color" To="Transparent" 
                    />
                </Storyboard>
            </vsm:VisualState>
    
  3. Replace the existing Disabled state with the following XAML.

    This sets the Opacity of DisabledRect to 1 in the Disabled state. This will display the DisabledRect when the IsEnabled property on the button is set to false.

            <!--Show the DisabledRect when the IsEnabled property on
    the button is false.-->
            <vsm:VisualState x:Name="Disabled">
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="DisabledRect" 
                     Storyboard.TargetProperty="Opacity"
                     To="1" Duration="0" />
                </Storyboard>
            </vsm:VisualState>
    
  4. Replace the existing Focused and Unfocused states with the following XAML.

          <!--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 VisualStates 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>
    
  5. Build the solution.

Specifying the Visual Behavior

Use a VisualTransition to specify the amount of time it takes the control to transition into a particular state. The GeneratedDuration property specifies how long a transition takes. In this section you add XAML for visual transitions.

To specify the visual behavior

  1. In generic.xaml, immediately after the <vsm:VisualStateGroup x:Name="CommonStates"> start tag, add the following Transitions element.

    <!--Define the VisualTransitions that can be used when the control transitions between VisualStates that are defined in the VisualStatGroup.-->
    <vsm:VisualStateGroup.Transitions>
    </vsm:VisualStateGroup.Transitions>
    
  2. Within the Transitions element, add the following VisualTransition object to specify that the button should take one hundredth of a second to go into the pressed state.

              <!--Take one hundredth of a second to transition to the
    Pressed state.-->
              <vsm:VisualTransition To="Pressed" 
                      GeneratedDuration="0:0:0.01" />
    
  3. Within the Transitions element, add the following VisualTransition object to specify that the button should take one half of a second to go into the MouseOver state.

    <!--Take one half second to trasition to the MouseOver state.-->
    <vsm:VisualTransition To="MouseOver" 
            GeneratedDuration="0:0:0.5" />
    
  4. Within the Transitions element, add the following VisualTransition object to specify that the button should take one hundredth of a second to go from the Pressed state to the MouseOver state.

              <!--Take one hundredth of a second to transition from the
    Pressed state to the MouseOver state.-->
              <vsm:VisualTransition From="Pressed" To="MouseOver" 
                      GeneratedDuration="0:0:0.01" />
    
  5. Within the Transitions element, add the following Storyboard to specify that an animation occurs when the control transitions from the MouseOver state to the Normal state.

    When the user moves the mouse pointer away from the button, the button's border changes to blue, then to yellow, then to black in 1.5 seconds.

              <!--Take one and a half seconds to transition from the
    MouseOver state to the Normal state. 
    Have the SolidColorBrush, BorderBrush, fade to blue, 
    then to yellow, and then to black in that time.-->
              <vsm:VisualTransition From="MouseOver" To="Normal" 
                      GeneratedDuration="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="Black" 
                             KeyTime="0:0:1.5" />
    
                          </ColorAnimationUsingKeyFrames.KeyFrames>
                      </ColorAnimationUsingKeyFrames>
                  </Storyboard>
              </vsm:VisualTransition>
    
  6. Build the solution.

Testing the Custom Button

To test the custom button, you need to reference it properly from the application project.

To test the custom button

  1. In the ButtonSkin project, add a reference to the ButtonControlLibrary project.

  2. In MainPage.xaml, add the following namespace mapping to the existing mappings.

    xmlns:blib="clr-namespace:ButtonControlLibrary;assembly=ButtonControlLibrary"
    
  3. Replace the Grid element with a StackPanel.

    <StackPanel>
    
    </StackPanel>
    
  4. Within the StackPanel, create two MyButton controls.

    <StackPanel>
        <blib:MyButton Content="Button1" />
        <blib:MyButton Content="Button2" Background="Purple" />
    </StackPanel>
    
  5. Build the solution.

  6. Run the application.

    You should see a page appear with two custom buttons.

  7. Try moving your mouse pointer over the buttons and clicking the buttons to test the behavior.