0

I'm working on conditionally loading a ResourceDictionary basing on which theme is currently selected. Say, that I have the following:

[Theme-Dark.xaml]

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <!-- Colors --> <Color x:Key="Dark1Color">#1b2936</Color> <!-- ... --> <!-- Brushes --> <SolidColorBrush x:Key="TextboxNormalBackgroundBrush" Color="{StaticResource Dark1Color}" /> <!-- ... --> </ResourceDictionary> 

[Styles.xaml]

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <!-- Styles --> <Style TargetType="TextBox"> <Setter Property="Background" Value="{StaticResource TextboxNormalBackgroundBrush}" /> </Style> </ResourceDictionary> 

If I add Theme-Dark.xaml to Styles.xaml's MergedDictionaries directly in xaml, everything works properly (so this is not a case of typos etc.). However, I would like to be able to load theme file with colors conditionally, basing on current application's theme. So I wrote the following code:

ResourceDictionary themeDictionary = new ResourceDictionary(); themeDictionary.Source = new Uri("/MyApp;component/Wpf/Theme-Dark.xaml", UriKind.RelativeOrAbsolute); ResourceDictionary styleDictionary = new ResourceDictionary(); styleDictionary.Source = new Uri("/MyApp;component/Wpf/Styles.xaml", UriKind.RelativeOrAbsolute); styleDictionary.MergedDictionaries.Add(themeDictionary); _content.Resources.MergedDictionaries.Add(styleDictionary); ehWpfContent.Child = _content; 

Upon opening the form containing ElementHost, which contains the control I want to style, I'm getting an exception:

System.Windows.Markup.XamlParseException: 'Provide value on 'System.Windows.Markup.StaticResourceHolder' threw an exception.' Line number '10' and line position '55'. ---> System.Exception: Cannot find resource named 'TextboxNormalBackgroundBrush'. Resource names are case sensitive. at System.Windows.StaticResourceExtension.ProvideValueInternal(IServiceProvider serviceProvider, Boolean allowDeferredReference) at MS.Internal.Xaml.Runtime.ClrObjectRuntime.CallProvideValue(MarkupExtension me, IServiceProvider serviceProvider) 

How can I properly add ResourceDictionary to MergedDictionaries, so that those resources will be recognized?

0

2 Answers 2

1

There are two different kinds of markup extensions for retrieving resources.

A resource can be referenced as either static or dynamic. References are created by using either the StaticResource Markup Extension or the DynamicResource Markup Extension. A markup extension is a XAML feature that lets you specify an object reference by having the markup extension process the attribute string and return the object to a XAML loader.

The StaticResource markup extension as its name might imply has a load-time lookup behavior.

Lookup behavior for that resource is analogous to load-time lookup, which will look for resources that were previously loaded from the markup of the current XAML page as well as other application sources, and will generate that resource value as the property value in the run-time objects.

Processing happens during load, which is when the loading process needs to assign the property value.

In your case the TextboxNormalBackgroundBrush is used in the Styles.xaml dictionary.

If I add Theme-Dark.xaml to Styles.xaml's MergedDictionaries directly in xaml, everything works properly (so this is not a case of typos etc.).

In this case, the resources (including the brush) are instantiated and included, before the controls and resources in the Styles.xaml dictionary, e.g. TextBox, are created and loaded. This means, the brush is available when StaticResource is being resolved.

On the opposite, in your runtime resource dictionary switching code, the Styles.xaml dictionary is instantiated (and its contained resources, too), but at that point the Theme-Dark.xaml resource dictionary is not merged anywhere, therefore the StaticResource cannot resolve any of its resources. When Theme-Dark.xaml is merged, the TextBox in Styles.xaml is already created and loaded or the parser threw an exception before reaching this line.

In order to solve this problem, use the DynamicResource markup extension instead.

DynamicResource Markup Extension instead processes a key by creating an expression, and that expression remains unevaluated until the app runs, at which time the expression is evaluated to provide a value.

This enables refering to resource that may be available later in time or are exchanged at runtime.

<Setter Property="Background" Value="{DynamicResource TextboxNormalBackgroundBrush}" / 
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for a very complete answer. There is one other solution though, which combines performance of StaticResource and ability to reach arbitrary resource: custom markup extension. Since I have required ResourceDictionary, I may simply implement a markup extension, which will ignore regular searches through visual hierarchy/loaded XAMLs and get required resource directly from the proper place.
So I came up with one more solution, even better than one mentioned in previous comment. One may create a new class, ThemeResourceDictionary, which in ctor loads appropriate ResourceDictionary into its MergedResources. Then you may simply merge it in XAML and all resources of theme dictionary will be fully available through regular {StaticResource}!
0

First of all, read thatguy's fantastic answer. It proposes first solution to this problem. I however in the meantime found two more, and it turned out, that they suited me better than using DynamicResource. The reason is that they are as performant as StaticResource, but don't share its shortcomings.

There's a pre-requirement to both solutions: you have to load proper theme resources in a form of ResourceDictionary prior to loading any XAML, which uses it.

This may look like following:

 public static class ThemeResourceRepository { private static readonly Uri darkThemeResourcesUri = new Uri("/MyAssembly;component/Wpf/Theme-Dark.xaml", UriKind.RelativeOrAbsolute); private static readonly Uri lightThemeResourcesUri = new Uri("/MyAssembly;component/Wpf/Theme-Light.xaml", UriKind.RelativeOrAbsolute); private static readonly Lazy<ResourceDictionary> darkThemeResources = new Lazy<ResourceDictionary>(() => LoadResources(darkThemeResourcesUri)); private static readonly Lazy<ResourceDictionary> lightThemeResources = new Lazy<ResourceDictionary>(() => LoadResources(lightThemeResourcesUri)); private static ResourceDictionary LoadResources(Uri uri) { ResourceDictionary result = new ResourceDictionary(); result.Source = uri; return result; } public static ResourceDictionary GetThemeResources() { switch (SomeConfigurationClass.CurrentSkin) { case Skins.Dark: return darkThemeResources.Value; case Skins.Light: return lightThemeResources.Value; default: throw new InvalidEnumArgumentException("Unsupported color theme!"); } } } 

And then:

Solution 1: Write custom MarkupExtension

Implement a simple markup extension, like the following:

public class ThemeResource : MarkupExtension { private readonly object key; public ThemeResource(string key) { this.key = key; } public override object ProvideValue(IServiceProvider serviceProvider) { var dictionary = ThemeResourceRepository.GetThemeResources(); return dictionary[key]; } } 

Then you can use it simply like:

<TextBlock Text="{c:ThemeResource SomeResourceKey}" /> 

Or even simpler:

Solution 2: Derive from ResourceDictionary

Implement custom ResourceDictionary as following:

public class ThemeResourceDictionary : ResourceDictionary { public ThemeResourceDictionary() { MergedDictionaries.Add(ThemeResourceRepository.GetThemeResources()); } } 

Then include it in your XAML:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wpf="clr-namespace:MyAssembly.MyNamespace"> <ResourceDictionary.MergedDictionaries> <wpf:ThemeResourceDictionary /> </ResourceDictionary.MergedDictionaries> <!-- ... --> </ResourceDictionary> 

Important observation is that this ResourceDictionary **will be read properly** during loading of XAML. Therefore, you may safely use {StaticResource}` afterwards and it will just work.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.