WPF Dynamic Theme

May 2, 2007

tags: ,
8 comments


When I first read about WPF, I was very happy to hear that WPF supports themes. After a short reading, I had understand that a WPF theme is actually a XAML file which is automatically loaded as you run a WPF application on different operating systems such as Windows XP, Vista, etc, running with a different theme (luna, royale, etc). Actually, there is an option to create a new theme to work with WPF, but it is too burden and difficult to create.

It is very nice that WPF provides an option to choose a theme for your applications, when you replace the Windows Theme. But what if you want to choose different themes for the same application, for different customers, and without replacing their Windows Theme?

For example, you want different colors or different styles for your controls, depends on user configuration.

To overcome this problem, I have developed a simple mechanism for loading dynamic themes at runtime. This mechanism provides an easy way to replace one theme with another, only by replacing the theme file, and without compiling it, or the WPF application.

First, you have to create your theme file as a XAML resource-dictionary file, which defines colors, templates and styles as resources.

There are actually two ways for loading this theme file:

1. Static Theme - merge the theme file with the application resources using XAML.

2. Dynamic Theme - merge the theme file with the application resources using WPF API.

The former is simpler but also static since your theme file must always be located under the same directory, under the same name. The latter is more dynamic, it provides you an option to select the theme file name and location at runtime.


To implement the static-method, do as follows:


  1. Create a new WPF project
  2. Create a new project directory, call it themes
  3. Create a new XAML dictionary file under the themes directory, call it current.theme.xaml
  4. Select the file, and change its “Build Action” to Content, Copy if newer
  5. Paste one of the themes I have added bellow into the theme file you have just created
  6. Open the App.xaml file, and replace the <Application.Resources>…</Application.Resources> with this one:

 


         <Application.Resources>



   <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
         <ResourceDictionary Source=themes\current.theme.xaml/>
      </ResourceDictionary.MergedDictionaries>
   </ResourceDictionary>
</Application.Resources>

 


Add one or more buttons to the main window, build and run the code.


Now replace the theme file-content with the other one (drop the bin\debug\themes\current.theme.xaml file into visual studio, paste the second theme and save). Run the .exe file without compiling it again!!! You can see that the theme has change.


The problem with the static-method is that you should use the same file name for each different theme.


 


To implement the dynamic-method, do as follows:



  1. Create a new WPF project
  2. Create a new project directory, call it themes
  3. Add one or more XAML dictionary files under the themes directory
  4. Set each file “Build Action” to Content, Copy if newer
  5. Open the App.xaml.cs file, and override the method bellow:

 



protected override void OnStartup(StartupEventArgs e)


{
   if (e.Args.Length == 1 && File.Exists(e.Args[0]))
   {
      Uri themeUri = new Uri(e.Args[0], UriKind.Relative);
      ResourceDictionary theme = (ResourceDictionary)Application.LoadComponent(themeUri);
      Resources.MergedDictionaries.Add(theme);
   }
   base.OnStartup(e);
}

 


Add one or more buttons to the main window, build and run the code with the following command line argument:


<your_app_name>.exe themes\<your_theme.xaml>


Now try to use other theme, or just change the theme file, and run the application again, but without compiling it!!!


 


So now you are able to create dynamic themes, which you can replace whenever you like to, and without compiling your application.


The problem with these two solutions is, somehow WPF searches for the resource names while merging dictionaries files. Hence, you must add each theme file as a project element, although you use it as a content rather than a resource (weird!!!). Still, the resource is dynamic, since you can always use the same files, and all you have to do is to replace their content without compiling the application again.


 


[bubble.theme.xaml]


<ResourceDictionary


    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml>
 
  <!– Default Color –>
  <SolidColorBrush    x:Key=DefaultBackgroundBrush    Color=#FF1F2A43/>
    <SolidColorBrush    x:Key=DefaultBorderBrush            Color=#FF265184/>
    <SolidColorBrush    x:Key=DefaultForegroundBrush    Color=#FFBFD1FF/>
 
    <LinearGradientBrush x:Key=DefaultBubbleBrush EndPoint=0.545,2.847 StartPoint=0.545,-0.639>
        <GradientStop Color=#FFCBCEFF Offset=0.115/>
        <GradientStop Color=#FF2A3756 Offset=0.476/>
    </LinearGradientBrush>
 
 
    <!– Focus Color –>
    <SolidColorBrush    x:Key=FocusedBorderBrush Color=#FF7482E5 />
    <LinearGradientBrush x:Key=FocusedBubbleBrush EndPoint=0.545,2.847 StartPoint=0.545,-0.639>
        <GradientStop Color=#FFCBCEFF Offset=0.115/>
        <GradientStop Color=#FF18183F Offset=0.731/>
    </LinearGradientBrush>
 
 
    <!– Highlight Color –>
    <SolidColorBrush x:Key=HighlightedBorderBrush Color=#FF5F6CC8/>
 
    <LinearGradientBrush x:Key=HighlightedBackgroundBrush EndPoint=0.505,2.832 StartPoint=0.505,-0.654>
        <GradientStop Color=#FF2C3B5C Offset=0/>
        <GradientStop Color=#FF1F2A43 Offset=1/>
    </LinearGradientBrush>
 
    <LinearGradientBrush x:Key=HighlightedBubbleBrush EndPoint=0.545,2.847 StartPoint=0.545,-0.639>
        <GradientStop Color=#FFCBCEFF Offset=0.052/>
        <GradientStop Color=#FF2C3B5C Offset=0.457/>
    </LinearGradientBrush>
 
    <SolidColorBrush  x:Key=DefaultControlBrightBrush Color=#FF293D66/>
 
 
    <!– Press Color –>
  <LinearGradientBrush x:Key=PressedBackgroundBrush EndPoint=0.518,0.948 StartPoint=0.514,0.104>
    <GradientStop Color=#FF14213F Offset=0/>
    <GradientStop Color=#FF36476E Offset=1/>
    <GradientStop Color=#FF0D0F1C Offset=0.423/>
  </LinearGradientBrush>
  <LinearGradientBrush x:Key=PressedBubbleBrush EndPoint=0.545,2.847 StartPoint=0.545,-0.639>
    <GradientStop Color=#FFCBCEFF Offset=0/>
    <GradientStop Color=#FF1A1E3B Offset=0.212/>
  </LinearGradientBrush>
 
 
    <!– Button Template –>
    <Style BasedOn={x:Null} TargetType={x:Type Button}>
        <Setter Property=Template>
            <Setter.Value>
                <ControlTemplate TargetType={x:Type Button}>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height=0.5*/>
                            <RowDefinition Height=0.5*/>
                        </Grid.RowDefinitions>
 
                        <Rectangle x:Name=PART_Shape
              RadiusX=4 RadiusY=4
              Grid.RowSpan=2
              Margin=0,-0.333,0,0.333
              Fill={StaticResource DefaultBackgroundBrush}
              Stroke={StaticResource DefaultBorderBrush}
                        />
 
                        <Rectangle x:Name=PART_Bubble
              Opacity=0.6
              RadiusX=3.333
              RadiusY=3.333
              Margin=5,3,5,0
              VerticalAlignment=Stretch
              Height=Auto
              Grid.RowSpan=1
              Fill={StaticResource DefaultBubbleBrush}
                        />
 
                        <ContentPresenter x:Name=PART_Content
               SnapsToDevicePixels={TemplateBinding SnapsToDevicePixels}
               HorizontalAlignment={TemplateBinding HorizontalContentAlignment}
               Margin=8,4,8,4
               VerticalAlignment={TemplateBinding VerticalContentAlignment}
               Grid.RowSpan=2
               RecognizesAccessKey=True
               RenderTransformOrigin=0.5,0.5
                        />
 
                    </Grid>
 
                    <ControlTemplate.Triggers>
 
                        <!– Enabled –>
                        <Trigger Property=IsEnabled Value=True>
                            <Setter Property=Foreground Value={StaticResource DefaultForegroundBrush}/>
                        </Trigger>
 
                        <!– Focused –>
                        <Trigger Property=IsFocused Value=True>
                            <Setter Property=Stroke TargetName=PART_Shape Value={StaticResource FocusedBorderBrush} />
                            <Setter Property=Fill TargetName=PART_Bubble Value={StaticResource FocusedBubbleBrush} />
                        </Trigger>
 
                        <!– Mouse Over –>
                        <Trigger Property=IsMouseOver Value=True>
                            <Setter Property=Fill TargetName=PART_Shape Value={StaticResource HighlightedBackgroundBrush} />
                            <Setter Property=Stroke TargetName=PART_Shape Value={StaticResource HighlightedBorderBrush} />
                            <Setter Property=Fill TargetName=PART_Bubble Value={StaticResource HighlightedBubbleBrush} />
                        </Trigger>
 
                        <!– Pressed –>
                        <Trigger Property=IsPressed Value=True>
                            <Setter Property=Fill TargetName=PART_Shape Value={StaticResource PressedBackgroundBrush} />
                            <Setter Property=Fill TargetName=PART_Bubble Value={StaticResource PressedBubbleBrush}/>
                            <Setter Property=RenderTransform TargetName=PART_Content>
                                <Setter.Value>
                                    <TransformGroup>
                                        <TranslateTransform X=1 Y=1 />
                                    </TransformGroup>
                                </Setter.Value>
                            </Setter>
                        </Trigger>
 
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
 
