RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


WPF Wonders: Building Control Templates : Page 4

WPF's properties and styles let you change a control's appearance. Templates let you modify a control at a much more fundamental level, changing the components that make up the control and the way it acts. This article shows how you can use templates to change the fundamental structure of WPF controls.


Building Better Buttons

The previous templates build distinctive Labels, but the Label control is a simple control. The template trigger handles the enabled and disabled states, but Labels mostly just sit there looking nice. They don't change their appearance in response to key presses, mouse overs, or button clicks.

A Button control is a little more complicated. Structurally, it's still relatively simple, but it has more states than a Label does. In particular, the button should respond to these states:

  • Normal: The button is just sitting there twiddling its virtual thumbs. This is the default state.
  • Mouseover: The user has moved the mouse over the button.
  • Focus: The input focus has moved to the button. If the user presses Enter or Space at this time, the button fires its Click event.
  • Defaulted: When the button's IsDefault property is True and no other button has the focus, then the button is said to be defaulted, and it should display a distinctive appearance. Normally, buttons display a dashed rectangle when defaulted.
    Figure 5. Beautiful Buttons: This figure shows Buttons in their disabled, defaulted, mouse over, and pressed states.
  • Pressed: The button has been pressed but not yet released.
  • Disabled: The button's Enabled property is False.

The ShapedButton example program shown in Figure 5 demonstrates a shaped Button template. The images in Figure 5 show the Button in four different states. The leftmost Button in each image is disabled so it's extra pale. The second button in the top image is defaulted while the focus is on the TextBox at the bottom of the window. In the second image, the use has moved the mouse over Button 3, which makes it brighter, and its text glows. In the final image, the user has clicked the mouse on Button 3; in its depressed state, the Button displays a special bevel style and a larger glow.

The first step in defining this template is to decide what controls it will use. The following code shows how the ShapedButton program defines the controls used by its template.

    <!-- Background -->
    <Polygon Name="pgnBody" Stretch="Fill"
     Stroke="Black" Fill="LightBlue"
     Points="50,0 100,50 50,100 0,50">
            <BevelBitmapEffect BevelWidth="5"/>
    <!-- Content. -->
    <ContentPresenter Name="cpContent" Opacity="0.5"
     VerticalAlignment="Center" HorizontalAlignment="Center"/>
    <!-- Tone everything down a bit. -->
    <Polygon Name="pgnCover" Stretch="Fill" Opacity="0.5"
     Points="50,0 100,50 50,100 0,50">
            <LinearGradientBrush StartPoint="10,0" EndPoint="0,10"
             SpreadMethod="Reflect" MappingMode="Absolute">
                <GradientStop Color="LightBlue" Offset="0"/>
                <GradientStop Color="White" Offset="1"/>

This template starts with a Grid. Grids are often handy because they make centering and aligning other controls easy.

The grid contains a Polygon named pgnBody that draws the control's diamond-shaped background. This control has a thick beveled edge.

Next, the template includes the ContentPresenter. Because it is included after pgnBody, it is drawn on top so the user can see it. The ContentPresenter's VerticalAlignment and HorizontalAlignment properties center it in the Grid. (In the main XAML code, the third Button contains a StackPanel holding two Labels with contents "Button" and "3." Notice in Figure 5 that the ContentPresenter includes the StackPanel and its contents in the Button.)

The only other control that the template uses is a second Polygon named pgnCover that fits over the first Polygon. It is filled with a LinearGradientBrush that shades from light blue to white. This Polygon's Opacity is initially 0.5 so it tones down all the template's other controls.

Those four controls (a Grid, two Polygons, and the ContentPresenter) define the template's structure. Now the template needs to describe how these controls should change for different control states.

