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 .
Let's 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 ListBox
es 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 lists, so let's 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:
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 (it's 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
:
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.