Click here to Skip to main content
15,885,216 members
Articles / Desktop Programming / WPF

Loading WPF themes at runtime

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
1 Sep 2009CPOL4 min read 66.3K   3K   34   3
This acticle presents how to build WPF themes and apply them to an application at runtime.

Introduction

This article presents a sample application that uses a combo box to change a WPF theme at runtime. This is a continuation of another article I wrote that presented a Blend like style for a scrollbar.

This code is intended to help an application developer change or add styles to his/her applications without recompiling it.

In order for this to happen, I chose a pluggable architecture. What I mean by this is that I have a dedicated folder in the application where all the themes reside. If the user wants a new theme, all he/she needs to do is to add a new assembly to that folder. When the application starts, I load the assemblies using Reflection, as will be seen later.

The themes are represented by resource dictionaries. These resource dictionaries also have a code-behind class attached to them so that I can access them in code. All the assembly needs to have is the resource dictionaries that represent the themes. The number of files doesn’t matter, the resource key names don’t matter. All that matters is that the templates be applied using styles that apply to controls by default (styles without a resource key).

Using the code

The application solution has three projects. One is the main project that uses the themes. The other two projects show how a theme should be built in order to be properly loaded. I will present those two projects first, and at the end, I’ll show the reflection code that loads these assemblies.

Theme assemblies

Like I said in the beginning, themes will be represented by an assembly. This assembly will contain one or more resource dictionaries that will contain the resources for the theme (brushes, animations, control templates, styles etc.). This can be seen in the image below:

Image 1

As you can see from the image above, every resource dictionary has an associated code-behind file. This code-behind file will be very useful when loading resources. In order to link the resource dictionary file to the code-behind file, you have to do a few things.

First, you have to set the x:Class attribute in the resource dictionary to the name you want the class to have. This must be a fully qualified name. The code can be seen below. The code is for the ListBoxStyle.xaml dictionary.

XML
<ResourceDictionary x:Class="Theme1.ListBoxDictionary"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

The next step is to add a new partial class to link this file. The code for the class is minimal, and can be seen below:

C#
namespace Theme1
{
    public partial class ListBoxDictionary:ResourceDictionary
    {
       public ListBoxDictionary()
       {
            InitializeComponent();
       }
    }
}

As you can see, you need a partial class that derives from ResourceDictionary. Because this class is linked to the XAML file, it will have the InitializeComponent() method that is automatically generated by the designer. All the custom class does is call this method in the constructor. You will need classes like this for all the dictionaries you provide.

The code for the second file is similar. The XAML class name can be seen below:

XML
<ResourceDictionary x:Class="Theme1.ScrollBarDictionary"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >

The code-behind file is equally simple:

C#
namespace Theme1
{
   public partial class ScrollBarDictionary: ResourceDictionary
   {
        public ScrollBarDictionary()
        {
            InitializeComponent();
        }
    }
}

I won’t present the code for the second theme because it is almost identical. The only thing that is different is the styles that are applied. You can see what the first theme looks like by viewing my previous article at:

or you can download the sample for this article.

After you have all the styles for your theme, you need to compile the assembly and you’re done. You can now add the assembly to the reserved folder in the main application.

The main application

The main application is really small. It has two windows. The main window will contain the list box control that will be styled, and the second window will let you choose the theme to apply. When the application starts, no theme is applied, but you can change this easily. The two windows can be seen below:

Image 2

On the left, you can see the second theme, and on the right, you can see the options window. Seeing that the code to open the options window is trivial (call ShowDialog() when I select Tools -> Options from the menu), I’ll only talk about the code in the options window.

When the options window is loaded, I load every assembly in the MyThemes directory and store the references in a generic list. The code for this can be seen below:

C#
DirectoryInfo di = new DirectoryInfo("../../MyThemes");

foreach (FileInfo fi in di.GetFiles())
{
  try
  {
       themes.Add(Assembly.LoadFile(fi.FullName));
  }
  catch{}
}
cbThemes.ItemsSource = themes.Select(p => p.GetName().Name).ToList();

The user can select a theme from the combo box and then press the apply button. When he/she does this, I retrieve the selected assembly based on the name:

C#
Assembly a = themes.Where(p => p.GetName().Name.Equals(cbThemes.SelectedValue))
          .SingleOrDefault();

If the assembly exists in the assembly list, I first clear the applications resource dictionary:

C#
if (a != null){ 
   ((App)Application.Current).Resources.MergedDictionaries.Clear();

After this, I iterate over all the types in the assembly, and if the type is a subclass of ResourceDictionary (there might be classes that are not), I instantiate it and add it to the merged dictionaries collection, like so:

C#
foreach (Type t in a.GetTypes())
{
     Trace.WriteLine(t.FullName);
     if(t.IsSubclassOf(typeof(ResourceDictionary)))
     {
          ConstructorInfo ci = t.GetConstructor(Type.EmptyTypes);
          ResourceDictionary rd = 
                 (ResourceDictionary)ci.Invoke(new object[] { });
          ((App)Application.Current).Resources.MergedDictionaries.Add(rd);
     }
}

This is it. Hope you like it.

History

  • Added Tuesday, September 1, 2009.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Romania Romania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionDoes this work for open (yet inactive) windows? Pin
Member 1031707417-Jun-15 11:25
Member 1031707417-Jun-15 11:25 
QuestionClarification Pin
abeuwe14-Nov-12 1:04
abeuwe14-Nov-12 1:04 
Hi, Thanks for your post. Does this need the themes file to be present where the executable is or is it independent of it. I tried copying the executable to another location and try running it and when I try to select a theme it fails.

Cheers
A
AnswerRe: Clarification Pin
Florin Badea14-Nov-12 3:30
Florin Badea14-Nov-12 3:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.