To make that a little easier, the template includes a Resources section that defines the following three bitmap effects.

    <!-- Make the content glow during mouse over. -->
    <BitmapEffectGroup x:Key="MouseOverGlow">
        <OuterGlowBitmapEffect GlowSize="5"/>
    <!-- Change the bevel when pressed. -->
    <BitmapEffectGroup x:Key="PressedBevel">
        <BevelBitmapEffect EdgeProfile="BulgedUp"
    <!-- Make the content glow more when pressed. -->
    <BitmapEffectGroup x:Key="PressedGlow">
        <OuterGlowBitmapEffect GlowSize="10"/>

The first effect defines an outer glow that takes effect when the mouse is over the button.

The second effect defines a new bevel effect that makes the edges of the button seem bulged up (the third Button in the last image in Figure 5) when the button is pressed.

The final effect defines a larger glow, which the program applies when the button is pressed.

The final piece to the template is its <Triggers> section, shown in the following code. Triggers are applied in the order in which they are listed in the code, so later triggers can override the behavior of earlier triggers. For example, if the mouse is both over the Button and the mouse is pressed, the mouseover trigger occurs first, followed by the mouse-pressed trigger.

    <!-- Mouse over. -->
    <Trigger Property="IsMouseOver" Value="True">
        <Setter TargetName="pgnCover"
         Property="Opacity" Value="0"/>
        <Setter TargetName="cpContent"
         Value="{StaticResource MouseOverGlow}"/>
    <!-- Focus. -->
    <Trigger Property="IsFocused" Value="True">
        <Setter TargetName="pgnCover"
         Property="Opacity" Value="0"/>
        <Setter TargetName="cpContent"
         Property="Opacity" Value="1"/>
        <Setter TargetName="cpContent"
         Value="{StaticResource MouseOverGlow}"/>
    <!-- Defaulted. -->
    <Trigger Property="IsDefaulted" Value="True">
        <Setter TargetName="pgnCover"
         Property="Opacity" Value="0.25"/>
        <Setter TargetName="pgnBody"
         Property="StrokeThickness" Value="3"/>
        <Setter TargetName="pgnBody"
         Property="StrokeDashArray" Value="3,3"/>
    <!-- Pressed. Comes after Focus, so it gets precedence. -->
    <Trigger Property="IsPressed" Value="True">
        <Setter TargetName="pgnBody"
         Value="{StaticResource PressedBevel}"/>
        <Setter TargetName="cpContent"
         Value="{StaticResource PressedGlow}"/>
    <!-- Disabled. Comes last so it gets ultimate precedence. -->
    <Trigger Property="IsEnabled" Value="False">
        <Setter TargetName="pgnCover"
         Property="Opacity" Value="0.75"/>

The IsMouseOver trigger sets the pgnCover control's Opacity to 0, which turns it transparent, effectively removing the cover and letting the remaining controls show at full brightness. The trigger also sets the ContentPresenter's bitmap effect to display a glow.

The IsFocused trigger also sets the pgnCover control's Opacity to 0, but it also sets the ContentPresenter's Opacity to 1 so it is fully opaque and very bright. Finally, it gives the ContentPresenter the same glow as the previous trigger.

The IsDefaulted trigger sets the pgnCover control's Opacity to 0.25, making it more transparent than it normally is, but not as transparent as when the mouse is over the Button or when the Button has focus. The trigger also gives the main background Polygon pgnBody a thick dashed border (see the first image in Figure 5).

The IsPressed trigger changes the background Polygon's bitmap effect to the bulging beveled edge and gives the ContentPresenter the larger glow.

The IsEnabled = False trigger comes last, so it takes precedence over all of the other triggers. This trigger sets the pgnCover control's Opacity to 0.75, so it is relatively opaque and the Button seems lighter than ever. The Button control itself automatically disables the controls so they don't respond to the user.

Download the example and experiment with it. First, give the normal behaviors a try. Use the Tab key to move between the Buttons and see how the defaulted control responds. Next, try moving the mouse over the Buttons and clicking on them.

Finally, if you're feeling adventurous, try changing the template's controls and triggers to see what else you can make the example do.

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date