Chapter 14. Styles, Templates, Skins, and Themes

IN THIS CHAPTER

Styles

Templates

Skins

Themes

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.

FAQ

image Why does WPF allow people to completely customize the look of standard controls? The inconsistencies from one application to another are going to confuse users!

This “celebrated” feature of WPF also makes many people nervous. Is this power and flexibility going to usher in a new age of beautiful software, or is it going to be abused in ways that annoy and frustrate users (such as the BLINK element in HTML)?

The answer is certainly “both.” I, too, was skeptical about WPF back in 2003, when most demos consisted of bouncing buttons and spinning rainbow-filled list boxes. But it’s good to know that you could create completely insane user interfaces, even if you shouldn’t. WPF’s philosophy is to make an application’s experience limited only by the skill of its designers rather than by the underlying platform. It’s hard to disagree with that stance.

If you can’t hire graphic designers, fortunately the default visual appearance of a WPF application looks consistent with the expectations of Windows users. The same goes for Silverlight, whose controls can easily adapt to different target environments, such as Windows phones. But if you can hire designers, WPF makes it easy for them to have an impact across the entire application (not just on icons or a splash screen).

As for inconsistencies between applications, the same could be said about web applications, which tend to infuse their own branding into the entire user experience much more than traditional Windows applications. Despite the lack of consistency (or even because of lack of consistency), websites with good user experiences can do very well. Also, people try to create non-standard-looking Windows applications anyway. And with lack of platform support, they have to jump through many hoops to get the desired effect, often producing buggy behavior or weird side effects.

Styles

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 Buttons 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 Buttons, as shown in Listing 14.1.

Figure 14.1 Three Buttons whose look has been customized.

image

Listing 14.1 Copy/Paste Galore!

image

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 Setters 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.

Listing 14.2 Consolidating Property Assignments Inside a Style,

image

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 Buttons 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.

Tip

Styles 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,:

image

Sharing Styles

Although 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.

Sharing Among Heterogeneous Elements

Although the Style in Listing 14.2 is shared among three Buttons, 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.

Figure 14.2 Heterogeneous elements given the same Style.

image

Listing 14.3 Sharing a Single Style with Heterogeneous Elements

image

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:

image

Tip

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:

image

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.

Tip

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.

Restricting the Use of Styles

If 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):

image

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 Setters with the type name. So, the previous XAML snippet could be rewritten as follows and have exactly the same meaning:

image

Creating Implicit Styles

Applying 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:

image

In such an application, all Buttons 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.

Warning: 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 ToggleButtons in your application but you don’t want it applied to any CheckBoxes. (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.

Tip

Styles can be applied in multiple places. For example, all FrameworkElements and FrameworkContentElements 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 Styles used internally, such as ButtonStyleKey and TextBoxStyleKey. Although these XXXStyleKey properties are read-only, you can leverage these keys to define your own Styles that override the default ones. Here’s an example:

image

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

Triggers, first introduced in Chapter 3, have a collection of Setters just like Style (and/or collections of TriggerActions). 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.”

Property Triggers

A property trigger (represented by the Trigger class) executes a collection of Setters when a specified property has a specified value. And when the property no longer has this value, the property trigger “undoes” the Setters.

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:

image

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 Buttons 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.

Figure 14.3 A simple property trigger can change Button’s visuals while the mouse is hovering.

image

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:

image

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:

image

Figure 14.4 Declaratively reacting to a validation error.

image

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.

Figure 14.5 A ListBox and TreeView whose item containers are given the same alternating row style.

image

Listing 14.4 A Style That Alternates Item Container Colors, Applied to ListBoxItems and TreeViewItems

image

image

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 ListBoxItems and TreeViewItems, 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

Data triggers are just like property triggers, except that they can be triggered by any .NET property rather than just dependency properties. (The Setters 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):

image

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.

Figure 14.6 The TextBox Style’s data trigger disables it when its text is set to "disabled".

image

Expressing More Complex Logic with Triggers

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).

Logical OR

Because Style.Triggers can contain multiple triggers, you can create more than one with exactly the same Setters to express a logical OR relationship:

image

This means, “if IsMouseOver is true or if IsFocused is true, apply the rotation and black foreground.”

Logical AND

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 Conditions that contain the information you would normally put directly inside a Trigger or DataTrigger. Therefore, you can use MultiTrigger as follows:

image

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.

Tip

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. EventSetters can be added to a Style just like Setters:

image

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.

Templates

Controls have many properties you can use to customize their look: Button has configurable Background and Foreground Brushes (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-UIElements, whose default template is simply a TextBlock containing a string returned by its ToString method. ItemsPanelTemplates 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.

Introducing Control Templates

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.

Figure 14.7 A fancy round Button, created with a custom ControlTemplate.

image

Listing 14.5 A Simple ControlTemplate Applied to a Button

image

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!

Tip

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.

Getting Interactivity with Triggers

As with Styles, Templates 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.

Figure 14.8 The hover and pushed-in effects for the ControlTemplate in Listing 14.6.

image

Listing 14.6 A ControlTemplate Enhanced with Triggers

image

image

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.

Tip

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.

Restricting the Target Type

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:

image

Note that the Setters 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 Controls. (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.

Respecting the Templated Parent’s Properties

There’s a bit of a problem with the templates we’ve created so far. Any Buttons 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.

Respecting ContentControl’s Content Property

The 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:

image

Or, because TemplateBinding has a constructor that accepts a dependency property, you could simply write this:

image

If TargetType is used to restrict the template’s use for Buttons (or other ContentControls), you could simplify this even further, like so:

image

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.

Listing 14.7 An Updated ControlTemplate That Displays the Target Button’s Content

image

image

Figure 14.9 shows what two Buttons 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.

Figure 14.9 Two different Buttons with the control template defined in Listing 14.7.

image

Tip

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:

image

with this:

image

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:

image

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.

Warning: TemplateBinding works only inside a template’s visual tree and doesn’t work with properties on Freezables!

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.

Respecting Other Properties

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.

Listing 14.8 Updates to the ControlTemplate That Make It More Reusable

image

image

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 Brushes don’t have a Color property.)

Both Ellipses (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 Buttons that make use of this new control template:

image

Figure 14.10 Buttons that tweak the look of their custom template from Listing 14.8.

image

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.

Hijacking Existing Properties for New Purposes

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 Buttons 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 Brushes 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 Controls 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:

image

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.

Respecting Visual States with Triggers

When creating a control template for Buttons, 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 ProgressBars such as the following:

image

Figure 14.11 Customized ProgressBar visuals for various stages of progress.

image

Figure 14.12 Customized ProgressBar visuals for disabled and indeterminate states.

image

The foregroundBrush resource is defined as a simple green gradient:

image

Listing 14.9 The Pie Chart Control Template for ProgressBar

image

image

image

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 MultiBindings 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.

Tip

Notice that Listing 14.9 defines value converters in ControlTemplate’s Resources collection. Like Style, all FrameworkTemplates have their own Resources collection. This collection can be used to keep templates self-contained.

Listing 14.10 The Value Converters Used in Listing 14.9

image

image

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).

Respecting Visual States with the Visual State Manager (VSM)

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.

Control Parts

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.

Table 14.1 Named Parts Used by WPF’s Controls

image

image

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!

Control States

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 CommonStatesNormal, MouseOver, Pressed, and Disabled—and two states in a group called FocusStatesUnfocused 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 CommonStatesDeterminate and Indeterminate—but overrides functionality in the RangeBase base class such that its three CommonStates and two FocusStates never get invoked.

Table 14.2 State Groups and States Used by WPF’s Controls

image

image

image

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.

Listing 14.11 An Update to the Pie Chart Control Template from Listing 14.9 That Uses the VSM

image

image

image

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 Ellipses—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 DoubleAnimations. 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.

Tip

VisualStateGroup has a Transitions property that can be set to one or more VisualTransitions 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.

Mixing Templates with Styles

Although 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:

image

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 ProgressBars 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:

image

Consumers of the Style could do the following:

image

Of course, the same approach can be used for other properties, such as Width and Height.

FAQ

image How do I make small tweaks to an existing control template rather than create a brand-new one from scratch?

There is no mechanism for tweaking existing templates (like Style’s BasedOn). Instead, you can easily retrieve a XAML representation for any existing Style or template, modify it, and then apply it as a brand-new Style or template. In fact, even if you want to create a completely different look, the best way to become familiar with how to design robust control templates is to look at the built-in WPF control templates used by their theme styles.

To obtain the “visual source code” in XAML for any control template, you simply use code such as the following (after the control has undergone layout, so the template gets applied):

image

Or, you can retrieve the entire Style for any element by programmatically grabbing the correct resource. The following code grabs the theme style of an element by using a dependency property called DefaultStyleKey (described in the “Themes” section) to identify the Style resource:

image

For other types of Styles, you could call FindResource with the appropriate key, such as typeof(Button) for a typed Button style (if it exists).

In addition, there are many alternative approaches that don’t involve writing code:

• Consult the Windows SDK, which contains XAML files with all the theme styles used by WPF’s controls.

• Use the .NET Reflector tool with its BAML Viewer add-in to view the embedded styles in assemblies such as PresentationFramework.Aero.dll.

• Create the appropriate control in Expression Blend and then choose Edit Template, Edit a Copy... to get a copy of its style pasted into your XAML. (You can also find a XAML file for each theme supported by WPF’s controls installed with Blend under Program Files.)

The last approach is my personal favorite. Blend also includes “simple styles” for the common controls, which are much easier to tweak and understand. These can be an instructive starting point for creating your own custom templates.

Skins

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 Styles 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 Styles and/or templates.

To demonstrate skinning, the following Window is a hypothetical progress dialog, shown in Figure 14.13:

image

Figure 14.13 A dialog box, shown with its default skin.

image

Notice that most of the Window’s elements are given explicit Styles. 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 Buttons. Marking all Cancel Buttons with an explicit CancelButtonStyle allows you to do just that. Referencing the explicit Styles as dynamic resources is critical to enable them to be updated at arbitrary times.

Tip

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:

image

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:

image

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:

image

You could alternatively use code like the following to retrieve a skin file from the Internet at an arbitrary URL:

image

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!

FAQ

image What happens if a skin doesn’t define a named Style expected by the application?

If you take the approach of completely replacing the current Application.Resources dictionary with a new ResourceDictionary, and if the new dictionary is missing Styles, the affected controls will silently revert to their default appearance. This is true of any dynamic resource that gets removed while the application is running. The dynamic resource mechanism does emit a debug trace, however, much like how data binding reports errors. For example, applying a skin missing a Style called CancelButtonStyle causes the following message to be emitted inside a debugger:

System.Windows.ResourceDictionary Warning: 9 : Resource not found;
ResourceKey='CancelButtonStyle'

To avoid this, another approach would be to iterate through the new resource dictionary and individually set each key/value pair in the application’s resource dictionary.

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.

Figure 14.14 Two alternate skins for the dialog.

image

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 ProgressBars. 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.

Listing 14.12 The “Light and Fluffy” Skin

image

image

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 TemplateBindings 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.

FAQ

image How can I prevent a user-contributed skin from acting maliciously?

There is no built-in mechanism to do this. It might be tempting to try to write your own logic to examine a user-supplied ResourceDictionary and remove things that you consider to be malicious, but this is basically a futile task. For example, if you want to prevent a skin from hiding elements, you can pretty easily remove Setters that operate on Visibility. But what about a skin that makes text the same color as the background? Or a skin that gives controls an empty-looking template? There’s more than one way to skin a cat! (Pun intended.)

And making your user interface unusable is the least of your concerns. Imagine a skin that finds a way to send private information displayed by the application back to a web server. There’s an inherent risk whenever arbitrary code (or XAML!) is executed inside a full-trust application. Loading the XAML in a separate process is one workaround but is probably too cumbersome for most scenarios.

If you’re concerned about this issue, you should probably define your own skin data format that is much more limited in expressiveness. But if you provide an easy way for a user to remove a “malicious skin,” then perhaps you don’t need to worry about this in the first place.

Themes

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 Styles 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.

Using System Colors, Fonts, and Parameters

The properties exposed by the SystemColors, SystemFonts, and SystemParameters classes automatically get updated when the Windows theme changes. Therefore, incorporating these into your Styles 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):

image

Figure 14.15 shows how the appearance of this Style subtly changes when the user switches Windows themes.

Figure 14.15 The same control with the same Style, viewed under two different themes.

image

Per-Theme Styles and Templates

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. Styles 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.

Tip

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:

image

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:

image

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 ProgressBars 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:

image

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:

image

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.

Figure 14.16 The same control with its theme style, viewed under two different themes.

image

Summary

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 Styles can (and often do) contain templates, elements in templates all have Styles (whether marked explicitly or inherited implicitly), and theme styles are managed separately from normal Styles (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.

Tip

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:

image

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.

Figure 14.17 Applying skins from the “WPF Themes” download.

image

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset