6.2. Resources and Styles
WPF's styling mechanism depends on the resource system to locate
styles. As you already saw in
Chapter 5, styles are defined in the Resources section of an
element and can be referred to by name, as shown in
Example 6-18.
Example 6-18. Referencing a Style resource
<Window x:Class="ResourcePlay.Window1" Text="ResourcePlay"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">
<Window.Resources>
<Style x:Key="myStyle">
<Setter Property="Button.FontSize" Value="36" />
</Style>
</Window.Resources>
<Grid>
<Button Style="{StaticResource myStyle}">Hello</Button>
</Grid>
</Window>
However, it is also possible to define a style that is applied
automatically to an element without the need for the explicit resource
reference. This is useful if you want the style to be applied to all elements
of a particular type without having to add resource references to every
element. Example 6-19 shows a
version of Example 6-18 modified
to take advantage of this.
Example 6-19. Implicit use of a Style
<Window x:Class="ResourcePlay.Window1" Text="ResourcePlay"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">
<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Button.FontSize" Value="36" />
</Style>
</Window.Resources>
<Grid>
<Button>Hello</Button>
</Grid>
</Window>
Notice that the Button no longer has its Style
property specified. However, the style will still be applied to the button
because of its TargetType. Instead of defining a key, the style now
has a TargetType set with the x:Type markup extension, which
instructs XAML to provide a System.Type object for the named class.
If a FrameworkElement does not have an explicitly
specified Style, it will always look for a Style resource
using its own type as the target type.
 |
If you were to create some non-Style resource,
such as a SolidColorBrush and set its x:Key to be the type of
some UI element, an error would occur if you tried to use that element type.
This is because when you create a Style with a TargetType and do not
specify the x:Key, the x:Key is implicitly set to be the same
as the TargetType. This key is used to locate the style. So, in
general, you should avoid setting the x:Key to a Type object.
|
|
Because elements look for their styles in resources, you can
take advantage of the resource scoping system. You can define a style resource
at a local scope if you just wish to affect a small number of elements, or at a
broader scope such as in Window.Resources, or at application scope.
And styles may even be drawn from the system scope. This relationship between
styling and resources is the key to both skinning and theming.
6.2.1. Skins and Themes
Skinning and theming are both techniques for controlling the
look and feel of a UI. A theme is a system-wide look, such as the Classic
Windows 2000 look, or the Windows XP "Luna" theme. A skin is a look specific to
a particular application, such as the distinctive styles available for media
programs like WinAmp and Windows Media Player.
Both skins and themes
can be implemented in WPF as a set of resources
that apply the required styles to controls. By using the convention that the
resource name is the Type for the control to which it applies, styles
will apply themselves consistently and automatically. These styles will usually
set the Template property in order to manage the appearance of the
control and may also set other properties, such as those for font handling.
(Templates were discussed in
Chapter 5.) The main difference between a skin and a theme is one of
scope: a skin would typically be stored in the application's Resources
property, while a theme lives at the system scope and is not directly
associated with any one application.
 |
