Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating a Skinned User Interface in WPF

0.00/5 (No votes)
27 Jul 2007 5  
Reviews the basics of creating a WPF user interface with various visual styles.

Screenshot - montage.png

Table of contents

Introduction

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.

Background

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.

Three pillars

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.

Hierarchical resources

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 strings; 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.

Merged resource dictionaries

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.

Dynamic resource references

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.

Putting the three pillars to use

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

What the demo application looks like

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:

Screenshot - black_skin.png

If you right-click anywhere on the Window, a ContextMenu pops open, allowing you to change the skin. Here is what that looks like:

Screenshot - skin_selection.png

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:

Screenshot - green_skin.png

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:

Screenshot - blue_skin.png

As you can probably tell, I'm not a very good visual designer.

How the demo application works

Here is the demo project structure, as seen in Visual Studio's Solution Explorer:

Screenshot - project_structure.png

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;

    // Update the checked state of the menu items.

    Grid mainGrid = this.Content as Grid;
    foreach (MenuItem mi in mainGrid.ContextMenu.Items)
        mi.IsChecked = mi == item;

    // Load the selected skin.

    this.ApplySkinFromMenuItem(item);
}

void ApplySkinFromMenuItem(MenuItem item)
{
    // Get a relative path to the ResourceDictionary which

    // contains the selected skin.

    string skinDictPath = item.Tag as string;
    Uri skinDictUri = new Uri(skinDictPath, UriKind.Relative);

    // Tell the Application to load the skin resources.

    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)
{
    // Load the ResourceDictionary into memory.

    ResourceDictionary skinDict = 
        Application.LoadComponent(skinDictionaryUri) as ResourceDictionary;

    Collection<ResourceDictionary> mergedDicts = 
        base.Resources.MergedDictionaries;

    // Remove the existing skin dictionary, if one exists.

    // NOTE: In a real application, this logic might need

    // to be more complex, because there might be dictionaries

    // which should not be removed.

    if (mergedDicts.Count > 0) 
        mergedDicts.Clear();

    // Apply the selected skin so that all elements in the

    // application will honor the new look and feel.

    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>

            <!-- AGENT SELECTOR HEADER -->
            <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>

            <!-- AGENT SELECTION LIST -->
            <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:

Screenshot - agent_selector.png

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.

External links

Revision history

  • July 27, 2007 �- Created the article
  • August 1, 2007 -- Article edited and moved to the main CodeProject.com article base

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here