Click here to Skip to main content
Email Password   helpLost your password?

Introduction

One of the best features in WPF is the ability to replace Resources, Styles, Control Templates and Data Templates at runtime. Based on this feature, one can design and implement a theme or a skin mechanism as introduced in many articles including this one.

There are several ways to implement such a mechanism; each has its pros and cons.

In this article, I will talk about different techniques for handling WPF themes and skins. I will also provide a helper class for loading and unloading themes.

Acronyms

Skin

Set of UI visual Resources and Styles that usually can be replaced at runtime by the user. Skins can be installed with the application, or can be downloaded later from the internet. Skins change the face of the user interface, shapes, colors, backgrounds etc. If well designed, a skin can be created and/or edited by users. See applications such as Media Player, WinAmp and many others.

Theme

Windows operating system based theme. The one you replace from the Display/Desktop setting. For example: Classic, Luna, Royale, Aero, etc. WPF has a built-in mechanism for loading styles based on the actual Windows theme (see this). Most developers mislead by saying Theme but intended to say Skin. Themes change the face of Windows controls, such as ComboBox, TextBox, Buttons, etc. Themes are replaced from the Windows settings and not from the Application settings.

This article is not dealing with the creation of Windows themes. It only shows how to load them at runtime as skins, without depending on Windows settings.

Background

Actually there are two well known techniques in WPF for skinning: Loose and Compiled.

Loose

Loose skin mechanism is based on loose XAML files which are actually resource dictionaries. These dictionaries contain Styles, Templates, and Resources and are neither serialized (baml) nor packed into any assembly.

To load a theme or a skin based on a loose XAML files, one may call the XamlReader.Load static method, passing it the URI of the file, or may create a ResourceDictionary and set its Source property to the loose XAML file URI. After load is complete, one should merge the dictionary with the application.

ResourceDictionary skin = new ResourceDictionary();
skin.Source = new Uri(@"Skins\Skin.xaml", UriKind.Relative);
Application.Current.Resources.MergedDictionaries.Remove(skin); 

Pros

Cons

Compiled

Compiled skin mechanism is based on resource dictionaries inside XAML files, which are parsed, serialized (baml) and packed into the governed assembly. Types such as custom controls (borders, decorators, etc.) and Value Converters are optionally compiled into the same assembly.

To load a theme or a skin based on a compiled resource, one may add a reference to the theme or skin assembly (unless the theme is in the GAC), and merge it with the application resources.

<Application.Resources>
  <ResourceDictionary>
   <ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="/PresentationFramework.Aero;V3.0.0.0;31bf3856ad364e35;
                component\themes/aero.normalcolor.xaml" />
   </ResourceDictionary.MergedDictionaries>
   <!-- Other Resources here -->
  </ResourceDictionary>
</Application.Resources>

Pros

Cons

Code - Skin Loader

To overcome some of the disadvantages in the compiled version theme, to be able to load compiled version theme from any directory and to simplify the load process of a skin, I have implemented a simple skin loader helper as follows:

Skin – An abstract base class for all skin loader types. It provides an interface for loading and unloading a skin, and maintains the skin resources. The Load and Unload methods in this type merge or remove the skin resources from the application single instance resources respectively. It is important to remove the resources of a skin before loading others. Failing to do so may cause a memory leak.

ReferencedAssemblySkin – Loads a referenced theme assembly as previously described. Before using this type, one should add a reference to the theme assembly, and compile the application with it. The assembly loads and stays in the main AppDomain for the application life time.

The screen shot below demonstrates how the Tomers.WPF.Themes.SimpleSkin.dll assembly loads into the process, using Process Explorer.

ss1.jpg

AppDomainAssemblySkin – Loads a skin assembly into a different application domain. This overcomes the problem that is caused by loading a theme assembly directly, as described in this article. The Load override implementation of this type loads the skin into a different application domain, and passes the skin resource stream to the calling application domain. Then a BamlHelper class is used to deserialize the resource stream back into a ResourceDictionary.

The screen shot below looks similar to the previous one, but as you can see, the Tomers.WPF.Themes.SimpleSkin.dll assembly (previously below gdi32.dll) didn't load into the AppDomain/Process.

ss2.jpg

DirectAssemblySkin – Similar to ReferencedAssemblySkin type, this type loads any theme assembly from any location. There is no need to add a reference to the theme assembly.

LooseXamlSkin – Loads a loose XAML file skin as described earlier.

Comparison

To test the load time of each of the strategies above, I wrote a simple test application. The test application draws six WPF controls in a stack, and provides an option to replace Skins at runtime.

