marko devcic

  • github:
    deva666
  • email:
    madevcic {at} gmail.com

Changing WPF themes dynamically

Posted on 1 September 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:

<Application x: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>
                <ResourceDictionary Source="/Themes/Blue.xaml" />
            </ResourceDictionary.MergedDictionaries>

            <Style x:Key="WindowBase"
                   TargetType="Window">
                <Setter Property="Background"
                        Value="{DynamicResource WindowBackgroundBrush}" />
                <Setter Property="BorderBrush"
                        Value="{DynamicResource WindowBorderBrush}" />
                <Setter Property="BorderThickness"
                        Value="1" />
                <Setter Property="WindowStyle"
                        Value="None" />
                <Setter Property="FontFamily"
                        Value="{DynamicResource DefaultFontFamily}" />
                <Setter Property="Foreground"
                        Value="{DynamicResource WindowForegroundBrush}" />               
                <Setter Property="AllowsTransparency"
                        Value="True" />
            </Style>

            <Style BasedOn="{StaticResource WindowBase}"
                   TargetType="Window"
                   x:Key="WindowStyle">
                <Setter Property="RenderOptions.BitmapScalingMode"
                        Value="LowQuality" />
                <Setter Property="RenderOptions.EdgeMode"
                        Value="Unspecified" />
                <Setter Property="BorderThickness"
                        Value="1" />
            </Style>

            <Style TargetType="Button">
                <Setter Property="Background"
                        Value="{DynamicResource 2ndBackgroundBrush}" />
                <Setter Property="BorderBrush"
                        Value="{DynamicResource 2ndBackgroundBrush}" />
                <Setter Property="BorderThickness"
                        Value="1" />
                <Setter Property="Foreground"
                        Value="{DynamicResource LabelBrush}" />
                <Setter Property="HorizontalContentAlignment"
                        Value="Center" />
                <Setter Property="VerticalContentAlignment"
                        Value="Center" />
                <Setter Property="Padding"
                        Value="10,2,10,2" />
                <Setter Property="MinHeight"
                        Value="25" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type Button}">
                            <Border Name="Chrome"
                                    Background="{TemplateBinding Background}"
                                    BorderBrush="{DynamicResource WindowBorderBrush}"
                                    BorderThickness="{TemplateBinding BorderThickness}"
                                    SnapsToDevicePixels="true"
                                    TextBlock.Foreground="{DynamicResource LabelBrush}">
                                <ContentPresenter Name="Presenter"
                                                  Margin="{TemplateBinding Padding}"
                                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                                  RecognizesAccessKey="True"
                                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                                  TextBlock.Foreground="{DynamicResource LabelBrush}" />
                            </Border>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsMouseOver"
                                         Value="True">
                                    <Setter Property="Background"
                                            Value="{DynamicResource LightAccent}" />

                                    <Setter Property="Foreground"
                                            Value="{DynamicResource HighlightBrush}" />
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

            <Style TargetType="Label"
                   >
                <Setter Property="Label.FontFamily"
                        Value="{DynamicResource DefaultFontFamily}" />
                <Setter Property="Label.FontSize"
                        Value="13" />
                <Setter Property="Label.Foreground"
                        Value="White" />
                <Setter Property="Label.Height"
                        Value="Auto" />
                <Setter Property="Label.Width"
                        Value="Auto" />
                <Setter Property="Label.HorizontalAlignment"
                        Value="Center" />
                <Setter Property="Label.VerticalAlignment"
                        Value="Center" />
                <Setter Property="Label.HorizontalContentAlignment"
                        Value="Center" />
                <Setter Property="Label.VerticalContentAlignment"
                        Value="Center" />
                <Setter Property="Label.FocusVisualStyle"
                        Value="{x:Null}" />
                <Setter Property="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.

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

    <FontFamily x:Key="DefaultFontFamily">Tahoma</FontFamily>
    <Color x:Key="WindowBorderColor">#FF1E90FF</Color>
    <Color x:Key="LightAccentColor">#454545</Color>
    <Color x:Key="2ndBackgroundColor">#2E2E2E</Color>
    <SolidColorBrush x:Key="WindowBorderBrush"
                     Color="DodgerBlue" />
    <SolidColorBrush x:Key="LightAccent"
                     Color="#454545" />
    <SolidColorBrush x:Key="DarkAccent"
                     Color="#383838" />
    <SolidColorBrush x:Key="ItemText"
                     Color="#d1d1d1" />

    <LinearGradientBrush x:Key="WindowBackgroundBrush"
                         EndPoint="0.5,1"
                         StartPoint="0.5,0"
                         SpreadMethod="Reflect">
        <GradientStop Color="#242424"
                      Offset="1" />
        <GradientStop Color="#FF2C2C2C"
                      Offset="1" />
    </LinearGradientBrush>
    <SolidColorBrush x:Key="2ndBackgroundBrush"
                     Color="#2E2E2E" />

    <SolidColorBrush x:Key="WindowForegroundBrush"
                     Color="Gray" />
    <SolidColorBrush x:Key="LabelBrush"
                     Color="LightGray" />
    <SolidColorBrush x:Key="HighlightBrush"
                     Color="White" />
    <SolidColorBrush x:Key="BackgroundBrush"
                     Color="#FF1B1B1B" />

</ResourceDictionary>

And the code to change between the themes:

        private void 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!