</ResourceDictionary>



 


[flat.theme.xaml]


<ResourceDictionary

    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml>
 
    <!– Default Color –>
    <SolidColorBrush    x:Key=DefaultBackgroundBrush    Color=#FF1F2A43/>
    <SolidColorBrush    x:Key=DefaultBorderBrush            Color=#FF265184/>
    <SolidColorBrush    x:Key=DefaultForegroundBrush    Color=#FFBFD1FF/>
 
    <LinearGradientBrush x:Key=DefaultBubbleBrush EndPoint=0.545,2.847 StartPoint=0.545,-0.639>
        <GradientStop Color=#FFCBCEFF Offset=0.115/>
        <GradientStop Color=#FF2A3756 Offset=0.476/>
    </LinearGradientBrush>
 
 
    <!– Focus Color –>
    <SolidColorBrush    x:Key=FocusedBorderBrush Color=#FF7482E5 />
    <LinearGradientBrush x:Key=FocusedBubbleBrush EndPoint=0.545,2.847 StartPoint=0.545,-0.639>
        <GradientStop Color=#FFCBCEFF Offset=0.115/>
        <GradientStop Color=#FF18183F Offset=0.731/>
    </LinearGradientBrush>
 
 
    <!– Highlight Color –>
    <SolidColorBrush x:Key=HighlightedBorderBrush Color=#FF5F6CC8/>
 
    <LinearGradientBrush x:Key=HighlightedBackgroundBrush EndPoint=0.505,2.832 StartPoint=0.505,-0.654>
        <GradientStop Color=#FF2C3B5C Offset=0/>
        <GradientStop Color=#FF1F2A43 Offset=1/>
    </LinearGradientBrush>
 
    <LinearGradientBrush x:Key=HighlightedBubbleBrush EndPoint=0.545,2.847 StartPoint=0.545,-0.639>
        <GradientStop Color=#FFCBCEFF Offset=0.052/>
        <GradientStop Color=#FF2C3B5C Offset=0.457/>
    </LinearGradientBrush>
 
    <SolidColorBrush  x:Key=DefaultControlBrightBrush Color=#FF293D66/>
 
 
    <!– Press Color –>
    <LinearGradientBrush x:Key=PressedBackgroundBrush EndPoint=0.518,0.948 StartPoint=0.514,0.104>
        <GradientStop Color=#FF14213F Offset=0/>
        <GradientStop Color=#FF36476E Offset=1/>
        <GradientStop Color=#FF0D0F1C Offset=0.423/>
    </LinearGradientBrush>
    <LinearGradientBrush x:Key=PressedBubbleBrush EndPoint=0.545,2.847 StartPoint=0.545,-0.639>
        <GradientStop Color=#FFCBCEFF Offset=0/>
        <GradientStop Color=#FF1A1E3B Offset=0.212/>
    </LinearGradientBrush>
 
    <!– Button Template –>
    <Style BasedOn={x:Null} TargetType={x:Type Button}>
        <Setter Property=BorderBrush Value={StaticResource DefaultBorderBrush}/>
        <Setter Property=Background Value={StaticResource DefaultBackgroundBrush}/>
        <Setter Property=Template>
            <Setter.Value>
                <ControlTemplate TargetType={x:Type Button}>
                    <Grid>
 
                        <Rectangle x:Name=PART_Shape
              RadiusX=4
              RadiusY=4
              Fill={TemplateBinding Background}
              Stroke={TemplateBinding BorderBrush}
              StrokeThickness=1
                        />
 
                        <ContentPresenter x:Name=PART_Content
               SnapsToDevicePixels={TemplateBinding SnapsToDevicePixels}
               HorizontalAlignment={TemplateBinding HorizontalContentAlignment}
               VerticalAlignment={TemplateBinding VerticalContentAlignment}
               RecognizesAccessKey=True
               Margin=8,4,8,4
                        />
 
                    </Grid>
 
                    <ControlTemplate.Triggers>
 
                        <!– Enabled –>
                        <Trigger Property=IsEnabled Value=True>
                            <Setter Property=Foreground Value={StaticResource DefaultForegroundBrush}/>
                        </Trigger>
 
                        <!– Focused –>
                        <Trigger Property=IsFocused Value=True>
                            <Setter Property=Stroke TargetName=PART_Shape Value={StaticResource FocusedBorderBrush}/>
                        </Trigger>
 
                        <!– Mouse Over –>
                        <Trigger Property=IsMouseOver Value=True>
                            <Setter Property=Fill TargetName=PART_Shape Value={StaticResource HighlightedBackgroundBrush} />
                            <Setter Property=Stroke TargetName=PART_Shape Value={StaticResource HighlightedBorderBrush}/>
                            <Setter Property=StrokeThickness TargetName=PART_Shape Value=2/>
                        </Trigger>
 
                        <!– Pressed –>
                        <Trigger Property=IsPressed Value=True>
                            <Setter Property=Fill TargetName=PART_Shape Value={StaticResource PressedBackgroundBrush} />
                            <Setter Property=RenderTransform TargetName=PART_Content>
                                <Setter.Value>
                                    <TransformGroup>
                                        <TranslateTransform X=1 Y=1 />
                                    </TransformGroup>
                                </Setter.Value>
                            </Setter>
                        </Trigger>
 
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
 
