Table of contents
This article discusses the basics of how to create a user interface in WPF that can be "skinned" at runtime. We will examine the support that WPF provides for UI skinning, as well as review how a simple demo application puts those features to use.
The term "skin," when applied to user interfaces, refers to a visual style that is consistently applied to all elements in the UI. A "skinnable" UI can be re-styled either at compile-time or runtime. The Windows Presentation Foundation provides great support for UI skinning.
There are various situations in which UI skinning might be important for an application. It can be used to allow the end-user to customize the UI based on his or her personal aesthetic preferences. Another scenario in which skinning might be used is when a company creates one application that is subsequently deployed to various clients. Each client might want their company logo, colors, font, etc. to be displayed by the application. If the UI is designed with skinning in mind, that task is easy to accomplish with minimal effort.
There are three fundamental pieces to this puzzle. This section provides a brief overview of those topics. Refer to the "External links" section at the end of the article for more information about them. If you are already familiar with hierarchical resources, merged resource dictionaries and dynamic resource references, feel free to skip this section.
To implement support for skinning, you must understand the basics of how the WPF resource system works. Many, many classes in WPF have a public property named Resources
, of type ResourceDictionary
. This dictionary contains a list of key-value pairs where the key is any object and the value is a resource that can be any object, as well. Most often, the keys put into a ResourceDictionary
are string
s; sometimes they are Type
objects. All resources are stored in these dictionaries and the resource lookup procedure uses them to find a requested resource.
The resource dictionaries in an application are arranged in a hierarchical fashion. When it comes time to locate a resource such as a Brush
, Style
, DataTemplate
or any other type of object, the platform executes a lookup procedure that navigates up the resource hierarchy searching for a resource with a certain key.
It first checks the resources owned by the element requesting the resource. If the resource cannot be found there, it checks that element's parent to see if it has the requested resource. If the parent element does not have the resource, it continues walking up the element tree asking every ancestor element if it has a resource with the requested key. If the resource still cannot be found, it will eventually ask the Application
object if it has the resource. For our purposes in this article, we can ignore what happens after that.
ResourceDictionary
exposes a property that allows you to merge in resources from other ResourceDictionary
instances, similar to a union in set theory. That property is named MergedDictionaries
and is of type Collection<ResourceDictionary>
. Here is an explanation from the SDK documentation which explains the scoping rules applied to resources in merged dictionaries:
Resources in a merged dictionary occupy a location in the resource lookup scope that is just after the scope of the main resource dictionary they are merged into. Although a resource key must be unique within any individual dictionary, a key can exist multiple times in a set of merged dictionaries. In this case, the resource that is returned will come from the last dictionary found sequentially in the MergedDictionaries collection. If the MergedDictionaries collection was defined in XAML, then the order of the merged dictionaries in the collection is the order of the elements as provided in the markup. If a key is defined in the primary dictionary and also in a dictionary that was merged, then the resource that is returned will come from the primary dictionary. These scoping rules apply equally for both static resource references and dynamic resource references.
Refer to the "External links" section at the bottom of this article for a link to that help page about merged resource dictionaries.
The last fundamental piece of the puzzle is the mechanism by which visual resources are dynamically associated with properties of elements. This is where the DynamicResource
markup extension comes into play. A dynamic resource reference is similar to a data binding in that, when the resource is replaced at runtime, the properties that consume it will be given the new resource.
For example, suppose we have a TextBlock
whose Background
property must be set to whatever Brush
the current skin dictates it should paint itself with. We can establish a dynamic resource reference for the TextBlock
's Background
property. When the skin changes at runtime, along with the brush to be used by the TextBlock
, the dynamic resource reference will automatically update the TextBlock
's Background
to use the new brush. Here is what that looks like in XAML:
<TextBlock Background="{DynamicResource myBrush}" Text="Whatever..." />
Refer to the "External links" section at the end of this article to see how to write that in code.
The resources for each skin should be placed into a separate ResourceDictionary
, each of which belonging in its own XAML file. At runtime we can load a ResourceDictionary
that contains all of the resources for a skin -- hereafter referred to as a "skin dictionary" -- and insert it into the MergedDictionaries
of the Application
's ResourceDictionary
. By placing a skin dictionary into the Application
's resources, all elements in the application will be able to consume the resources it contains.
All of the elements in the UI that must support skinning should reference the skin resources via dynamic resource references. This allows us to change the skin at runtime and have those elements use the new skin resources.
The easiest way to accomplish this is to have an element's Style
property assigned to a dynamic resource reference. By using an element's Style
property, we allow the skin dictionaries to contain Style
s that can set any number of properties on the skinned elements. This is much easier to write and maintain than setting dynamic resource references on every single property that gets its value from a skin dictionary.
The demo application, which can be downloaded at the top of this article, contains one simple Window
that can be skinned in three ways. That is, unless you decide to create more skins. When you first run the application, it uses the default skin, which looks like this:
If you right-click anywhere on the Window
, a ContextMenu
pops open, allowing you to change the skin. Here is what that looks like:
In a real application, this would definitely be a grotesque way of allowing the user to choose a skin, but this is just a demo app! If the user were to click on the agent named David in the ListBox
and then select the green bar in the ContextMenu
, the "Green Skin" would be applied and the UI would look like this:
Note: The fact that the selected agent's last name is Greene has nothing to do with the fact that the UI is now green! :)
The last skin I created is a little weird, but I like it. Here's what the UI looks like when the "Blue Skin" is applied:
As you can probably tell, I'm not a very good visual designer.
Here is the demo project structure, as seen in Visual Studio's Solution Explorer:
ContextMenu
, which allows the user to change the active skin, is declared in the MainWindow
XAML file like so:
<Grid.ContextMenu>
<ContextMenu MenuItem.Click="OnMenuItemClick">
<MenuItem Tag=".\Resources\Skins\BlackSkin.xaml" IsChecked="True">
<MenuItem.Header>
<Rectangle Width="120" Height="40" Fill="Black" />
</MenuItem.Header>
</MenuItem>
<MenuItem Tag=".\Resources\Skins\GreenSkin.xaml">
<MenuItem.Header>
<Rectangle Width="120" Height="40" Fill="Green" />
</MenuItem.Header>
</MenuItem>
<MenuItem Tag=".\Resources\Skins\BlueSkin.xaml">
<MenuItem.Header>
<Rectangle Width="120" Height="40" Fill="Blue" />
</MenuItem.Header>
</MenuItem>
</ContextMenu>
</Grid.ContextMenu>
When the user selects a new skin in the menu, this code executes in MainWindow
's code-behind file:
void OnMenuItemClick(object sender, RoutedEventArgs e)
{
MenuItem item = e.OriginalSource as MenuItem;
Grid mainGrid = this.Content as Grid;
foreach (MenuItem mi in mainGrid.ContextMenu.Items)
mi.IsChecked = mi == item;
this.ApplySkinFromMenuItem(item);
}
void ApplySkinFromMenuItem(MenuItem item)
{
string skinDictPath = item.Tag as string;
Uri skinDictUri = new Uri(skinDictPath, UriKind.Relative);
DemoApp app = Application.Current as DemoApp;
app.ApplySkin(skinDictUri);
}
The call to ApplySkin
on the DemoApp
object results in this method to be executed:
public void ApplySkin(Uri skinDictionaryUri)
{
ResourceDictionary skinDict =
Application.LoadComponent(skinDictionaryUri) as ResourceDictionary;
Collection<ResourceDictionary> mergedDicts =
base.Resources.MergedDictionaries;
if (mergedDicts.Count > 0)
mergedDicts.Clear();
mergedDicts.Add(skinDict);
}
Now we will take a look at an example of how elements in the UI consume the skin resources. The following XAML represents the "Agents" area on the left side of the MainWindow
. It contains a ListBox
full of insurance agent names and a header which reads "Agents."
<UserControl
x:Class="SkinnableApp.AgentSelectorControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Border Style="{DynamicResource styleContentArea}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
-->
<Border Style="{DynamicResource styleContentAreaHeader}">
<StackPanel Orientation="Horizontal">
<Image Margin="4,4,0,4"
Source=".\Resources\Icons\agents.ico" />
<TextBlock FontSize="20" Padding="8" Text="Agents"
VerticalAlignment="Center" />
</StackPanel>
</Border>
-->
<ListBox Background="Transparent" BorderThickness="0"
Grid.Row="1" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{DynamicResource agentListItemTemplate}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" />
</Grid>
</Border>
</UserControl>
Here is what that AgentSelectorControl
looks like when the default skin is applied:
There are three uses of the DynamicResource
markup extension in the AgentSelectorControl
seen above. Each of them refers to a resource that must exist in a skin dictionary. All of the skin dictionaries are available in the demo project, so I won't bother bloating this article with gobs of marginally interesting XAML.
- July 27, 2007 �- Created the article
- August 1, 2007 -- Article edited and moved to the main CodeProject.com article base