简体   繁体   中英

Can't share ResourceDictionary between different projects

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.


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 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.

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.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM