Arguably the most celebrated feature in WPF is the ability to give any user interface element a radically different look without having to give up all of the built-in functionality that it provides. Even with Cascading Style Sheets (CSS), HTML lacks this much power, which is the reason most websites use images to represent buttons rather than “real buttons.” Of course, it’s pretty easy to simulate a button’s behavior with an image in HTML, but what if you want to give a completely different look to a SELECT
element (HTML’s version of ComboBox
)? It’s a lot of work if you want to do more than change simple properties (such as its foreground and background colors).
This chapter explains the four main components of WPF’s restyling support:
• Styles—A simple mechanism for separating property values from user interface elements (similar to the relationship between CSS and HTML). Styles are also the foundation for applying the other mechanisms in this chapter.
• Templates—Powerful objects that most people are really referring to when they talk about “restyling” in WPF.
• Skins—Application-specific collections of styles and/or templates, typically with the ability to be replaced dynamically.
• Themes—Visual characteristics of the host operating system, with potential customizations by the end user.
As you’ll see, an important enabler of WPF’s restyling support is the semantics of resources.
A style, represented by the System.Windows.Style
class, is a pretty simple entity. Its main function is to group together property values that could otherwise be set individually. The intent is to then share this group of values among multiple elements.
Take, for example, the three customized Button
s in Figure 14.1. This look is achieved by setting seven properties. Without a Style
, you would need to duplicate these identical assignments on all three Button
s, as shown in Listing 14.1.
But with a Style
, you can add a level of indirection—setting the properties in one place and pointing each Button
to this new element, as shown in Listing 14.2. Style
uses a collection of Setter
s to set the target properties. Creating a Setter
is just a matter of specifying the name of a dependency property (qualified with its class name) and a desired value for it.
Using a Style
is nice for several reasons, such as having only one spot to change if you later have second thoughts about rotating the Button
s or if you want to change their Background
. Defining a Style
as a resource also gives you all the flexibility that the resource mechanism provides. For example, you could define one version of buttonStyle
at the application level but override it with a different Style
(still with a key of buttonStyle
) in an individual Window
’s Resources
collection.
Note that despite its name, there’s nothing inherently visual about a Style
. But it’s typically used for setting properties that affect visuals. Indeed, Style
only enables the setting of dependency properties, which tend to be visual in nature.
Style
s can even inherit from one another! The following Style
adds bold text to the buttonStyle
defined in Listing 14.2 by using the BasedOn
property,:
Style
sAlthough you could set an element’s Style
property directly in its XAML definition (using property element syntax), the whole point of using a Style
is to share it among multiple elements, as done in Listing 14.2. Style
supports a few different mechanisms that enable you to control exactly how that sharing occurs.
Although the Style
in Listing 14.2 is shared among three Button
s, with some tweaks it can also be shared among heterogeneous elements. Listing 14.3 accomplishes this by changing each Button.
XXX
referenced inside the Style
to Control.
XXX
and then applying the new style to many elements. The result is shown in Figure 14.2.
You don’t need to worry about a Style
being applied to an element that doesn’t have all the listed dependency properties; the properties that exist are set and the ones that don’t exist are ignored. For example, InkCanvas
doesn’t have Foreground
or FontSize
properties. Yet when the Style
is applied to it in Listing 14.3, all the relevant properties (Background
, Height
, Width
, and so on) are correctly applied. Similarly, adding the following Setter
to the Style
in Listing 14.3 affects the TextBox
but leaves all the other elements looking as they do in Figure 14.2:
Any individual element can override aspects of its Style
by directly setting a property to a local value. For example, the Button
in Listing 14.3 could do the following to retain the rotation, size, and so on from controlStyle
yet have a red Background
rather than a purple one:
This works because of the order of precedence for dependency property values presented in Chapter 3, “WPF Fundamentals.” The local value trumps anything set from a Style
.
To enable sharing of complex property values even within a Style
, Style
has its own Resources
property. You can leverage this collection to make your Style
self-contained rather than create a potentially brittle dependency to resources defined elsewhere.
Style
sIf you want to enforce that a Style
can be applied only to a particular type, you can set its TargetType
property accordingly. For example, the following Style
can be applied only to a Button
(or a subclass of Button
):
Any attempt to apply this Style
to a non-Button
generates a compile-time error. Therefore, TargetType="{x:Type Control}"
could be applied to the Style
in Listing 14.3, and it would still work with all the elements except InkCanvas
.
In addition, when you apply a TargetType
to a Style
, you no longer need to prefix the property names inside Setter
s with the type name. So, the previous XAML snippet could be rewritten as follows and have exactly the same meaning:
Style
sApplying a TargetType
to a Style
gives you another feature as well. If you omit its Key
, the Style
gets implicitly applied to all elements of that target type within the same scope. This is typically called a typed style as opposed to a named style, which is the only kind of Style
you’ve seen so far.
The scope of a typed Style
is determined by the location of the Style
resource. For example, it could implicitly apply to all relevant elements in a Window
if it’s a member of Window.Resources
. Or, it could apply to an entire application if you define it as an application-level resource, as follows:
In such an application, all Button
s get this style by default. But each Button
can still override its appearance by explicitly setting a different Style
or explicitly setting individual properties. Any Button
can restore its default Style
by setting its Style
property to null
.
TargetType
must match exactly for a typed style to be applied!With a named style, it’s okay for the target element to be a subclass of the TargetType
. But a typed style typically gets applied only to elements whose type matches exactly. This is done to prevent surprises. For example, maybe you’ve created a Style
for all ToggleButton
s in your application but you don’t want it applied to any CheckBox
es. (CheckBox
is a subclass of ToggleButton
.) This behavior is controlled by each element (by its selection of a default style key, covered in the “Themes” section at the end of this chapter). Therefore, it’s possible to write a custom element that inherits the typed style from its base class.
Style
s can be applied in multiple places. For example, all FrameworkElement
s and FrameworkContentElement
s have a FocusVisualStyle
property in addition to their Style
property. A Style
assigned to FocusVisualStyle
is active only when the element has keyboard focus, and it is very handy for overriding the look of the standard dotted rectangle that indicates keyboard focus (which can look weird when a control has been drastically altered).
Some controls have their own additional places to plug in a Style
. For example, ItemsControl
has an ItemContainerStyle
property that applies to each item’s container (such as ListBoxItem
or ComboBoxItem
). Other controls, such as ToolBar
, expose ResourceKey
properties that represent the keys for several Style
s used internally, such as ButtonStyleKey
and TextBoxStyleKey
. Although these XXX
StyleKey
properties are read-only, you can leverage these keys to define your own Style
s that override the default ones. Here’s an example:
One reason ToolBar
uses ResourceKey
properties instead of Style
properties is that dependency properties do not support dynamic resource references as their default value. ItemsControl
can get away with giving ItemContainerStyle
a default value of null
because the default style for its item container is always the same. ToolBar
, however, requires different default styles, depending on the theme.
Triggers, first introduced in Chapter 3, have a collection of Setter
s just like Style
(and/or collections of TriggerAction
s). But whereas a Style
applies its values unconditionally, a trigger performs its work based on one or more conditions.
Recall that there are three types of triggers:
• Property triggers—Invoked when the value of a dependency property changes
• Data triggers—Invoked when the value of a plain .NET property changes
• Event triggers—Invoked when a routed event is raised
FrameworkElement
, Style
, DataTemplate
, and ControlTemplate
(covered in the next section) all have a Triggers
collection, but whereas Style
and the template classes accept all three types, FrameworkElement
accepts only event triggers. Fortunately, Style
happens to be the logical place to put triggers even if you had a choice because of the ease in sharing them and their direct tie to the visual aspects of elements.
So, let’s look at a few examples of property triggers and data triggers inside styles. We’ll save event triggers for Chapter 17, “Animation.”
A property trigger (represented by the Trigger
class) executes a collection of Setter
s when a specified property has a specified value. And when the property no longer has this value, the property trigger “undoes” the Setter
s.
For example, the following update to buttonStyle
makes the rotation happen only when the mouse pointer is hovering over the Button
and sets the Foreground
to Black
rather than White
:
Figure 14.3 shows the result of applying this Style
to a Button
. The trigger sets the Foreground
to Black
so the content is more readable against the light blue background that Button
s get by default in Windows 7 while the mouse is hovering. This “hover background” is not baked into Button
but rather comes from a trigger on Button
’s theme style (discussed in the “Themes” section at the end of the chapter). It can be overridden by explicitly setting the Background
property inside the trigger we just created.
A more complicated example of a property trigger is one that works with data-binding validation rules. In the preceding chapter, we created a JpgValidationRule
class attached to a data-bound TextBox
and ensured that the user only typed in a valid .jpg
filename. To declaratively and visually show the results of a failed validation, you can create a property trigger based on the Validation.HasError
attached property, as follows:
In this property trigger, data binding is used to provide an appropriate message inside the ToolTip
. Notice the handy use of RelativeSource
to grab the Validation.Errors
attached property from whatever element this Style
ends up being applied to.
Applying this Style
to a TextBox
such as the following produces the result shown in Figure 14.4 when a validation error is raised:
Listing 14.4 demonstrates another use of property triggers in a style to take advantage of ItemsControl
’s AlternationIndex
property, introduced in Chapter 10, “Items Controls.” It also demonstrates the use of ItemsControl
’s ItemContainerStyle
property to style the sometimes-implicit item containers. (Recall that if you add arbitrary objects or elements to a ListBox
, for example, they get wrapped in ListBoxItem
containers.) Figure 14.5 shows the result.
This style gives items a white foreground on a green background by default, but when its AlternationIndex
attached property is 1
, the triggers change the foreground to black and the background to white. Therefore, this style is meant to be applied as an item container style to an items control with AlternationCount
set to 2
(giving the sequence 0, 1, 0, 1, ...).
Notice that in order to be used for both ListBoxItem
s and TreeViewItem
s, the style generically applies to Control
(the most derived base class common to both) and the attached property is referenced as ItemsControl.AlternationIndex
instead of something more specific (such as ListBox.AlternationIndex
). For this to work as shown in Figure 14.5, every TreeViewItem
with children must also have AlternationCount
set to 2
and the ItemContainerStyle
set to the appropriate style. That’s because TreeViewItem
(an items control itself) doesn’t inherit those settings from its parent.
Data triggers are just like property triggers, except that they can be triggered by any .NET property rather than just dependency properties. (The Setter
s inside a data trigger are still restricted to setting dependency properties, however.)
To use a data trigger, you add a DataTrigger
object to the Triggers
collection and specify the property/value pair. To support plain .NET properties, you specify the relevant property with a Binding
rather than a simple property name.
The following TextBox
has a Style
that triggers the setting of IsEnabled
, based on the value of its Text
property, which is not a dependency property. When Text
is the string "disabled"
, IsEnabled
is set to false
(which is admittedly an unusual application of a data trigger):
The same Binding
to the Text
property happens to be used outside the trigger, which sets the TextBox
’s Background
to whatever the Text
value is (thanks to the string-to-Brush
type converter). If Text
isn’t set to a valid color name, Background
reverts to its default color because of the way errors in data binding are handled. (The addition of data binding to a normal Setter
such as this can make it seem like it’s part of a trigger when it’s really not.) Figure 14.6 shows this TextBox
with a few different Text
values.
The logic expressed with the previous triggers has been in the form “when property=value, set the following properties.” But more powerful options exist:
• Multiple triggers can be applied to the same element (to get a logical OR).
• Multiple properties can be evaluated for the same trigger (to get a logical AND).
Because Style.Triggers
can contain multiple triggers, you can create more than one with exactly the same Setter
s to express a logical OR relationship:
This means, “if IsMouseOver
is true
or if IsFocused
is true
, apply the rotation and black foreground.”
To express a logical AND relationship, you can use a variation of Trigger
called MultiTrigger
or a variation of DataTrigger
called MultiDataTrigger
. MultiTrigger
and MultiDataTrigger
have a collection of Condition
s that contain the information you would normally put directly inside a Trigger
or DataTrigger
. Therefore, you can use MultiTrigger
as follows:
This means, “if IsMouseOver
is true
and if IsFocused
is true
, apply the rotation and black foreground.” MultiDataTrigger
works the same way as MultiTrigger
but with support for plain .NET properties.
If you want to add even more complex event-driven behavior to a Style
, you can make use of an EventSetter
(which shares a common base class with Setter
) to attach an event handler to any element that makes use of the Style
. EventSetter
s can be added to a Style
just like Setter
s:
Although this requires procedural code to handle the event, it is, nevertheless, a handy way to share a common handler among many elements without resorting to copying and pasting.
Control
s have many properties you can use to customize their look: Button
has configurable Background
and Foreground Brush
es (which can even be fancy gradients), TabControl
’s tabs can be relocated by setting the TabStripPlacement
property, and so on. But you can do only so much with such properties.
A template, on the other hand, allows you to completely replace an element’s visual tree with anything you can dream up, while keeping all of its functionality intact. And templates (like many other things in WPF) aren’t just some add-on mechanism for third parties; the default visuals for every Control
in WPF are defined in templates (and customized for each Windows theme). The source code for every control is completely separated from its default visual tree representations (or “visual source code”).
Templates and the desire to separate visuals from logic are also the reasons that WPF’s controls don’t expose more simple properties for tweaking their look. For example, it would be nice to change the color of the Expander
’s arrow back in Figure 14.2, as the gray color doesn’t show up nicely against the purple background. This relatively simple change can be accomplished only by defining a new template for Expander
, however. Expander
has no ArrowBrush
or ArrowColor
property because an Expander
with a custom template might not even have an arrow!
There are a few different kinds of templates. What has been described so far is the focus of this section: control templates. Control templates are represented by the ControlTemplate
class that derives from the abstract FrameworkTemplate
class. The other FrameworkTemplate
-derived classes are covered in previous chapters: DataTemplate
(described in the preceding chapter) and ItemsPanelTemplate
(described in Chapter 10). Data templates customize the look of any .NET object, which is especially important for non-UIElement
s, whose default template is simply a TextBlock
containing a string returned by its ToString
method. ItemsPanelTemplate
s can be assigned to an ItemsControl
’s ItemsPanel
as an easy way to alter its layout.
Slick custom visuals undoubtedly involve using 2D (or 3D!) graphics, animation, or other rich media, covered in the next part of the book. This chapter sticks to some simple 2D drawings.
The important piece of the ControlTemplate
class is its VisualTree
content property, which contains the tree of elements that define the desired look. After you define a ControlTemplate
(undoubtedly in XAML), you can attach it to any Control
or Page
by setting its Template
property. Listing 14.5 defines a simple yet slick control template as a resource and then applies it to a single Button
. Figure 14.7 shows the result.
To get this look, the template’s visual tree uses two circles (created with Ellipse
elements) placed inside a single-cell Grid
. Despite the custom look, the resultant Button
still has a Click
event, an IsDefault
property, and all the other functionality you’d expect. After all, it is still an instance of the Button
class!
In Listing 14.5, the Button
is considered the templated parent of the elements in the control template’s visual tree. FrameworkElement
and FrameworkContentElement
both have a TemplatedParent
property that represents this relationship.
As with Style
s, Template
s can contain all types of triggers in a Triggers
collection. Listing 14.6 adds triggers to the preceding ControlTemplate
to visually respond to a mouse hover and click. A trigger on Button.IsMouseOver
makes the Button
orange, and a trigger on Button.IsPressed
shrinks the button with a ScaleTransform
to give it a “pushed in” look. Figure 14.8 shows the result.
Notice that the larger circle in the template’s visual tree is given the name outerCircle
. This is done so it can be referenced by a trigger. The first trigger uses Setter
’s TargetName
property (which makes sense only inside a template) to make its setting of Fill
to Orange
apply to only the outerCircle
element. Omitting the TargetName
would cause an error in this case because the trigger would apply to the entire Button
, which doesn’t have a Fill
property. The capability to target subelements of a template with triggers is essential for sophisticated templates.
Analogous to Setter
’s TargetName
property, Trigger
(as well as EventTrigger
and Condition
) has a SourceName
property that enables you to react to a change on a specific subelement of a template rather than the entire template. For example, you could have triggers for IsMouseOver
on individual subelements to get a richly customized hover effect.
The second trigger doesn’t need to target a subelement, however. The ScaleTransform
(applied as a RenderTransform
) applies to the entire Button
, as does the setting of RenderTransformOrigin
to center the scaling. It’s hard to convey in Figure 14.8, but a slight centered shrinkage (10% in this case) is a very effective visual effect for a Button
press.
As with Style
, ControlTemplate
has a TargetType
property that can restrict where the template can be applied. It also enables you to remove the class name qualifications on any property references inside a template (such as the values of Trigger.Property
and Setter.Property
). Therefore, the template from Listing 14.6 could be rewritten as follows:
Note that the Setter
s in this example already had unqualified Property
values in previous listings. That’s because the properties are either qualified by the use of TargetName
or are common to all Control
s. (Without an explicit TargetType
, the target type is implicitly Control
.)
Unlike with a Style
, the use of TargetType
does not enable you to remove the template’s x:Key
(when used in a dictionary). There is no such thing as a default control template; you have to set the template inside a typed Style
to get such behavior.
There’s a bit of a problem with the templates we’ve created so far. Any Button
s they’re applied to look exactly the same, no matter what the values of its properties are. For example, in the last two listings, the Button
has "OK"
as content, but it never gets displayed. If you’re creating a control template that’s meant to be broadly reusable, you need to do some work to respect various properties of the target Control
.
ContentControl
’s Content
PropertyThe key to inserting property values from the target element inside a control template is data binding. Fortunately, a class called TemplateBindingExtension
makes this easy.
TemplateBindingExtension
is a markup extension that is similar to Binding
, but simpler, more lightweight, and customized for templates. It’s often referred to as simply TemplateBinding
because of the tendency to omit the Extension
suffix when used in XAML.
The data source for TemplateBinding
is always the target element, and the path is any of its dependency properties, selected by setting TemplateBinding
’s Property
property. Therefore, you could add to the control template in Listing 14.6 a TextBlock
that contains the target Button
’s Content
, as follows:
Or, because TemplateBinding
has a constructor that accepts a dependency property, you could simply write this:
If TargetType
is used to restrict the template’s use for Button
s (or other ContentControl
s), you could simplify this even further, like so:
Of course, a Button
can contain nontext Content
, so using a TextBlock
to display it creates an artificial limitation. To ensure that all types of Content
get displayed properly in the template, you can use a generic ContentControl
instead of a TextBlock
. Listing 14.7 does just that. The ContentControl
is given a Margin
and wrapped in a Viewbox
so it’s displayed at a reasonable size relative to the rest of the Button
.
Figure 14.9 shows what two Button
s look like with this new control template applied. One Button
has simple "OK"
text content, and the other has an Image
. In both cases, the content is reflected in the new visuals as expected.
Rather than use a ContentControl
inside a control template, you should use the lighter-weight ContentPresenter
element. ContentPresenter
displays content just like ContentControl
, but it was designed specifically for use in control templates. ContentPresenter
is a primitive building block, whereas ContentControl
is a full-blown control with its own control template (that contains a ContentPresenter
)!
In Listing 14.7, you can replace this:
with this:
ContentPresenter
even has a built-in shortcut; if you omit setting its Content
to {TemplateBinding Content}
, it implicitly assumes that’s what you want. So, you can replace the preceding line of code with the following:
This works only when the control template is given an explicit TargetType
of ContentControl
or a ContentControl
-derived class (such as Button
).
The remaining templates in this chapter use ContentPresenter
instead of ContentControl
, as that’s what real-world templates use.
TemplateBinding
works only inside a template’s visual tree and doesn’t work with properties on Freezable
s!TemplateBinding
doesn’t work outside a template or outside its VisualTree
property, so you can’t even use TemplateBinding
inside a template’s trigger. Furthermore, TemplateBinding
doesn’t work when applied to a Freezable
(for mostly artificial reasons). For example, attempting to bind the Color
property of any explicit Brush
fails.
However, TemplateBinding
is just a less-powerful but convenient shortcut for using a regular Binding
. You can get the same effect by using a regular Binding
with a RelativeSource
equal to {RelativeSource TemplatedParent}
and a Path
equal to the dependency property whose value you want to retrieve. Such a Binding
works in the cases mentioned where TemplateBinding
does not.
No matter what type of control you’re creating a control template for, there are undoubtedly other properties on the target control that should be honored if you want the template to be reusable: Height
and Width
, perhaps Background
, Padding
, and so on. Some properties (such as Foreground
, FontSize
, FontWeight
, and so on) might automatically inherit their desired values thanks to property value inheritance in the visual tree, but other properties need explicit attention.
Listing 14.8 is an update to Listing 14.7 that respects the Background
, Padding
, and Content
properties of the target Button
. It also implicitly respects the size of the target element by removing the explicit Height
and Width
settings and letting the layout system do its job. Listing 14.8 uses a ContentPresenter
rather than a ContentControl
, although both produce the same result.
The target Button
’s Padding
is now used as the ContentPresenter
’s Margin
. It’s common to use the element’s Padding
in a template as the Margin
of an inner element. After all, that’s basically the definition of Padding
!
In addition, a few nonintuitive changes have been made to the template’s visual tree to accommodate an externally specified size and Background
. We could have simply used {TemplateBinding Background}
as the Fill
for outerCircle
, giving each Button
the flexibility to specify a solid color, a gradient, and so on. But perhaps the “red glow” at the bottom is a characteristic that we’d like to keep consistent wherever the template is used. In other words, we want to replace only the blue part of the gradient with the externally specified Background
. However, GradientStop.Color
can’t be directly set to {TemplateBinding Background}
because Color
is of type Color
, whereas Background
is of type Brush
(and because GradientStop
derives from Freezable
)! Therefore, the listing uses a normal Binding
instead, which supports referencing the Color
subproperty. (Note that this Binding
works only when Background
is set to a SolidColorBrush
because other Brush
es don’t have a Color
property.)
Both Ellipse
s (or the parent Grid
) could have been given an explicit Height
and Width
matching those of the target Button
by binding to its ActualHeight
and ActualWidth
properties. Instead, these values are omitted altogether because the root element is implicitly given the templated parent’s size anyway! This means that an individual target Button
has the power to make itself look like an ellipse by specifying different values for Width
and Height
. If we wanted to preserve the perfect circular look, we could wrap the entire visual tree in a Viewbox
.
The final trick used by Listing 14.8 is the ScaleTransform
on the inner circle to make it 80% of the size of the outer circle. In previous listings, this transform is unnecessary because both the outer and inner circles have a hard-coded size. But with a dynamic size, ScaleTransform
enables us to effectively perform a little math on the size. (If we wanted a fixed-size difference between the circles, a simple Margin
would do the trick.)
Figure 14.10 shows the rendered result for the following Button
s that make use of this new control template:
Each Button
in Figure 14.10 has values for Background
, Padding
, and Content
that are explicitly used by the control template. Their values for Height
and Width
are implicitly respected by the template, and their FontSize
setting is implicitly picked up by the template’s ContentPresenter
. The size of the font isn’t directly reflected in the rendered output because the template wraps the ContentPresenter
inside a Viewbox
to keep it within the bounds of the outer circle. The Margin
specified on each Button
is not used by the template, but it still affects the StackPanel
layout as usual, giving a little bit of space between each Button
.
Sometimes, you might want to parameterize some aspect of a control template, despite there being no corresponding property on the target control. For example, the template in Listing 14.8 has a hard-coded orange Brush
representing the hover state. What can you do to allow individual Button
s to customize this Brush
? There’s no corresponding property already on Button
to be set!
One option is to define a custom control, using the techniques described in Chapter 20, “User Controls and Custom Controls.” It wouldn’t be too much work to write a new class that derives from Button
and adds a single HoverBrush
property. But that’s a bit heavyweight for such a simple task. Another option would be to define several control templates that each uses a different hover Brush
. But that would be reasonable only if the set of desired Brush
es were small and known. Yet another option would be to define an appropriate attached property somewhere, perhaps on a utility class that already exists.
Instead, what most people resort to is a devious little hack known as hijacking a dependency property. This involves looking at the target control for any dependency properties of the desired type to see if you can leverage them in an unintended way. For example, all Control
s have three properties of type Brush
: Background
, Foreground
, and BorderBrush
. Because Background
and Foreground
already play important roles in Listing 14.8, neither one would look very good as a hover Brush
. (There would also be no way to set the hover Brush
independently of the other two.) But BorderBrush
is a different story. It’s completely unused by the template in Listing 14.8, so why not use that?
There really is no reason not to use it, other than the fact that it makes the usage of the template confusing and less readable. Nevertheless, here’s how you could update the IsMouseOver
trigger from Listing 14.8 to hijack BorderBrush
:
A Binding
must be used in this case rather than a TemplateBinding
because the Trigger
is outside the visual tree.
If the target control doesn’t have an appropriate property, you might even be able to hijack an attached property from a totally unrelated element! When choosing a property, be sure to pay attention to its metadata, such as its default value and what gets triggered when its value changes (such as invalidating layout).
If this hack leaves a bad taste in your mouth, then by all means use an alternative approach. This hack is definitely not recommended by the WPF team! But it’s a useful trick to know about if you’re looking for a quick fix.
When creating a control template for Button
s, visually reacting to hover and pressed states with corresponding triggers is a nice touch, but it’s purely optional. Imagine using the template from Listing 14.8 on a CheckBox
or ToggleButton
, however. (This can be done simply by changing the TargetType
.) Because the template doesn’t show different visuals for the Checked
versus Unchecked
versus Indeterminate
states, it’s a pretty lousy template for these controls!
In fact, the template in Listing 14.8 is still incomplete, even for a Button
! The fact that it doesn’t show any different visuals when IsEnabled
is false
or IsDefaulted
is true
makes it a pretty lousy template!
Therefore, you should consider all the visual states a control should expose when designing a control template for it. This might take the form of triggers on the appropriate properties or events, or it could just be a matter of binding them appropriately.
For example, to be useful, a control template for ProgressBar
must show the current value. Listing 14.9 contains a control template (defined as an application-level resource) for ProgressBar
that makes it look like a pie chart. The most important aspect of the template—filling up the pie according to the current Value
—is accomplished by binding to the templated parent and using value converters to do the necessary trigonometry. In addition to this, triggers on IsEnabled
and IsIndeterminate
alter the visuals for these states. Figures 14.11 and 14.12 show the results for ProgressBar
s such as the following:
The foregroundBrush
resource is defined as a simple green gradient:
The root of the visual tree is a Viewbox
, so the 20×20 single-cell Grid
can scale appropriately. The background circle (which has a radius of 10 prior to scaling) is given the templated parent’s Background
, BorderBrush
, and BorderThickness
. The “pie” is a Path
(an element covered in the next chapter) that is given the templated parent’s Foreground
and relies on two MultiBinding
s with value converters defined in Listing 14.10 to get the right shape. MultiBinding
is used rather than a simple TemplateBinding
or Binding
so the pie gets updated when any of ProgressBar
’s three relevant properties change: Value
, Minimum
, and Maximum
. The two triggers create the results in Figure 14.12 by filling an element with a hard-coded Brush
(and in the case of IsIndeterminate
, hiding the pie). A more appropriate effect for IsIndeterminate
is probably an animation that spins the pie around, but at least there’s some visual distinction as is. Note that not all of ProgressBar
’s properties are honored by this template. For example, Orientation
is unused, but there’s not a great way to honor it, considering the visual representation.
Notice that Listing 14.9 defines value converters in ControlTemplate
’s Resources
collection. Like Style
, all FrameworkTemplate
s have their own Resources
collection. This collection can be used to keep templates self-contained.
The first value converter is pretty simple. ArcSegment
’s IsLargeArc
property (from Listing 14.9) must be true
when the pie is more than half full and false
otherwise. Therefore, ValueMinMaxToIsLargeArcConverter
does this simple calculation based on the three values from the target ProgressBar
and returns the appropriate Boolean value.
The second value converter is much more complicated. Its job is to return the proper Point
along the circle’s circumference, according to the current values. To do this, it converts the ProgressBar
’s Value
to an angle (in degrees), makes some adjustments, and then converts it to radians. With this angle, a little trigonometry is used to get the point, based on the fixed radius of 10 and the center point of (10,10).
For a control template designer, knowing all the visual states that need to be respected can be difficult. Each control has a large number of properties, and it might not always be clear which ones are visually important or how to manage all the possible states with triggers. Fortunately, WPF 4 makes this task easier with the inclusion of the Visual State Manager (VSM), a feature that first appeared in Silverlight.
The VSM support includes a collection of types and members that make it easy for control authors to formally specify parts and states for their controls, taking the guesswork out of writing control templates that support them all. Importantly, it enables design tools to provide a decent experience for creating complex templates. Expression Blend takes great advantage of such parts and states.
The “parts” portion of the parts and states model has actually been in WPF from its first release. The idea is that controls can look for specially named elements in the visual tree of the template being applied to them, so that they can apply some logic to those visual pieces. Consider these examples:
• If a ProgressBar
control template has elements named PART_Indicator
and PART_Track
, the control ensures that the Width
(or Height
, based on ProgressBar
’s Orientation
) of PART_Indicator
remains the correct percentage of the Width
(or Height
) of PART_Track
, based on ProgressBar
’s Value
, Minimum
, and Maximum
properties. For the pie chart template from Listing 14.9, this behavior is clearly undesirable. But for a template that more closely matches the standard ProgressBar
look, taking advantage of this support greatly simplifies it (and removes the need for procedural code to do the math).
• If a ComboBox
control template has a Popup
named PART_Popup
, ComboBox
’s DropDownClosed
event is automatically raised when the Popup
is closed. If it has a TextBox
named PART_EditableTextBox
, it integrates automatically with ComboBox
’s ability to update the selection as the user types.
• Controls such as TextBox
and PasswordBox
have most of their functionality tied to an element in the control template called PART_ContentHost
. If you don’t have an element with this name in your control template, you’ll have to reimplement the entire editable surface!
In some cases, the named part can be any FrameworkElement
, but in other cases the type of the named part must be something more specific in order to be respected. Table 14.1 reveals all the named parts leveraged by WPF’s built-in controls. Derived classes that automatically inherit the named part logic are not listed, such as TextBox
and PasswordBox
, which get their PART_ContentHost
logic from TextBoxBase
.
Therefore, claims of WPF’s controls being “lookless” and having an implementation that’s completely independent from their visuals (such as my own claim earlier in this chapter) aren’t entirely true! However, these “secret handshakes” with magically named parts are optional. This is important, as it means you still have the flexibility to radically change a control’s visuals, such as with the pie chart template for ProgressBar
.
To give design tools the ability to discover every named part available for use, controls document them with a TemplatePartAttribute
on their class—one for each named part—that reveals the name and expected type of the part. WPF also has a convention of using parts named PART_
XXX
(a convention broken by one of CalendarItem
’s parts, seen in Table 14.1), although Silverlight does not have this convention.
On the one hand, named parts are an implementation detail that you don’t need to know about. On the other hand, you can sometimes create control templates with much less effort by taking advantage of this built-in logic!
The “states” portion of the parts and states model is the functionality that is new to WPF 4. As with control parts, controls can have internal logic to transition to named states that they define (by calling a static VisualStateManager.GoToState
method). Control templates can then use a few new elements to organize visual settings specific to each state rather than use triggers. Writing control templates that take advantage of states is optional, but as of WPF 4, this is the recommended approach. Such templates are not only better supported by tools such as Expression Blend, they are more likely to work for Silverlight controls as well.
The states defined by each control are grouped into mutually exclusive state groups. For example, Button
has four states in a group called CommonStates
—Normal
, MouseOver
, Pressed
, and Disabled
—and two states in a group called FocusStates
—Unfocused
and Focused
. At any time, Button
is in one state from every group, so it is Normal
and Unfocused
by default. This grouping mechanism exists to avoid a long list of states meant to cover every combination of independent properties (such as NormalUnfocused
, NormalFocused
, MouseOverUnfocused
, MouseOverFocused
, and so on).
Table 14.2 lists all the groups and states supported by WPF’s built-in controls. Notice the explosion of states for DataGridRow
and DataGridRowHeader
; these states really should have been organized into three separate groups. (Someone didn’t get the memo.) States inherited from base classes are not listed; you can find Button
’s states under ButtonBase
. Similarly, DataGridColumnHeader
lists only its SortStates
group, even though it also inherits the two groups from ButtonBase
. Some controls choose not to respect states defined by its base classes. For example, ProgressBar
supports two CommonStates
—Determinate
and Indeterminate
—but overrides functionality in the RangeBase
base class such that its three CommonStates
and two FocusStates
never get invoked.
To write a control template that takes advantage of states, you set the VisualStateManager.VisualStateGroups
attached property on the root element in the template’s visual tree to a collection of VisualStateGroup
objects, each of which has a collection of VisualState
children.
Listing 14.11 updates the pie chart control template from Listing 14.9 to take advantage of ProgressBar
’s visual states. Because ProgressBar
only supports states for Determinate
versus Indeterminate
and does not have states for Normal
versus Disabled
, this template still needs one trigger for the case of IsEnabled
being false
. The previous trigger that acted on IsIndeterminate
being true
has been replaced by acting on the Indeterminate
visual state, however.
The content of each VisualState
is a Storyboard
, a type covered in depth in Chapter 17. It enables you to change certain property values either instantly (as done in Listing 14.11) or with a smooth transition. Changing the arbitrary background Ellipse Fill
to a specific LinearGradientBrush
isn’t feasible with a Storyboard
, so this listing changes the visual tree to contain two Ellipse
s—backgroundNormal
that is visible by default and backgroundIndeterminate
that is not (due to its Opacity
being set to 0
). The transition to the Indeterminate
visual state therefore instantly “animates” the Opacity
of backgroundNormal
to 0
and the Opacity
of backgroundIndeterminate
to 1
. To make this happen more gradually, you can increase the Duration
value on the two DoubleAnimation
s. Chapter 17 reveals all the flexibility that the use of these animation objects can give you. It also revisits the Button
control template created in this chapter (Listing 14.8), to show how it could be rewritten to leverage the VSM.
VisualStateGroup
has a Transitions
property that can be set to one or more VisualTransition
s that can do automatic animated transitions between any combinations of states. See Chapter 17 for more information.
As with their parts, controls should document their state groups and states by using the TemplateVisualStateAttribute
. However, the built-in WPF controls do not currently do this.
Style
sAlthough all the control templates thus far are applied directly to elements for simplicity, it’s more common to set a Control
’s Template
property inside a Style
and then apply that style to the desired elements:
Besides the convenience of combining a template with arbitrary property settings, there are important advantages to doing this:
• It gives you the effect of default templates. For example, when a typed Style
gets applied to elements by default and that Style
contains a custom control template, the control template gets applied without any explicit markings on those elements!
• It enables you to provide default yet overridable property values that control the look of the template. In other words, it enables you to respect the templated parent’s properties but still provide your own default values.
The final point is very relevant for the templates examined so far. For the ProgressBar
pie chart template, I wanted the pie to be filled with a green gradient by default. If such a Brush
is hard-coded inside the template, consumers would have no way to customize the fill. On the other hand, by binding to the templated parent’s Foreground
(which is what Listing 14.9 does), the onus is on every ProgressBar
to set its Foreground
appropriately. ProgressBar
’s default Foreground
is a solid green color, not the desired gradient!
By placing the green gradient in a Style
’s Setter
, however, you get the desired default look while still allowing individual ProgressBar
s to override the fill by explicitly setting their Foreground
property locally. And the {TemplateBinding Foreground}
expression inside the template doesn’t need to change. The Style
could look as follows:
Consumers of the Style
could do the following:
Of course, the same approach can be used for other properties, such as Width
and Height
.
Skinning refers to the act of changing an application’s appearance (or skin) on the fly, typically by third parties. WPF doesn’t have a distinct concept called a skin, nor does it have a formal notion of skinning, but it doesn’t need one. You can easily write an application or a component that supports dynamic skinning by using WPF’s dynamic resource mechanism (described in Chapter 12, “Resources”) combined with Style
s and/or templates.
To support skinning in an application, one of the first things you need to do is decide on a data format. Whereas it might make sense to invent a format for Win32 or Windows Forms applications, XAML is a no-brainer data format for skins in WPF applications if you are okay with loading arbitrary code into your process. (Loading someone’s XAML is like loading someone’s add-in; it has the power to invoke unrelated code and may therefore do something malicious. See the FAQ at the end of this section for more information.)
But what should such XAML files look like?
Often, the initial instinct is to load an entire Window
or Page
dynamically from a loose XAML file and hook it up to the appropriate logic (using the technique shown at the end of Chapter 2, “XAML Demystified”). Loading your entire user interface on the fly gives complete flexibility, but, in most cases, it’s probably too much flexibility. Authors of such XAML files would need a lot of discipline to include all the right elements with all the right names and all the right event handlers, and so on. (Either that or the code to connect the user interface to the application logic needs to be extremely forgiving.) Visual Studio 2010 follows this approach with its XAML-based Start Page. By loading an arbitrary Page
, authors can plug in something completely different. If all they want to do is reskin what’s there, they need to start by copying the existing Page
and tweak it from there.
For environments in which you don’t want to encourage complete user interface replacement, the best approach is to make ResourceDictionary
the root of a skin representation. ResourceDictionary
makes a great extensibility point in general because of the ease with which it can be swapped in and out or merged with others. When defining a skin, it makes sense for the ResourceDictionary
to contain Style
s and/or templates.
To demonstrate skinning, the following Window
is a hypothetical progress dialog, shown in Figure 14.13:
Notice that most of the Window
’s elements are given explicit Style
s. This is not a requirement for skinning, but it’s often a nice touch for giving skin authors more control over the visual experience. For example, suppose you want to give a specific look to a Cancel Button
that’s different from the look you want for all other Button
s. Marking all Cancel Button
s with an explicit CancelButtonStyle
allows you to do just that. Referencing the explicit Style
s as dynamic resources is critical to enable them to be updated at arbitrary times.
When giving an element a Style
that you expect to be reskinned dynamically, don’t forget to reference it as a dynamic resource!
So that it will have the look shown in Figure 14.13, the preceding Window
is paired with the following App.xaml
file that provides a default definition of each Style
resource:
Notice that CancelButtonStyle
is empty, so applying it to a Button
has no effect. This is perfectly valid because the expectation is that a skin might replace this Style
with something more meaningful.
With this in place, a skin file could simply look like the following:
Then all the host application needs to do is dynamically load the skin XAML file and assign it as the new Application.Resources
dictionary. The following code does this for a .xaml
file sitting in the current directory:
You could alternatively use code like the following to retrieve a skin file from the Internet at an arbitrary URL:
Because assigning a dictionary to Application.Current.Resources
code wipes out the current dictionary, you should also store the default ResourceDictionary
if you want to restore it later!
The progress dialog sample (whose full source code is included with the rest of the book’s code at http://informit.com/title/9780672331190) switches the skin when you click the Cancel Button
for demonstration purposes, but for a real application, this action would likely be taken when a user initiates it from some skin-choosing user interface.
In this book’s source code, you’ll find two alternative skins for the progress dialog in Figure 14.13. Figure 14.14 shows the dialog with these two skins.
Notice that the “electric” skin restyles the ProgressBar
(using the pie chart template from the previous section) even though the application didn’t give it an explicit Style
. It does this by making it a typed Style
that applies to all ProgressBar
s. Fortunately, any additions of, removals of, or changes to typed styles in a ResourceDictionary
are automatically reflected the same way as explicit dynamic resources. The skin’s CancelButtonStyle
uses a TranslateTransform
to reposition it next to the ProgressBar
rather than below it. It also does something quite unique for the Label
’s Style
: It uses a template to send the Label
’s content through a “jive translator” web service. (This, of course, works only if the Label
contains text.)
The “light and fluffy” skin has its own set of fairly radical changes. Listing 14.12 shows the complete source for this skin.
The customized DialogStyle
and HeadingStyle
are pretty straightforward (although the latter uses a slick drop shadow effect introduced in the next chapter). But this skin, to keep a minimalistic user interface, uses CancelButtonStyle
to completely hide the Cancel Button
! In this case, doing so is appropriate (assuming that closing the Window
behaves the usual way). In other cases, users might not appreciate a skin that hides pieces of the user interface!
The typed Style
for ProgressBar
also performs an interesting trick for the purpose of simplifying the user interface. It defines a custom template to wrap the ProgressBar
inside an Expander
(that’s collapsed by default)! The wrapped ProgressBar
has several TemplateBinding
s to keep its display in sync with the templated parent. Notice that this inner ProgressBar
is given a null Style
. This is necessary to avoid a nasty recursion problem. Without the explicit Style
, the inner ProgressBar
gets the default typed Style
that it’s a part of, making it an Expander
inside an Expander
inside an Expander
, and so on.
Whereas skins are application specific, themes generally refer to visual characteristics of the operating system that are reflected in user interface elements of all programs. For example, changing your Windows theme to Windows Classic gives buttons and scrollbars a flat and rectangular look. On Windows XP, switching the default theme’s color scheme between Blue, Olive Green, and Silver affects the color and sheen of standard controls. To maintain consistency with the user’s chosen Windows theme, the built-in WPF controls have a separate control template for each theme, as you saw with Button
in Chapter 9, “Content Controls.”
Consistency with the operating system theme is important for the default control templates. But when somebody creates custom control templates, they typically do so to avoid consistency with the rest of the operating system! Nevertheless, it can still be a nice touch to incorporate elements of the user’s operating system theme to prevent the customized controls from sticking out like a sore thumb. It’s also important to understand how theming works if you create your own custom controls that should blend in with the operating system theme by default.
This section examines how easy it is to create Style
s and templates (and, therefore, skins) that adapt to the current theme. There are basically two ways to do this. The first is simple but not as powerful, and the second is a bit more work but completely flexible.
The properties exposed by the SystemColors
, SystemFonts
, and SystemParameters
classes automatically get updated when the Windows theme changes. Therefore, incorporating these into your Style
s and templates is an easy way to blend them in with the user’s theme.
The following updated ProgressBar
pie chart Style
makes use of the SystemColors
class to control the colors in its default fill (using the technique explained in Chapter 12):
Figure 14.15 shows how the appearance of this Style
subtly changes when the user switches Windows themes.
Many of the built-in WPF controls differ from theme to theme in richer ways than just colors, fonts, and simple measurements. For example, they’re generally shinier in the Windows 7 Aero theme and dull in Windows Classic. This is accomplished by having a separate control template for each theme.
The ability to define your own styles and templates that differ in interesting ways based on the current theme can be quite useful. For example, it could be argued that the Windows Classic version of ProgressBar
in Figure 14.15 is too pretty! Someone who uses the Windows Classic theme probably isn’t going to appreciate fancy gradients and other effects!
If you want to create your own per-theme styles and templates, you could programmatically load and swap them whenever the theme changes (using the techniques discussed in the “Skins” section). WPF doesn’t expose a theme-changing event, however, so this would involve intercepting the Win32 WM_THEMECHANGE
message (the same way WM_DWMCOMPOSITIONCHANGED
is intercepted in Chapter 8, “Exploiting Windows 7”). Fortunately, WPF does expose a theming mechanism built on top of the low-level Win32 APIs, enabling you to provide per-theme resources with almost no procedural code.
The first step is to organize your theme-specific resources into distinct resource dictionary XAML files (one per theme) that are compiled into your assembly. You can then designate each resource dictionary as a theme dictionary by placing it in a themes
subfolder (which must be in the root of your project!) and naming it ThemeName
.
ThemeColor
.xaml
(case-insensitive). A theme dictionary can be loaded and applied automatically by WPF when your application launches and whenever the theme changes. Style
s inside a theme dictionary are called theme styles.
The following are themes that Microsoft has created, along with their corresponding valid theme dictionary URIs:
• The Aero theme (Windows Vista and Windows 7): themesAero.NormalColor.xaml
• The default Windows XP theme: themesLuna.NormalColor.xaml
• The olive green Windows XP theme: themesLuna.Homestead.xaml
• The silver Windows XP theme: themesLuna.Metallic.xaml
• The Windows XP Media Center Edition 2005 and Windows XP Tablet PC Edition 2005 theme: themesRoyale.NormalColor.xaml
• The Windows Classic theme: themesClassic.xaml
• The Zune Windows XP theme: themesune.NormalColor.xaml
Note that Windows Classic is a bit special, as it doesn’t have the ThemeColor
part of the URI.
Be sure to provide a generic dictionary whenever you create theme dictionaries. This enables you to provide a consistent experience when encountering an unexpected theme.
Furthermore, you can specify a fallback resource dictionary that gets used if you don’t have a dictionary corresponding to the current theme and color. This fallback dictionary, often called the generic dictionary, must be named themesGeneric.xaml
.
With one or more theme dictionaries and/or a generic dictionary in place, you must now opt in to the automatic theming mechanism with an assembly-level ThemeInfoAttribute
. This attribute’s constructor takes two parameters of type ResourceDictionaryLocation
. The first one specifies where WPF should find the theme dictionaries, and the second one specifies where WPF should find the generic dictionary. Each one can independently be set to the following values:
• None
—Don’t look for a resource dictionary. This is the default value.
• SourceAssembly
—Look for them inside the current assembly.
• ExternalAssembly
—Look for them inside a different assembly, which must be named AssemblyName
.
ThemeName
.dll
(where AssemblyName
matches the current assembly’s name). WPF uses this scheme for its built-in theme dictionaries, found in PresentationFramework.Aero.dll
, PresentationFramework.Luna.dll
, and so on. This is a nice way to avoid having extra copies of resources loaded in memory at all times.
Therefore, a typical use of ThemeInfoAttribute
looks like the following:
There’s one final catch to the theming support: It’s designed to provide the default styles for elements. As ThemeInfoAttribute
indicates, theme styles must exist in the same assembly defining the target element or a specific companion assembly. Unlike with application-level (or lower) resource dictionaries, you can’t define a typed style for externally defined elements such as Button
or ProgressBar
in a theme dictionary or generic dictionary in your own application and have it override the default style—unless you use an additional mechanism involving ThemeDictionaryExtension
.
ThemeDictionaryExtension
is a markup extension that enables you to override the theme styles for any elements. It can reference any assembly containing a set of theme dictionaries, even the current application. You can apply ThemeDictionaryExtension
as the Source
for a ResourceDictionary
to affect everything under its scope. Here’s an example:
Imagine that you want to make the pie chart style for ProgressBar
vary, based on the Windows theme. If the MyApplication
assembly contains per-theme styles with a TargetType
of {x:Type ProgressBar}
, all ProgressBar
s in this application get the customized per-theme style by default, thanks to the use of ThemeDictionaryExtension
.
Another approach for attaching per-theme styles to existing elements is to define a custom subclass. Creating custom controls is the subject of Chapter 20, but creating a custom control (or another element) solely for the purpose of giving it a theme style is pretty simple. For the per-theme pie chart style example, you could create a custom control called ProgressPie
, as follows:
Because ProgressPie
derives from ProgressBar
, it automatically has all the necessary functionality. But having a unique type gives you the ability to support a new theme style distinct from ProgressBar
’s theme style. The only magic incantation is the single line of code in ProgressPie
’s static constructor that sets the DefaultStyleKey
dependency property. DefaultStyleKey
is a protected dependency property on FrameworkElement
and FrameworkContentElement
that determines the key to use for its default style. (The terms default style and theme style are often used interchangeably.)
WPF’s built-in elements set this property to their own type, so their corresponding theme dictionaries use typed styles. If the preceding code didn’t set a DefaultStyleKey
, ProgressPie
would inherit the value from ProgressBar
, which is typeof(ProgressBar)
. Therefore, ProgressPie
makes typeof(ProgressPie)
its DefaultStyleKey
.
This book’s source code contains a Visual Studio project that contains the preceding definition of ProgressPie
, the preceding usage of ThemeInfoAttribute
, and a handful of theme dictionaries that get compiled into the application. Each theme dictionary is a standalone XAML file with the following structure:
Figure 14.16 displays a theme-styled ProgressPie
under two different themes. Although you can dig into how each Style
was created, the point is that theme styles give you the flexibility to completely change an element’s visuals when the theme changes. Unlike the visuals in Figure 14.15, I think Figure 14.16 succeeds in making the Windows 7 ProgressPie
extra sexy and the Windows Classic ProgressPie
extra boring. Kidding aside, making theme styles too different from each other will probably confuse your users more than help them.
The combination of styles, templates, skins, and themes is very powerful and often confusing to someone learning about WPF. Adding to the confusion is the fact that Style
s can (and often do) contain templates, elements in templates all have Style
s (whether marked explicitly or inherited implicitly), and theme styles are managed separately from normal Style
s (so an element like Button
’s Style
property is null
by default, even though it clearly has a theme style applied).
These mechanisms are so powerful, in fact, that often you can restyle an existing control as an alternative to writing your own custom control. This is great news, as restyling an existing control is significantly easier than writing a custom control, and it can perhaps be done entirely by a graphic designer rather than a programmer. If you do find that you need to write a custom control (the topic of Chapter 20), the lessons learned here about creating robust templates and adapting to themes are still very applicable.
You can play around with alternate skins for many WPF controls by downloading the WPF Themes .zip
file from http://wpf.codeplex.com. These “themes” are what this chapter calls skins; they are just resource dictionaries that define new typed styles for most of WPF’s built-in controls. To use one of them, you can simply reference the resource dictionary as the Resources
collection in your Application
, Window
, or elsewhere:
Unfortunately, at the time of writing, these skins don’t include styles for the new controls in WPF 4 such as DataGrid
, Calendar
, and DatePicker
. Figure 14.17 demonstrates the seven included skins applied to several controls.