Merging WPFThemes with your own styles

Last few months I’ve been working on a WPF and as my design skills are close to zero I’ve decided to the the app a bit of sexiness by using the WPF goodness of Themes. Best place to start for that are the WPFThemes on CodePlex.

Sample from one of the themes:

  1. <!– Button –>
  2. <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
  3.     <Setter Property="IsEnabled" Value="true"/>
  4.     <Setter Property="IsTabStop" Value="true"/>

Applying the standard themes to your project is pretty straight forward if you want to use 100% of the theme:

  1. public partial class ApplicationInstance : Application
  2.     {
  3.         protected override void OnStartup(StartupEventArgs e)
  4.         {
  5.             ThemeManager.ApplyTheme(this, "ShinyDarkTeal");
  6.             […]
  7.         }

This works a treat however because the default styles are all keyed on the types there is no way for you to “customize” the styles in the themes and add new properties to them without modifying the original themes (which would create a maintenance nightmare and no chance of you ever merging with new version of WPFThemes) or use named styles which would imply you have to define the style of each element in all your code.

Option 1: Modify original theme:

  1. <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
  2.     <Setter Property="IsEnabled" Value="true"/>
  3.     <Setter Property="Height" Value="50" /> <!– my custom property –>
  4.     <Setter Property="IsTabStop" Value="true"/>

Proper maintenance nightmare.

Option 2: Create named style:

  1. <Style x:Key="MyButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
  2.     <Setter Property="Height" Value="50" />
  3.     <Setter Property="MinWidth" Value="90" />
  4. </Style>

Ugly as you need to use named keys through your application.

Option 3: Override it as window level not application level

  1. <Window.Resources>
  2.         <Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
  3.             <Setter Property="Height" Value="50" />
  4.         </Style>

Ugly as you have to maintain it for each window.

Note: You can’t use option 3 at application level as the BasedOn will be applied based on the standard style not the themed one.

None of the two options sounded like a good option so when all I wanted to do is be able to set default Width or Height of my buttons:

  1. <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
  2.     <Setter Property="Height" Value="50" />
  3.     <Setter Property="MinWidth" Value="90" />
  4. </Style>

Wouldn’t it be nice if we could no “use the theme as it’ given to us” but “merge the theme” into our code as have it as the base of what we want to build on top of it.

Merging themes and overwriting via target inheritance

The only other option that I found was to do a custom load of the theme file as named keys and not as types even if they are defined as types and force my custom styles to inherit from the theme styles.

First the code:

CustomStyles.xaml – my custom (master) styles

  1. <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
  2.     <Setter Property="Height" Value="50" />
  3.     <Setter Property="MinWidth" Value="90" />
  4. </Style>

x:Key is x:Type Button so I want this style to get applied to all my buttons.

Theme.xaml – default theme from WPFThemes

  1. <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
  2.     <Setter Property="IsEnabled" Value="true"/>
  3.     <Setter Property="IsTabStop" Value="true"/>

These are the styles from the themes. We don’t want to modify them but we would like to merge them with our Custom Styles.

Merge code:

  1. private void MergeThemeFile()
  2. {
  3.     ResourceDictionary themeDictionary = ThemeManager.GetThemeResourceDictionary("BlueGlossyControls");
  4.     
  5.     string customStylesXaml = @"/myassembly;component/Resources/Styles/CustomStyles.xaml";
  6.     ResourceDictionary customStyles = Application.LoadComponent(new Uri(customStylesXaml, UriKind.Relative)) as ResourceDictionary;
  7.     
  8.     // Force remove of the custom styles dictionary so it does not get loaded
  9.     DropDictionary(customStylesXaml);
  10.  
  11.     ResourceDictionary finalDictionary = new ResourceDictionary();
  12.  
  13.     // we have two dictionaries, try to make the customStyles be the master by inheriting them from the theme
  14.     // update this, drop the xType if we already have some types and merge
  15.     foreach(DictionaryEntry resourceEntry in themeDictionary)
  16.     {
  17.         if (resourceEntry.Value is Style && resourceEntry.Key is Type)
  18.         {
  19.             Type themeKeyType = resourceEntry.Key as Type;
  20.             Style themeStyle = resourceEntry.Value as Style;
  21.             
  22.             // resource for a specific type, try to merge with customStyles
  23.             var localStyle = customStyles[resourceEntry.Key] as Style;
  24.             if (localStyle != null)
  25.             {
  26.                 // make local style inherit theme style
  27.                 string themeKey = "Theme-" + themeKeyType.FullName;
  28.                 finalDictionary[themeKey] = themeStyle;    // save the theme as "Theme-[FullName]"
  29.                 localStyle.BasedOn = themeStyle;    // make local style based on the Theme one
  30.                 finalDictionary[themeKeyType] = localStyle;
  31.                 continue;
  32.             }
  33.         }
  34.         finalDictionary.Add(resourceEntry.Key, resourceEntry.Value);
  35.     }
  36.     foreach (DictionaryEntry resourceEntry in customStyles)
  37.     {
  38.         if ( !finalDictionary.Contains(resourceEntry.Key))
  39.         {
  40.             finalDictionary[resourceEntry.Key] = resourceEntry.Value;
  41.         }
  42.     }
  43.     this.Resources.MergedDictionaries.Add(finalDictionary);
  44. }
  45. private void DropDictionary(string name)
  46. {
  47.     foreach (var resource in this.Resources.MergedDictionaries)
  48.     {
  49.         if (resource.Source.ToString() == name)
  50.         {
  51.             this.Resources.MergedDictionaries.Remove(resource);
  52.             break;
  53.         }
  54.     }
  55. }

This code will try to load the theme file and see if we have any keys as types that we override in the CustomStyles.xaml.

If we have a “conflict” we change the x:Key of the theme style to a named key and we change the BasedOn of the custom style to be based on the theme style. If there is no conflict we load the old theme.

This merges the two files like this:

  1. <!– Original Style From Theme: x:Key="{x:Type Button}" –>
  2. <Style x:Key="ThemeGlassyControls" TargetType="{x:Type Button}">
  3.     <Setter Property="IsEnabled" Value="true"/>
  4.     <Setter Property="IsTabStop" Value="true"/>
  5. </Style>
  6. <!– Original Style from CustomStyles based on the new Theme style–>
  7. <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}" BasedOn="ThemeGlassyControls">
  8.     <Setter Property="Height" Value="50" />
  9.     <Setter Property="MinWidth" Value="90" />
  10. </Style>

We then load the new combined style.

This get the best of the two words out there combined. We can apply a theme while also overwriting/merging parts of it.

Merged styles: WPFThemes Gradients, My Width/Height

Now the next challenge is to be able to override part of the content. I’d like my buttons to have two lines of text. One with the text and one with the shortcut defined via a dependency property. I’d like to keep the theme contents of the button and be able to add to that in a very simply way.

This entry was posted in .Net, WPF and tagged . Bookmark the permalink.

Leave a comment