Some time ago I had to do a Theming infrastructure for the application in my company. Our application was pretty big with hundreds or maybe thousands of resources in XAML. Structuring big XAML resource hierarchies with Merged Dictionaries turned to be a big pain. I had to learn all of WPF’s problems the hard way. Don’t repeat my mistakes, read this post carefully before restructuring your XAML resource files.
This article will show how to:
- Use Merged Dictionaries in WPF to have application-wide resources that you can later use as a StaticResource or DynamicResource.
- Correctly use nested Merged Dictionaries for better file separation
- How to create Theme-specific resource files
- Go over the special Generic.xaml file, how to use it for custom controls and how to combine it with other application-wide resources.
Using MergedDictionaries in App.xaml
In a big Application, we’ll want to organize our WPF resources well. So let’s say we’ll have a Resource Dictionary file for Font sizes
MyFontResources.xaml:
<ResourceDictionary xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:local="clr-namespace:MergedDictionaryProblems"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:Double x:Key="FontSize.Medium">26</sys:Double>
</ResourceDictionary>
Now I’d like all my TextBlocks to have this font size. In App.xaml:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MyFontResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="TextBlock" x:Key="MyTextBlockStyle">
<Setter Property="FontSize"
Value="{StaticResource FontSize.Medium}"/>
</Style>
</ResourceDictionary>
</Application.Resources>
And a little test – Show the TextBlock. In MainWindow.xaml:
<TextBlock Text="WPF Merged Dictionary experiment" Style="{StaticResource MyTextBlockStyle}"/>
The result is:
Very good.
Now I’d like a new file to hold all my TextBlock styles. Let’s call it… TextBlockStyles.xaml. And it will look like this:
<Style TargetType="TextBlock" x:Key="MyTextBlockStyle">
<Setter Property="FontSize" Value="{StaticResource FontSize.Medium}"/>
</Style>
My App.xaml now looks like this:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MyFontResources.xaml"/>
<ResourceDictionary Source="TextBlockStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
And the result is still:
So far so good.
We see that in App.xaml when placing one merged dictionary below another, the XAML file in the 2nd dictionary can use the resources defined in the 1st one.
And order does matter here.
Using MergedDictionaries in a file that’s not App.xaml
Let’s say I want another hierarchy level: Themes.
I want a ThemeBlue.xaml and ThemeRed.xaml, both of which are independent from one another. Let’s do that.
I create a new file ThemeBlue.xaml with:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MyFontResources.xaml"/>
<ResourceDictionary Source="TextBlockStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
In App.xaml:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ThemeBlue.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
And the result is:
Wait… that’s not right.
This brings us to a problem – When using merged dictionaries in a resource dictionary that’s not App.xaml, you cannot use Resources as StaticResources from a merged dictionary defined before your own dictionary.
Two solutions come to mind:
- Define any Merged Dictionary directly in App.xaml. Why do I need those additional hierarchy levels anyway?
- Use DynamicResource always
Both of these solutions are problematic, but we’ll talk about them later. First, let’s consider another scenario.
Custom controls and Generic.xaml
In my huge applications with hundreds of screens, I might have lots and lots of Custom Controls.
A custom control has a default style defined in a special file called Generic.xaml. Check out my article on Default styles in XAML
Let’s add a new custom button called “CustomButton”
public class CustomButton : Button
{
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
}
In Generic.xaml:
<Style TargetType="{x:Type local:CustomButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomButton}">
<Border Background="Beige">
<TextBlock Text="Click here" FontSize="{StaticResource FontSize.Medium}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In App.xaml:
<Application.Resources>
<sys:Double x:Key="FontSize.Medium">26</sys:Double>
Running this will actually crash the program
The reason is the StaticResource we used FontSize=”{StaticResource FontSize.Medium}”.
Changing it to DynamicResource will work.
Placing the FontSize resources inside a merged dictionary in Generic.xaml will solve the problem only if the Style is in Generic.xaml and not in a merged dictionary.
For Generic.xaml, you can use StaticResource for resources defined in Generic.xaml. Otherwise use DynamicReousrce.
Regroup and count
- When using Merged dictionaries in a resource dictionary that’s not App.xaml (2nd+ hierarchy level), the resources defined in that dictionary cannot be used as StaticResource in another dictionary defined below it (in a line after it)
When merged dictionary is used in App.xaml it works well. - In Generic.xaml, you cannot use resources defined in App.xaml as StaticResource. Only as DynamicResource.
- In Generic.xaml, you can use resources as StaticResource if defined in Generic.xaml or in a resource dictionary in Generic.xaml ‘s MergedDictionaries. As long as your default style is in Generic.xaml itself and not in a merged dictionary.
If you define a default style in a merged dictionary out of Generic.xaml, the resources defined in App.xaml will work with DynamicResource, but you won’t be able to use resources defined in Generic.xaml or a merged dictionary out of Generic.xaml
Solutions
Let’s check out our options.
In all options except #4, for Default styles, you’ll have to have a very long Generic.xaml file or use Implicit styles.
-
Keep all our resources in a single very long file. You can even use regions inside your XAML file with this extension .
This long file can be Generic.xaml but we will have to load Generic.xaml as a merged dictionary in App.xaml. -
Define all merged dictionaries in a single hierarchy level in App.xaml.
This is the actual solution I used in my application. In hindsight, I’d go with solution #4. -
Use whatever hierarchy you want for your resources and use DynamicResource everywhere
-
Create whatever XAML hierarchy you want, and place your files in a folder with .txaml extensions.
A small simple program (provided below in GitHub) will run as a pre-build event and merge your .txaml files into one long .XAML fileThis allows to structure resources folders and files however you want, without WPF’s limitations. StaticResource and the designer will work always.
This is the only solution where you can have CustomControl styles in multiple files, not just one long Generic.xaml.
You might see by now I think solution #4 is the best.
I think it solves all our problems really. Well, you still have to use DynamicResource for your Generic.xaml default styles, but other than that it’s pretty perfect.
I created the merging tool and an example solution, so you can see this in action. Feel free to use this in your applications.
I’m using 2 folders for my resources and running the merge tool twice.
One folder is merged into AppResources.xaml and another folder is merged into Generic.xaml.
In each folder we can have as many files and as many sub-folders we want. Just remember to order them correctly if the resources rely on each other.
If you look at “Controls” project, you’ll see the pre-build event that does the merging.
If themes we can create a folder for each theme and use the merger to merge each theme into a single XAML file. Once you have one file per theme, and possibly a file for shared resources, you can load them easily per customer configuration.
DynamicResource issues
DynamicResource is a valid solution… with a price 🙂
- DynamicResource has a performance problem. It’s heavier than StaticResource. The performance difference however will most likely be unnoticeable (See Benchmark )
- You can’t use DynamicResource as a parameter in your converters. The example below will crash the program if used with DynamicResource
Converter={converters:BooleanToObjectConverter TrueValue='{StaticResourceMainWindow.Right.Half}',FalseValue='{StaticResource Thickness.MainWindow.LeftAndRight.Half}'},Mode=OneWay}"
3. I had very difficult bugs with DynamicResource regarding unfrozen resources. Long story short, when using DynamicResource, add po:Freeze on all Freezables (like Brushes)
xmlns:po=http://schemas.microsoft.com/winfx/2006/xaml/presentation/options
…
<SolidColorBrush po:Freeze="True" x:Key="ProgressBarBackground" Color="#ffedd5d5" />
4. When extending a Style with BasedOn, WPF doesn’t allow the base style to be a DynamicResource.
<Style BasedOn="{DynamicResource basestyle}"> <!-- This is illegal -->
...
More Sources
I talk here about application scope defined resources. Not specific window or Control resources.
These have other problems I didn’t go into.
Here are some good posts about this issue if you just can’t get enough of Merged Dictionaries 🙂
- http://www.wpftutorial.net/MergedDictionaryPerformance.html
- http://stackoverflow.com/questions/6693320/mergeddictionaries-and-resource-lookup.
This post is interesting. I personally have over a hundred resource dictionary files in my application and everything works well, but this guy sounds convincing. - If you’re planning to implement Themes / Skins, check out WPF complete guide to Themes and Skins