You have to use MergedDictionaries
and use the pack URI scheme to fully qualify the merged resource.
"I have several Windows application projects that all have the same copy-pasted ResourceDictionary in their app.xaml file."
Usually you create a single WPF APP project and set it as the startup project. Every additional projects are of type library. This means they don't contain an application or framework entry point, which is a class that derives from Application
, usually the partial class App
defined in App.xaml and App.xaml.cs. Visual Studio offers a project template for control libraries like WPF CustomControl Library or WPF User Control Library.
A WPF application contains only one active App.xaml file. If you need to reference resources in an assembly other than the startup assembly, you import them by defining a MergedDictionaries
in the relevant resource files.
App.xaml
<Application.Resources>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/SomeProject;component/SomePath/First.xaml" />
<ResourceDictionary Source="pack://application:,,,/SomeProject;component/SomePath/Second.xaml" />
<ResourceDictionary Source="pack://application:,,,/SomeProject;component/SomePath/Third.xaml" />
...
</ResourceDictionary.MergedDictionaries>
</Application.Resources>
It is recommended to move all relevant and shared resources to the App.xaml dictionary if possible. This eliminates the need to define MergedDictionaries
outside of App.xaml, which can improve performance.
Also make sure the order of the merged ResourceDictionary
items inside the MergedDictionaries
collection are added in the right order.
Problem
Note that the XAML parser follows certain lookup rules. Also StaticResource
lookup doesn't support forward declaration: all referenced resources must be defined before the declaration of the actual reference.
Especially when dealing with MergedDictionaries
the order of declaration is very important.
In short the static resource lookup starts locally with the ResourceDictionary
of the current element. If the resource key was not found in its scope, the XAML parser traverses up the logical tree to check the dictionaries of the logical parents, until it reaches the root element e.g. Window
. After the root element the parser checks the application's resource dictionary and then the theme dictionary.
If a the parser encounters a MergedDictionaries
(after checking the current ResourceDictionary
first), it iterates the merged ResourceDictionary
collection in reverse order from bottom to top or from last to first.
Since there is no forward declaration supported by the XAML parser, the order of the merged resources is very important.
Take the following MergedDictionaries
collection:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/SomePath/First.xaml" />
<ResourceDictionary Source="/SomePath/Second.xaml" />
<ResourceDictionary Source="/SomePath/Third.xaml" />
</ResourceDictionary.MergedDictionaries>
Now consider the following situation: you have an element e.g. a Button
that statically references a ControlTemplate
, which is defined in a parent element's dictionary inside the merged dictionary of Third.xaml. But this template also contains an element, that statically references a Style
defined in First.xaml.
If elements or resources declared in Third.xaml would need to statically reference resource from First.xaml, then the parser couldn't not resolve those resources: parser searches for the ControlTemplate
and reaches the parent's ResourceDictionary
. This dictionary doesn't contain the reference, but a MergedDictioanaries
collection. So it starts to iterate over this collection in reverse order, from last to first or from bottom to top: it starts with Third.xaml and successfully finds the referenced ControlTemplate
.
In order to instantiate this template, the parser must resolve all template resources. Inside this template the parser finds an element that needs a Style
, but this Style
was not found in any previous merged ResourceDictionary
. It is defined in the ResourceDictionary
of First.xaml, which has not been visited yet (forward declaration). Therefore this resource cannot be resolved.
Solution
To fix this, you can either put the merged dictionaries into the right order:
<!-- Collection is iterated in reverse order -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/SomePath/Third.xaml" />
<ResourceDictionary Source="/SomePath/Second.xaml" />
<ResourceDictionary Source="/SomePath/First.xaml" />
</ResourceDictionary.MergedDictionaries>
Or replace static references with dynamic references by using the DynamicResource
markup.
The DynamicResource
markup instructs the XAML parser to create a temporary expression during the first lookup pass (this first lookup pass is the one described before and resolves static references at compile time). After this first pass, a second lookup occurs at runtime. The parser again traverses the tree to execute the temporary expressions previously created by the DynamicResource
markup during the first lookup pass.
So whenever you can't provide a definition of a resource before its declaration, you have to use DynamicResource
lookup.