The image below is a screen shot from the test application:

ss3.jpg

These are the results from the comparison:

Skin Strategy First Load Time (ms.) Second Load Time (ms.)
Simple Direct 16 15
Simple AppDomain 125 93
Classic Loose 1045 950
Classic AppDomain 375 312

Conclusion

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralXamlParseException
Member 6038196
1:13 16 Jul '09  
I use AppDomainAssemblySkin to load WPF themes in WPF toolkit, but got XamlParseException ,
How can I solve the problem?
QuestionShared Resources
JohnFenton
9:55 16 Mar '08  
Great progam! Thanks!!!

Is there a way to make this work with a shared resource file?

When I have this in my skins XAML

<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>

it balks that the resource cannot be found.
GeneralRe: Shared Resources
Tomer Shamam
2:12 18 Apr '08  
Hi,

Sorry for my late response. I was quite busy lately.

As for your question, you should provide the assembly name:
Source="/ReferencedAssembly;component/ResourceFile.xaml"

You may find the link bellow very helpful.
http://msdn2.microsoft.com/en-us/library/aa970069.aspx
GeneralRe: Shared Resources
Jammer
2:00 21 May '08  
Of course!!! <slaps forehead> Smile

Jammer

Going where everyone here has gone before! Smile
My Blog

GeneralVery Nice
Paul Conrad
11:51 27 Jan '08  

Very nice article. Worth bookmarking Big Grin


"I guess it's what separates the professionals from the drag and drop, girly wirly, namby pamby, wishy washy, can't code for crap types." - Pete O'Hanlon


GeneralRe: Very Nice
Tomer Shamam
11:56 27 Jan '08  
Thank you Smile
QuestionNice Article - One Question
Karl Shifflett
17:03 22 Jan '08  
I have a question about something you wrote.

"Once loaded can’t be unloaded until restarting the application."

I think you can unload resource dictionaries. You can remove all of them, or just one.

Application.Current.Resources.MergedDictionaries.Clear();


I'm probably missing something here.Confused Can you clear this up for me.

Thank you for posting a great WPF article,

Cheers, Karl

My Blog | Mole's Home Page | Choosing WPF over ASP.NET

Just a grain of sand on the worlds beaches.



AnswerRe: Nice Article - One Question
Tomer Shamam
22:58 22 Jan '08  
You can unload resources indeed (I did that in my solution), but you can’t unload an assembly from the AppDomain (it’s a well known .NET problem).
GeneralYou are the Master
Lior Israel
13:40 22 Jan '08  
Master of XAML Smile

lior

GeneralRe: You are the Master
Tomer Shamam
23:05 22 Jan '08  
Shucks 10x
GeneralGreat article!
woody3k
14:17 13 Jan '08  
Thanks for this, the example really helps clear up the advantages to each method.

I've generated some VS2005 projects for the example code, for those of us still using the earlier version. Can I email these to you so you don't need to do the same?

Andrew.
AnswerRe: Great article!
Tomer Shamam
22:46 13 Jan '08  
I will be glad to. Just send me the vs2005 solution, so I'll add it to my post.

Thanks
GeneralRe: Great article!
Andrew Wood
11:06 22 Jan '08  
Hi,

I uploaded a VS2005 version of this solution here. I'd tried using the email option in comments, but perhaps that didn't arrive.

Thanks again for your efforts on this article.

Andrew
GeneralRe: Great article!
Tomer Shamam
11:52 22 Jan '08  
10x for your help. I have posted it.
GeneralPerfect timing
Josh Smith
4:53 7 Jan '08  
Great article! I wrote code like this once, but never turned it into a set of reusable utility classes. I was going to write it again for my Podder[^] application, but I think I'll just use yours instead. You got my 5! Smile

:josh:
My WPF Blog[^]
Without a strive for perfection I would be terribly bored.

GeneralRe: Perfect timing
Tomer Shamam
1:32 13 Jan '08  
10x and good luck Smile
GeneralA very nice article on Skins/Themes
Sacha Barber
0:41 5 Jan '08  
I was going to do one of these, but you have done it perfectly here

Well done

Sacha Barber
Your best friend is you.
I'm my best friend too. We share the same view, and never argue

My Blog : sachabarber.net

AnswerRe: A very nice article on Skins/Themes
Tomer Shamam
1:39 6 Jan '08  
10x, Enjoy it Smile
GeneralRe: A very nice article on Skins/Themes
Sacha Barber
2:14 6 Jan '08  
most welcome...its cool

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same view, and never argue

My Blog : sachabarber.net


Last Updated 22 Jan 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010