In the version of WPF available at the time of
writing, there was no documented way in which to add a new theme. However, an
understanding of how themes work is useful in order to understand how controls
get their default appearance.
|
|
Since a skin's purpose is to control the appearance of a
particular application, it may well provide more than just styles for standard
controls; it might define various other named resources for use in specific
parts of the application. For example, a music- player application might
present a ListBox whose purpose is to present a list of songs. A skin
might well want to provide a particular look for this list without necessarily
affecting all listboxes in the application. So the application would probably
set that ListBox to use a specific named style, enabling the skin to
define a style for just that ListBox. In that particular case, the
provision of such a specific style might be optional, but in other
circumstances, the application might require the skin to provide certain
resources. For example, if the application has a toolbar, the skin might be
required to provide resources defining the graphics for that toolbar.
Also, a theme applies to all applications, so it must provide
templates and styles for all control types. By contrast, a skin is
application-specific, so it doesn't necessarily have to provide a comprehensive
set of styles. If the application doesn't use every single control type, the
skin needs to supply styles only for the controls the application uses.
Examples 6-20 and 6-21
show the XAML and code-behind for an extremely simple skin.
Example 6-20. BlueSkin.xamla very simple skin
<ResourceDictionary x:Class="SimpleSkin.BlueSkin"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="Blue" />
<Setter Property="Foreground" Value="White" />
</Style>
</ResourceDictionary>
Example 6-21. BlueSkin.xaml.cscode-behind for a very
simple skin
using System;
using System.Windows;
namespace SimpleSkin {
public partial class BlueSkin : ResourceDictionary {
public BlueSkin( ) {
InitializeComponent( );
}
}
}
This just sets the foreground and background for a Button.
A more complex skin would target more element types and set more properties.
Most skins include some Template property setters in order to
customize the appearance of controls. But even in this simple example, the
underlying principles remain the same.
Example 6-22 shows a UI, and Example
6-23 shows the corresponding code-behind that allows skins to be
switched. (This example assumes that two skin classes, BlueSkin and GreenSkin,
have been defined using the technique shown in
Example 6-20.)
Example 6-22. Window1.xamlswitching skins
<Window x:Class="SimpleSkin.Window1" Text="SimpleSkin"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005">
<Grid Margin="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<RadioButtonList x:Name="radioSkins">
<TextBlock>Green</TextBlock>
<TextBlock>Blue</TextBlock>
</RadioButtonList>
<Button Grid.Row="1">Hello</Button>
</Grid>
</Window>
Example 6-23. Window1.xaml.csswitching skins
code-behind
using System;
using System.Windows;
using System.Windows.Controls;
namespace SimpleSkin {
public partial class Window1 : Window {
public Window1( ) {
InitializeComponent( );
EnsureSkins( );
radioSkins.SelectionChanged += SkinChanged;
}
static ResourceDictionary greenSkin;
static ResourceDictionary blueSkin;
static bool resourcesLoaded = false;
private static void EnsureSkins( ) {
if (!resourcesLoaded) {
greenSkin = new GreenSkin( );
blueSkin = new BlueSkin( );
resourcesLoaded = true;
}
}
private void SkinChanged(object o, SelectionChangedEventArgs e) {
switch (radioSkins.SelectedIndex) {
case 0:
Application.Current.Resources = greenSkin;
break;
case 1:
Application.Current.Resources = blueSkin;
break;
}
}
}
}
This SimpleSkin class contains some code to ensure that
the skins get created just once. The code that changes skins simply sets the
application resource dictionary to be the one for the selected skins. (The
source for the second skin, GreenSkin, is not shown. It looks almost
identical to Example 6-20,
only using green instead of blue.) The styling and resource systems react
automatically to the change in resources, updating all of the affected controls
when you switch skins, so this is all the code that is required.
Figure 6-5 shows the code in action.
There is one slight snag with switching skins this way. If you
have any resources stored at the application scope other than the skin
resources, they will be lost when switching skins. Currently, the only solution
to this is to make sure each skin contains a copy of any non-skin-specific,
application-scope resources. The best way to do this is to keep
non-skin-specific resources in a separate class and merge them into the skin
resources. The current build of WPF has no support for merging resource
dictionaries automatically. WPF team members have indicated they are
considering easier ways of doing this in a future release, but in the current
preview, you must do this manually, as
Example 6-24 shows.
Example 6-24. Merging resources
ResourceDictionary skinResources = new FooSkinResources( );
ResourceDictionary nonSkinAppResources = new DrawingResources( );
foreach (DictionaryEntry de in nonSkinAppResources) {
skinResources.Add(de.Key, de.Value);
}
You would add code like this to the method in which you load the
resources. In Example 6-23,
you would perform this resource merging in the EnsureSkins method,
merging the non-skin application resources into both the blue and the green
skins.
|