I have several Windows application projects that all have the same copy-pasted ResourceDictionary
in their app.xaml
file. I want to remove this code duplication, put a ResourceDictionary
in one file in a project that's referred by all of them and use the ResourceDictionary.Source
parameter to reference to it.
Currently every project has something like this in their app.xaml file:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/SomeProject;component/SomePath/First.xaml"/>
<ResourceDictionary Source="/SomeProject;component/SomePath/Second.xaml"/>
<ResourceDictionary Source="/SomeProject;component/SomePath/Third.xaml"/>
...
</ResourceDictionary.MergedDictionaries>
So I put it all in one file called Resources.xaml in a project called Common
(for the example's sake), and in the app.xaml
I changed the code to:
<Application.Resources>
<ResourceDictionary Source="pack://application:,,,/Common;component/Resources.xaml"/>
</Application.Resources>
When I click F12 on the filename, it directs me to the intended Resources.xaml
file, but when I launch the application I get an exception:
System.Windows.Markup.XamlParseException: ''{DependencyProperty.UnsetValue}' is not a valid value for property 'Background'.'
Inner Exception: InvalidOperationException: '{DependencyProperty.UnsetValue}' is not a valid value for property 'Background'.
I changed Resources.xaml build option to "Resource" from "Page", but it didn't change anything. I also looked at this question , and it seems as if I'll have to change all my StaticResource
references to DynamicResources
, which is not a real viable solution for me.
How Can I prevent the exception? Is there any other way to prevent this code duplication?
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.
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 eg 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 eg 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.
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.