Introduction
You might have noticed that I am switching to WPF from winforms. I’m doing this gradually, but DI/IoC is making this very painless. For the moment I have both in the same app. I have not seen any problems yet. One of the advantages of WPF is that you can do theming, this lets you can change the look of your application in a single line of code. I guess you can do something similar in winforms but it needs a lot more work. First I will show you how easy it is to theme your application and then how much easier it is if you refactor your code to a pattern. “What pattern?”, I hear you ask. Well I don’t really care for the moment.
Changing themes
First thing to do is download some [themes from codeplex][1]. And then we copy these themes into our project under a themes folder.
Now I just add 3 buttons to my window, one for each theme.
And this is the code that goes with it to change the themes.
Private Sub btnBlue_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnBlue.Click
Dim skin = New ResourceDictionary()
skin.Source = New Uri("ThemesBureauBlueTheme.xaml", UriKind.Relative)
Application.Current.Resources.MergedDictionaries.Add(skin)
End Sub
Private Sub btnBlack_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnBlack.Click
Dim skin = New ResourceDictionary()
skin.Source = New Uri("ThemesBureauBlackTheme.xaml", UriKind.Relative)
Application.Current.Resources.MergedDictionaries.Add(skin)
End Sub
Private Sub btnSilver_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnSilver.Click
Dim skin = New ResourceDictionary()
skin.Source = New Uri("ThemesExpressionLightTheme.xaml", UriKind.Relative)
Application.Current.Resources.MergedDictionaries.Add(skin)
End Sub```
And this is the result.
On startup, no theme.
<div class="image_block">
<img src="https://lessthandot.z19.web.core.windows.net/wp-content/uploads/blogs/DesktopDev/WPF/theme2.png" alt="" title="" width="354" height="261" />
</div>
Blue theme.
<div class="image_block">
<img src="https://lessthandot.z19.web.core.windows.net/wp-content/uploads/blogs/DesktopDev/WPF/theme3.png" alt="" title="" width="354" height="261" />
</div>
Black theme.
<div class="image_block">
<img src="https://lessthandot.z19.web.core.windows.net/wp-content/uploads/blogs/DesktopDev/WPF/theme4.png" alt="" title="" width="354" height="261" />
</div>
And silver theme.
<div class="image_block">
<img src="https://lessthandot.z19.web.core.windows.net/wp-content/uploads/blogs/DesktopDev/WPF/theme5.png" alt="" title="" width="354" height="261" />
</div>
That was simple.
## Refactoring
As we can see from the code above we are already repeating ourselves. So we solve that first by Adding a method.
```vbnet
Private Sub btnBlue_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnBlue.Click
ChangeTheme("BureauBlue")
End Sub
Private Sub btnBlack_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnBlack.Click
ChangeTheme("BureauBlack")
End Sub
Private Sub btnSilver_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnSilver.Click
ChangeTheme("ExpressionLight")
End Sub
Private Sub ChangeTheme(ByVal Theme As String)
Dim skin = New ResourceDictionary()
skin.Source = New Uri("Themes" & Theme & "Theme.xaml", UriKind.Relative)
Application.Current.Resources.MergedDictionaries.Add(skin)
End Sub```
Much better, Now I can add a theme and I just have to add one line of code.
```vbnet
Private Sub ChangeTheme(ByVal Theme As String)
Dim _FileName = "Themes" & Theme & "Theme.xaml"
Dim skin = New ResourceDictionary()
skin.Source = New Uri(_FileName, UriKind.Relative)
Application.Current.Resources.MergedDictionaries.Add(skin)
End Sub```
Now it is time to think about writing tests, lets consider the above as a spike to find out how changing themes works.
I can think of three test I want from the start.
* Can change theme to Blue
* Can change theme to Black
* Can change theme to Silver
This could be one such test.
```vbnet
<Test()> _
Public Sub CanThemeChangeToBlue()
Dim _ThemeChanger = New ThemeChanger()
_ThemeChanger.ChangeTheme("Blue")()
Assert.AreEqual(0, (From e In Application.Current.Resources.MergedDictionaries Where e.Source.AbsolutePath.Contains("ThemesBureauBlueTheme.xaml")).Count)
End Sub
This of course will not even compile. But with a bit of reshaper magic we make it run.
So now I need a ThemeChanger Class for easy testing. ThemeChanger Class also has a method called Changetheme.
So ThemeChanger will look like this after we copy paste our code from the window into it.
Public Class ThemeChanger
Private Sub ChangeTheme(ByVal Theme As String)
Dim _FileName = "pack://application:,,,/WpfApplication1;component/themes/" & Theme & "/Theme.xaml"
Dim skin = New ResourceDictionary()
skin.Source = New Uri(_FileName, UriKind.RelativeOrAbsolute)
Application.Current.Resources.MergedDictionaries.Add(skin)
End Sub
End Class```
Now the tests above will go green.
And the window will now look like this.
```vbnet
Class MainWindow
Private _ThemeChanger As ThemeChanger
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_ThemeChanger = New ThemeChanger
End Sub
Private Sub btnBlue_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnBlue.Click
_ThemeChanger.ChangeTheme("BureauBlue")
End Sub
Private Sub btnBlack_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnBlack.Click
_ThemeChanger.ChangeTheme("BureauBlack")
End Sub
Private Sub btnSilver_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnSilver.Click
_ThemeChanger.ChangeTheme("ExpressionLight")
End Sub
End Class
Ok, all that will work. But I still see a few problem areas. For one I instantiate my themechanger (a dependency in my constructor and for two I have a magic string (magic strings are a path to the dark side.)
Lets solve the magic string first.
I like to use generics for that.
So I need an interface that all my classes will share and they need a method to change the theme.
Public Interface ITheme
Sub ChangeTheme()
End Interface```
And an implementation would look like this.
```vbnet
Public Class BlackTheme
Implements ITheme
Public Sub ChangeTheme() Implements ITheme.ChangeTheme
Dim _FileName = "pack://application:,,,/WpfApplication1;component/themes/BureauBlack/Theme.xaml"
Dim skin = New ResourceDictionary()
skin.Source = New Uri(_FileName, UriKind.RelativeOrAbsolute)
Application.Current.Resources.MergedDictionaries.Add(skin)
RaiseEvent ThemeChanged()
End Sub
End Class```
But I would have all my classes now have all those lines of code they don’t really need. Let’s make that better.
```vbnet
Public MustInherit Class BaseTheme
Implements ITheme
Protected Sub ChangeTheme(ByVal Theme As String)
Dim _FileName = "pack://application:,,,/WpfApplication1;component/themes/" & Theme & "/Theme.xaml"
Dim skin = New ResourceDictionary()
skin.Source = New Uri(_FileName, UriKind.RelativeOrAbsolute)
Application.Current.Resources.MergedDictionaries.Add(skin)
RaiseEvent ThemeChanged()
End Sub
Public MustOverride Sub ChangeTheme() Implements themes.ITheme.ChangeTheme
End Class```
And now the implementation of BlackTheme will look like this.
```vbnet
Public Class BlackTheme
Inherits BaseTheme
Public Overloads Overrides Sub ChangeTheme()
ChangeTheme("BureauBlack")
End Sub
End Class```
And the implementation of Themechanger will look like this. And that is easily testable too.
```vbnet
Public Class ThemeChanger
Public Sub ChangeTheme(Of Theme As {New, ITheme})()
Dim _Theme as new Theme
_Theme.ChangeTheme()
End Sub
End Class```
And A little change to our test.
```vbnet
<Test()> _
Public Sub CanThemeChangeToBlue()
Dim _ThemeChanger = New ThemeChanger()
_ThemeChanger.ChangeTheme(Of BlueTheme)()
Assert.AreEqual(0, (From e In Application.Current.Resources.MergedDictionaries Where e.Source.AbsolutePath.Contains("ThemesBureauBlueTheme.xaml")).Count)
End Sub
Our window is now looking like this.
Class MainWindow
Private _ThemeChanger As ThemeChanger
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_ThemeChanger = New ThemeChanger
End Sub
Private Sub btnBlue_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnBlue.Click
_ThemeChanger.ChangeTheme(Of BlueTheme)()
End Sub
Private Sub btnBlack_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnBlack.Click
_ThemeChanger.ChangeTheme(Of BlackTheme)()
End Sub
Private Sub btnSilver_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnSilver.Click
_ThemeChanger.ChangeTheme(Of SilverTheme)()
End Sub
End Class```
Now I still have that dependecy on Themechanger that gets initialized in the constructor of the window. I don’t like this and here I have to make a design decision. Do I make Themechanger static/shared or do I make an interface and inject it. I prefer not to make things static since they are not so easy to test but in this case I make an exception.
Since it won’t make a difference to testing.
So in the end we end up with ThemeChanger being this.
```vbnet
Public Class ThemeChanger
Public Shared Sub ChangeTheme(Of Theme As {New, ITheme})()
Dim _Theme as new Theme
Theme.ChangeTheme()
End Sub
End Class```
And the test will now become.
```vbnet
<Test()> _
Public Sub CanThemeChangeToBlue()
ThemeChanger.ChangeTheme(Of BlueTheme)()
Assert.AreEqual(0, (From e In Application.Current.Resources.MergedDictionaries Where e.Source.AbsolutePath.Contains("ThemesBureauBlueTheme.xaml")).Count)
End Sub
And the window will be.
Class MainWindow
Private Sub btnBlue_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnBlue.Click
ThemeChanger.ChangeTheme(Of BlueTheme)()
End Sub
Private Sub btnBlack_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnBlack.Click
ThemeChanger.ChangeTheme(Of BlackTheme)()
End Sub
Private Sub btnSilver_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnSilver.Click
ThemeChanger.ChangeTheme(Of SilverTheme)()
End Sub
End Class```
## Advantages
I can now add more themes and I only have to add the xaml, an ITheme implementation and a button somewhere.
I can have those button on all my windows without having to write much code.
If a theme is deleted it will be easy to find out where I have references to it since the compiler will tell me when an ITheme implementation no longer exists.
I can easily test most things (uhm that is not really true because WPF doesn’t let you test things easily.
## Problems
I had some major problems getting my test to work. The code worked just fine when run but the NUnit testrunner gave me an Exception. Namely this one.
To begin with I had these uri’s.
```vbnet
New Uri("Themes/BureauBlue/Theme.xaml", UriKind.RelativeOrAbsolute)```
That gave me this error.
> System.NotSupportedException : The URI prefix is not recognized.
> at System.Net.WebRequest.Create(Uri requestUri, Boolean useUriBase)
> at System.Net.WebRequest.Create(Uri requestUri)
> at MS.Internal.WpfWebRequestHelper.CreateRequest(Uri uri)
> at System.IO.Packaging.PackWebRequest.GetRequest(Boolean allowPseudoRequest)
> at System.IO.Packaging.PackWebRequest.GetResponse()
> at MS.Internal.WpfWebRequestHelper.GetResponse(WebRequest request)
> at System.Windows.ResourceDictionary.set_Source(Uri value)
> at WpfApplication1.themes.BaseTheme.ChangeTheme(String Theme) in BaseTheme.vb: line 9
> at WpfApplication1.themes.BlackTheme.ChangeTheme() in BlackTheme.vb: line 6
> at WpfApplication1.themes.ThemeChanger..ctor() in ThemeChanger.vb: line 8
> at WpfApplication1.Tests.TestThemeChanger.CanThemeChangeToBlue() in TestThemeChanger.vb: line 25
I solved that by adding the pack reference to it.
like this.
```vbnet
Dim _FileName = "pack://application:,,,/WpfApplication1;component/themes/" & Theme & "/Theme.xaml"
```
But that gave me this exception.
> System.UriFormatException : Invalid URI: Invalid port specified.
> at System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind)
> at System.Uri..ctor(String uriString, UriKind uriKind)
> at WpfApplication1.themes.BaseTheme.ChangeTheme(String Theme) in BaseTheme.vb: line 9
> at WpfApplication1.themes.BlackTheme.ChangeTheme() in BlackTheme.vb: line 6
> at WpfApplication1.themes.ThemeChanger..ctor() in ThemeChanger.vb: line 8
> at WpfApplication1.Tests.TestThemeChanger.CanThemeChangeToBlue() in TestThemeChanger.vb: line 25
And the above is caused bycause WPF is not initalized and thus the resources aren’t there.
So this needs to be added to the testclass.
```vbnet
<SetUp()>
Public Sub InitializeWPFToBeAbleToUsePackURIs()
Dim app = New System.Windows.Application()
Windows.Application.ResourceAssembly = GetType(InitializingNewItemEventArgs).Assembly
End Sub```
I found a [blogpost][2] that was really helpfull in solving the above problem and especialy the comments were helpfull. Thank you Puaal for that post and thank you Oskar for the comment.
## Conclusion
You see what a little refactoring can do to your code. It makes it more flexible and more testable. And don’t worry that it takes a little time to write this stuff you will be rewarded afterwards.
And what patterns did we use? I find it less important that you can name them, I find it more important that you know how to use them.
But here is a little list.
* [Command pattern][3] (ITheme and implementations)
* [Strategy pattern][4] (BaseTheme)
If you see more then I’ll be glad to know.
I guess this became quit a long post.
[1]: http://wpfthemes.codeplex.com/
[2]: http://compilewith.net/2009/03/wpf-unit-testing-trouble-with-pack-uris.html
[3]: http://www.dofactory.com/Patterns/PatternCommand.aspx
[4]: http://www.dofactory.com/Patterns/PatternStrategy.aspx