Building the Template
The core of overriding the default look of a WPF component is to create a Template setter as shown in the snippet below. For a complete listing of the components look, see
Common.xaml which you can find in both the
downloadable source file and in Listing 1:
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
It's important to note that you need to define the
TargetType in your style property in order for the proper dependency properties to be available. Because different event triggers are available for different types of components, this property helps determine what events and options are available as setters. If the syntax of this style property looks strange to you, you might want to take a look at the sidebar
WPF Oddities.
Once inside the
ControlTemplate tag, you're free to put any kind of component or container. It expects only one component, so to do something more complex, your one component will need to be a layout component that can contain more sub-components, such as a Grid or StackPanel. The code example included with this article uses a Border component and a few sub-border components, one of which contains a StackPanel to hold multiple child components inside of a single Border.
Inside your template, one important tag is the
ContentPresenter, which is responsible for displaying the sub-component provided to the usage of your template. For a button, this is typically the text label provided to the button via the
Text attribute:
<ContentPresenter />
The
ContentPresenter itself supports a number of dependency properties that allow you to customize how you want sub-components to be handled. For most types of components, it's enough to just include an empty
ContentPresenter tag, just to get the included text rendered.
Responding to Events
 | |
| Figure 2. Mouseover Effect Applied: In this figure, the "Eyes" button has been clicked, and the mouseover effect has added a shadow at the top and the shine at the bottom. |
Most components include some form of interactivityand any form of interactivity requires that something happen with the component to let the user know that the component has accepted their input. The code example included provides support only for a mouseover effect. When the mouseover effect is applied it adds the shadow at the top of the button and the shine at the bottom to create the look of a slight depression in the button area.
Figure 2 illustrates the mouse-over state of the "Eyes" button:
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Border"
Property="Background"
Value="{StaticResource NormalBrush}" />
<Setter TargetName="TopHighlight"
Property="Background"
Value="{StaticResource ShadowInverted}"/>
<Setter TargetName="BottomHighlight"
Property="Background"
Value="{StaticResource ShinyInverted}"/>
</Trigger>
You don't have to create the inverse state when working with events in XAML. For example, the included code provides setters that set the borders to use different gradients to invert the direction of the highlight and shadow on the PillButton if
IsMouseOver=True. You don't have to define an event for
IsMouseOver=False to return these to a normal state.
If your component contains a multiple components as the PillButton does, you can address each of these items to change different style properties on the elements by setting the
TargetName. When defining the component's template, each component you define will need to have an
x:Name attribute set if you want to be able to modify this element in your event triggers. In the setters of your event trigger, the
TargetName correlates to the
x:Name of the component defined in the template.
Each component type has a multitude of eventsmany are unique to that particular component type. For some help locating what events are supported by a given WPF component, take a look at the
What Events Can I Respond To?
Achieving the Glass Look
The reflection is courtesy of a rectangle placed below the pill itself that applies a few filters. This rectangle is automatically sized to match the pill via the binding reference:
<Rectangle
Height="{Binding Path=ActualHeight,ElementName=Border}"
Width="{Binding Path=ActualWidth, ElementName=Border}">
Within the rectangle, you can use a VisualBrush object that uses another component as a source. In this case, the code uses the pill button as the visual source. It then applies a group of filters chained together that invert the image (mirror), apply a gradient transparency to it (to make it fade), and apply a blur effect. The combined result of these three filters on the rendered result of the pill button is the coveted glass effect shown in
Figure 1.
Because all this magic is done in real time using full binding, if you animate the pill the reflection will move and resize with the pill.
Lookless controls provide a powerful and flexible way to define application look and feel while cleanly separating visual style from behavior. For further exploration, look into styling other types of WPF controls. Each control has unique properties and approaches that you'll need to explore to figure out how to take each control to its limitsand beyond.