WPF: Adventures in Theming (Part 3)

So in [Part 1] I explained the basics of theming your WPF application and in [Part 2] I provided a more in-depth discussion of how the styles are applied to the various controls in WPF. I also touched on some problems with the WPF style system that prevents us from really creating themes for our applications. The lookup system does not give us an ideal place to inject our custom theme and because not every style lookup goes through the Application resource dictionary you have to modify existing code to make it lookup the correct style.

So I’m going to show you how we can hack WPF so that we can have a real theme solution for our applications.  No more BasedOn=”{StaticResource {x:Type …}}”. Automatic style usage for derived controls like Window and UserControl or any other control you’ve derived just to add capability but don’t need a new style.  Excited? :D

The Idea

What we’re going to do is flat out replace the dictionary that WPF thinks it’s using to lookup a specific theme’s version of a resource for our application.  If we do this the only fallback for a control that you do not replace the theme for will revert to the generic/classic look.  I highly recommend that you start with a complete theme if you’re planning to do this.

Luckily Microsoft provides us with a series of such themes.  You can download the XAML for any of the WPF themes directly from MSDN.  [Here is the one for Aero].  There are a few free ones on the internet / CodePlex, but most of them don’t have a style for every control; just the most common ones.  So I would try starting with the Aero theme and just piecemeal replace old Aero styles with your own.

The Hack

If you remember in [Part 2], the class responsible for caching theme data is the System.Windows.SystemResources class.  While it is internal, we can still access it using reflection.  Once we have a reference to it, there’s a private member we can access called “_dictionaries”.  It’s a hashtable of assemblies as keys to an entry in the theme dictionary containing references to the theme dictionary and the generic dictionary for an assembly.

If we grab the object stored at a corresponding assembly in the hashtable which is of the type, System.Windows.SystemResources.ResourceDictionaries.  Once we have this object, we just need to replace the ResourceDictionary stored in the “themedDictionary” field on this object.  We also need to set another field, “themedLoaded” to true, so that it doesn’t attempt to stomp our custom theme thinking that it hasn’t already loaded the theme for an assembly.

However, if the user changes the system theme while the application is running it will unmake our changes so we need to make sure we listen for a theme change.  This can be done by adding a hook for WndProc messages listening for the WM_THEMECHANGED window message.

Finally, when we replace a theme dictionary with our own.  There’s no event for that so no controls reset to the new theme.  So we need to make our application think the theme has changed.  We can do this with a few pinvoke calls by calling EnumWindows, and performing a SendMessage for each window in our application, sending a message with the same WM_THEMECHANGED.

That’s it, that’s how it works.  If you’re interested on the actual code you can download the source from the CodePlex project I added.

The WPF Theme Replacer

I’ve added a new project to CodePlex called the [WPF Theme Replacer].  That wraps this hack in a nice friendly class to encapsulate the horror…the horror…

Check it out :D