无法在不同项目之间共享ResourceDictionary

问题描述

我有几个Windows应用程序项目,它们的ResourceDictionary文件中都具有相同的复制粘贴app.xaml。我要删除代码重复项,将ResourceDictionary放在所有文件都引用的项目中的一个文件中,然后使用ResourceDictionary.source参数对其进行引用。

当前,每个项目的app.xaml文件中都包含以下内容

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

因此,我将所有内容放在一个名为Common的项目中的Resources.xaml文件中(出于示例的考虑),在app.xaml中,将代码更改为:

<Application.Resources>
    <ResourceDictionary Source="pack://application:,/Common;component/Resources.xaml"/>
</Application.Resources>

当我单击文件名上的F12时,它会将我定向到预期的Resources.xaml文件,但是当我启动该应用程序时,出现异常:

System.Windows.Markup.XamlParseException: “ {DependencyProperty.UnsetValue}”不是属性的有效值 “背景”。

内部异常:InvalidOperationException: “ {DependencyProperty.UnsetValue}”不是属性的有效值 “背景”。

我将Resources.xaml构建选项从“页面”更改为“资源”,但没有任何改变。 我还查看了this question,似乎必须将所有StaticResource的引用更改为DynamicResources,这对我来说并不是一个切实可行的解决方案。

如何防止该异常?还有其他方法可以防止此代码重复吗?

解决方法

您必须使用MergedDictionaries并使用pack URI方案来完全限定合并的资源。

“我有几个Windows应用程序项目,它们的app.xaml文件中都具有相同的粘贴粘贴的ResourceDictionary。”

通常,您创建一个 WPF APP 项目并将其设置为启动项目。每个其他项目都是库类型。这意味着它们不包含应用程序或框架入口点,该入口点是从Application派生的类,通常是 App.xaml 和App em> App.xaml.cs 。 Visual Studio为控件库提供了一个项目模板,如 WPF CustomControl库 WPF用户控件库
WPF应用程序仅包含一个活动的 App.xaml 文件。如果需要在启动程序集以外的程序集中引用资源,则可以通过在相关资源文件中定义MergedDictionaries来导入它们。

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>

如果可能的话,建议将所有相关的共享资源移至 App.xaml 词典。这样就无需在 App.xaml 之外定义MergedDictionaries,从而可以提高性能。

还要确保ResourceDictionary集合内合并的MergedDictionaries项目的顺序是正确添加的。


问题

请注意,XAML解析器遵循某些查找规则。另外,StaticResource查找不支持前向声明:必须在实际引用的声明之前 定义所有引用的资源。
特别是在处理MergedDictionaries时,声明的顺序非常重要。

简而言之,静态资源查找从当前元素的ResourceDictionary开始。如果在其范围内未找到资源密钥,则XAML解析器将遍历逻辑树以检查逻辑父级的字典,直到到达根元素(例如Window。在根元素之后,解析器先检查应用程序的资源字典,然后再检查主题字典。

如果解析器遇到MergedDictionaries(首先检查了当前ResourceDictionary之后),则会从下到上或从上到下以相反的顺序迭代合并的ResourceDictionary集合首先

由于XAML解析器不支持任何前向声明,因此合并资源的顺序非常重要。
采取以下MergedDictionaries集合:

<ResourceDictionary.MergedDictionaries>
  <ResourceDictionary Source="/SomePath/First.xaml" />
  <ResourceDictionary Source="/SomePath/Second.xaml" />
  <ResourceDictionary Source="/SomePath/Third.xaml" />
</ResourceDictionary.MergedDictionaries>

现在考虑以下情况:您有一个元素,例如静态引用Button的{​​{1}},它是在 Third.xaml 合并字典内的父元素字典中定义的。但是此模板还包含一个元素,该元素静态引用在 First.xaml 中定义的ControlTemplate

如果在 Third.xaml 中声明的元素或资源需要静态引用 First.xaml 中的资源,则解析器无法解析这些资源:解析器搜索Style并到达父级的ControlTemplate。该词典不包含引用,但包含ResourceDictionary集合。因此,它开始从上到下或从下到上以相反的顺序遍历此集合:它以 Third.xaml 开头,并成功找到了引用的MergedDictioanaries

为了实例化此模板,解析器必须解析所有模板资源。解析器在此模板内找到需要一个ControlTemplate的元素,但是在任何先前合并的Style中都找不到此Style。它是在 First.xaml ResourceDictionary中定义的,尚未被访问(转发声明)。因此,无法解决此资源。

解决方案

要解决此问题,您可以将合并的字典置于正确的顺序:

ResourceDictionary

或通过使用<!-- 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> 标记将静态引用替换为动态引用。

DynamicResource标记指示XAML解析器在第一次查找过程中创建一个临时表达式(该第一次查找过程是前面所述的过程,并在编译时解析静态引用)。第一次通过之后,将在运行时进行第二次查找。解析器再次遍历该树以执行先前在第一次查找过程中由DynamicResource标记创建的临时表达式。

因此,每当您无法在声明之前 提供资源的定义时,都必须使用DynamicResource查找。