marko devcic

Software Engineer
  • github:
    deva666
  • email:
    madevcic {at} gmail.com
Changing WPF themes dynamically

Changing WPF themes dynamically

Posted on 09/01/2014

When I first started with the WPF, I struggled with changing of resources (themes) during runtime. Coming from WinForms background, the WPF was overwhelming at first and I had to do a lot of research to get to know how the Resource Dictionaries work, where can they be declared and what their scope is. Recently I've seen this question being brought up on StackOverflow a few times, so I'm going to show you how I've done it my last WPF project.

I presume anybody reading this post has a basic understanding of WPF, styles and Resource Dictionaries, if not, here's one article that explains the basics.

For demo purposes, I'm going to build a simple WPF app that will change colors with a click of a button. The key will be to not repeat the code in Resource Dictionaries, so I'll have all the styles and templates (the things that won't be changing) in one base dictionary file. These styles are going to refer to colors that will be placed in 2 separate Resource Dictionaries and only they will be changed during the runtime.

So in my App.xaml I'm going to define only styles for a window, label and a button:

<Applicationx:Class="Themes.App"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"StartupUri="MainWindow.xaml"><Application.Resources><ResourceDictionary><ResourceDictionary.MergedDictionaries><ResourceDictionarySource="/Themes/Blue.xaml"/></ResourceDictionary.MergedDictionaries><Stylex:Key="WindowBase"TargetType="Window"><SetterProperty="Background"Value="{DynamicResource WindowBackgroundBrush}"/><SetterProperty="BorderBrush"Value="{DynamicResource WindowBorderBrush}"/><SetterProperty="BorderThickness"Value="1"/><SetterProperty="WindowStyle"Value="None"/><SetterProperty="FontFamily"Value="{DynamicResource DefaultFontFamily}"/><SetterProperty="Foreground"Value="{DynamicResource WindowForegroundBrush}"/><SetterProperty="AllowsTransparency"Value="True"/></Style><StyleBasedOn="{StaticResource WindowBase}"TargetType="Window"x:Key="WindowStyle"><SetterProperty="RenderOptions.BitmapScalingMode"Value="LowQuality"/><SetterProperty="RenderOptions.EdgeMode"Value="Unspecified"/><SetterProperty="BorderThickness"Value="1"/></Style><StyleTargetType="Button"><SetterProperty="Background"Value="{DynamicResource 2ndBackgroundBrush}"/><SetterProperty="BorderBrush"Value="{DynamicResource 2ndBackgroundBrush}"/><SetterProperty="BorderThickness"Value="1"/><SetterProperty="Foreground"Value="{DynamicResource LabelBrush}"/><SetterProperty="HorizontalContentAlignment"Value="Center"/><SetterProperty="VerticalContentAlignment"Value="Center"/><SetterProperty="Padding"Value="10,2,10,2"/><SetterProperty="MinHeight"Value="25"/><SetterProperty="Template"><Setter.Value><ControlTemplateTargetType="{x:Type Button}"><BorderName="Chrome"Background="{TemplateBinding Background}"BorderBrush="{DynamicResource WindowBorderBrush}"BorderThickness="{TemplateBinding BorderThickness}"SnapsToDevicePixels="true"TextBlock.Foreground="{DynamicResource LabelBrush}"><ContentPresenterName="Presenter"Margin="{TemplateBinding Padding}"VerticalAlignment="{TemplateBinding VerticalContentAlignment}"HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"RecognizesAccessKey="True"SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"TextBlock.Foreground="{DynamicResource LabelBrush}"/></Border><ControlTemplate.Triggers><TriggerProperty="IsMouseOver"Value="True"><SetterProperty="Background"Value="{DynamicResource LightAccent}"/><SetterProperty="Foreground"Value="{DynamicResource HighlightBrush}"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style><StyleTargetType="Label"><SetterProperty="Label.FontFamily"Value="{DynamicResource DefaultFontFamily}"/><SetterProperty="Label.FontSize"Value="13"/><SetterProperty="Label.Foreground"Value="White"/><SetterProperty="Label.Height"Value="Auto"/><SetterProperty="Label.Width"Value="Auto"/><SetterProperty="Label.HorizontalAlignment"Value="Center"/><SetterProperty="Label.VerticalAlignment"Value="Center"/><SetterProperty="Label.HorizontalContentAlignment"Value="Center"/><SetterProperty="Label.VerticalContentAlignment"Value="Center"/><SetterProperty="Label.FocusVisualStyle"Value="{x:Null}"/><SetterProperty="TextOptions.TextFormattingMode"Value="Display"/></Style></ResourceDictionary></Application.Resources></Application>

Notice that I've added only one merged Resource Dictionary at the top of the file, this will be the default theme. During the runtime this is the file that will be swapped.

Also, all the colors and brushes in the styles are marked as a DynamicResource. If you put them as a StaticResource, they will be looked up only during the initial loading of the XAML and any subsequent change to that resource will not be observed.

Now that I have all the base styles defined, themed Resource Dictionary will have all the colors these styles are referring to.

<ResourceDictionaryxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><FontFamilyx:Key="DefaultFontFamily">Tahoma</FontFamily><Colorx:Key="WindowBorderColor">#FF1E90FF</Color><Colorx:Key="LightAccentColor">#454545</Color><Colorx:Key="2ndBackgroundColor">#2E2E2E</Color><SolidColorBrushx:Key="WindowBorderBrush"Color="DodgerBlue"/><SolidColorBrushx:Key="LightAccent"Color="#454545"/><SolidColorBrushx:Key="DarkAccent"Color="#383838"/><SolidColorBrushx:Key="ItemText"Color="#d1d1d1"/><LinearGradientBrushx:Key="WindowBackgroundBrush"EndPoint="0.5,1"StartPoint="0.5,0"SpreadMethod="Reflect"><GradientStopColor="#242424"Offset="1"/><GradientStopColor="#FF2C2C2C"Offset="1"/></LinearGradientBrush><SolidColorBrushx:Key="2ndBackgroundBrush"Color="#2E2E2E"/><SolidColorBrushx:Key="WindowForegroundBrush"Color="Gray"/><SolidColorBrushx:Key="LabelBrush"Color="LightGray"/><SolidColorBrushx:Key="HighlightBrush"Color="White"/><SolidColorBrushx:Key="BackgroundBrush"Color="#FF1B1B1B"/></ResourceDictionary>

And the code to change between the themes:

privatevoid btnChangeTheme_Click(object sender, RoutedEventArgs e)
        {
            Application.Current.Resources.MergedDictionaries.Clear();
            if(_blue)
                Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary() { Source = new Uri("/Themes/Orange.xaml", UriKind.Relative) });
            else
                Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary() { Source = new Uri("/Themes/Blue.xaml", UriKind.Relative) });
            _blue = !_blue;
        }

The styles and templates stay the same, so before adding the new theme, we clear only the merged dictionary, which has colors definitions.

That's it, you can download the complete Visual Studio project here.

Happy coding!