</ResourceDictionary>



 


If you find a bug, or an error, I will be glad to hear about.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

8 comments

  1. RanMay 4, 2007 ב 22:20

    Nice post, next time add a source code, it will halp.

    Reply
  2. cacompguySeptember 26, 2007 ב 00:42

    Hi,
    Nice post. I am using Infragistics control. They do have “Theme” property. How do I style standard controls like button to match diplayed theme dynamically?

    Thank you

    Reply
  3. Tomer ShamamSeptember 28, 2007 ב 13:53

    Hi cacompguy,

    The Infragistics control Theme property is not part of the WPF, also I do not think that the Infragistics Theme property concept overlapped with my post.
    You should refer the Infragistics SDK documentation.

    Reply
  4. Kris AdamsAugust 30, 2010 ב 13:53

    Hi there,

    Any idea of a fix for the issue where loading themes dynamically in Windows XP with .Net 3.5 SP1 causes the app to crash (on the Resources.MergedDictionaries.Add method)?

    It’s very frustrating, and not a well publicized error. So hard to find an answer to.

    Reply
  5. Ilya TsilikovJuly 29, 2011 ב 17:16

    by the way i deed the same thing but I have a problem: the controls that are already loaded doesn’t apply themes to them. Have you met such problem?

    Reply
  6. kamlendraApril 19, 2012 ב 10:25

    can i update or change values of your
    ResourceDictionary theme = object
    like i have a resource dictionary of key value pairs, and bind it to a list view and Displaying the values.

    ...
    now i give flexibility to user to change the value. it is not updating back in the “theme” object.

    Reply
  7. HarshJuly 2, 2012 ב 16:11

    Hi, My requirement is when user change some value resource xaml file then application should pick new theme value withoud rebuilding this.

    Reply
  8. Dave RoseNovember 26, 2012 ב 14:17

    Be careful when changing themes that have different control templates for the same control and you have other styles based off of them.

    I had to move some styles that were the same for all themes and put them in a my custom controls and reload it as a 2nd resource directory after each theme change. I would just clear the resource directory and reload inside a begin init.

    Reply