Skinning in WPF

I like writing XAML.  It gives me a sense of completion when I write a style of template by hand and it just works, I like using blend as well but raw is more fun Smile.

Lets say we have a requirement to change the front-end of a product for different brandings and “products” (that is, the same product with a different ‘skin’).  One of the challenges we have is that we don’t want to write new user controls every time the client screen needs to change (especially for something simple such as switching to a different style for certain builds).  What follows is a way of using the power of WPF resources to skin an application with little work.

we start off with 2 ListBoxes on a window, for the purposes of this article the list boxes are simple with a list of colours, as follows (they are both the same).

<ListBox Grid.Column="0" Margin="5"> 
    <ListBoxItem>Red</ListBoxItem> 
    <ListBoxItem>Yellow</ListBoxItem> 
    <ListBoxItem>Pink</ListBoxItem> 
    <ListBoxItem>Green</ListBoxItem> 
    <ListBoxItem>Orange</ListBoxItem> 
    <ListBoxItem>Purple</ListBoxItem> 
    <ListBoxItem>Blue</ListBoxItem> 
</ListBox> 

Now, this will just display 2 boring list so lets add a nice style in the app.xaml file for the left hand list as follows:

<Style x:Key="RoundedList" TargetType="{x:Type ListBoxItem}">
    <Setter Property="Width" Value="150" />
    <Setter Property="Margin" Value="5,2" />
    <Setter Property="Padding" Value="2" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                <Border x:Name="border" BorderThickness="1" BorderBrush="Silver" Background="AliceBlue" CornerRadius="5">
                    <ContentPresenter x:Name="Content" Margin="0" HorizontalAlignment="Center" TextBlock.Foreground="Black" VerticalAlignment="Stretch" />
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="Selector.IsSelected" Value="True">
                        <Setter TargetName="border" Property="Background" Value="Silver" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Adding the ItemContainerStyle ({StaticResource RoundedList}) to the list box will now give us something like the following:

image

As we can see we now have a nice styled list on the left which will change colour when we click on each item.  To demonstrate the skinning I am going to apply the same style to the right hand list but with a different name (so we now have 2 styles in the app.xaml file – RoundedList and RoundedList_skin).

So, for certain builds of this app we want the list buttons to shrink slightly when we click them but we only want it to do that for certain configurations.  We can add a new WPF resource dictionary to our project (Skin.xaml) and add the new style to it – it is important that the new style retains the name of the one it is replacing as we shall see shortly.

<Style x:Key="RoundedList_skin" TargetType="{x:Type ListBoxItem}">
    <Setter Property="Width" Value="150" />
    <Setter Property="Margin" Value="5,2" />
    <Setter Property="Padding" Value="2" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                <Border x:Name="border" BorderThickness="1" BorderBrush="Silver" Background="AliceBlue" CornerRadius="5">
                    <ContentPresenter x:Name="Content" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Stretch" />
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="Selector.IsSelected" Value="True">
                        <Setter Property="RenderTransform">
                            <Setter.Value>
                                <ScaleTransform ScaleX="0.85" ScaleY="0.85"/>
                            </Setter.Value>
                        </Setter> 
                        <Setter Property="RenderTransformOrigin" Value="0.5, 0.5"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
       </Setter.Value>
    </Setter>
</Style>

The transform in this style simply makes the item look as though it had been pressed.

And now for the actual trick of making the new skin available to the app thus replacing the defaults in App.xaml.  I need a new Application configuration file (App.config) with an appSetting (skins) to hold the required skins for this application build.  This is a comma-separated list of resource dictionaries that we are going to include in this build.  In order to allow the new skins to override the existing ones we are required to move the current resources that we have defined into a dictionary of their own (this is due to the scoping rules of merged dictionaries in WPF – the primary dictionary will always take precedence).  I am simply going to move them into a new dictionary called OriginalSkin.xaml and include this in my skins appSetting which now looks like this.

<add key="skins" value="OriginalSkin.xaml,Skin.xaml"/>

We now have the two dictionaries ready to be loaded.  In App.xaml.cs we need a new method to load the skins from the config and this should be called from the OnStartup method of the app (its virtual so is available right there in app.xaml.cs to be overridden).

private void LoadSkins()
{
    string skins = ConfigurationManager.AppSettings["skins"];
    if (string.IsNullOrEmpty(skins))
    {
        return;
    }

    string[] resources = skins.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
    foreach (string resource in resources)
    {
        ResourceDictionary dictionary = new ResourceDictionary();
        dictionary.Source = new Uri(resource, UriKind.Relative);
        this.Resources.MergedDictionaries.Add(dictionary);
    }
}

This code loops through the skins in the appSetting and adds them as merged dictionaries.  The beauty of this is that merged dictionaries are read in the order that they are added so the latter dictionaries will override the earlier ones resulting in our newly minted RoundedList_skin being called instead of the original one but the original version of RoundedList is still available for the other ListBox:

image

We can now add any number of skins to this configuration but we will only load the ones listed in the App.config file – this now makes the app very extensible and can be changed at the drop of a hat.

The sample application that I used for this post can be found here

About these ads

About Leom Burke
Working for a healthcare company in the UK with a focus on C#/WPF/WCF/EF. Personally I also enjoy working with ASP.NET (MVC), ruby and investigating best practice using methods like TDD and bettering the quality of code. This is personal blog and as such has no relationship to the thoughts, views and feelings of the company I happen to be working for.

One Response to Skinning in WPF

  1. Pingback: DotNetKicks.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: