首页 > 解决方案 > 无法在不同项目之间共享 ResourceDictionary

问题描述

ResourceDictionary我有几个 Windows 应用程序项目,它们的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>

所以我把它全部放在一个名为 Resources.xaml 的文件中,在一个名为Common(为了示例的缘故)的项目中,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 构建选项从“页面”更改为“资源”,但它没有改变任何东西。我还查看了这个问题,似乎我必须更改所有对 的StaticResource引用DynamicResources,这对我来说不是一个真正可行的解决方案。

如何防止异常?有没有其他方法可以防止这种代码重复?

标签: c#.netwpfresourcedictionary

解决方案


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

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

通常你创建一个单独的WPF APP项目并将其设置为启动项目。每个额外的项目都是类型库。这意味着它们不包含应用程序或框架入口点,这是一个派生自 的类,通常是在App.xamlApp.xaml.cs中定义Application的部分类。Visual Studio 为WPF CustomControl LibraryWPF User Control Library等控件库提供项目模板。 WPF 应用程序仅包含一个活动的App.xaml文件。如果您需要引用启动程序集以外的程序集中的资源,您可以通过在相关资源文件中定义 a 来导入它们。App
MergedDictionaries

应用程序.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.xamlMergedDictionaries之外定义的需要,这可以提高性能。

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


问题

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

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

如果解析器遇到 a MergedDictionaries(在检查当前first 之后),它会以相反的顺序从底部到顶部或从 last 到 firstResourceDictionary迭代合并的ResourceDictionary集合。

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

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

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

如果在Third.xaml中声明的元素或资源需要静态引用来自First.xaml的资源,则解析器无法解析这些资源:解析器搜索ControlTemplate并到达父级的ResourceDictionary. 这本字典不包含参考,而是一个MergedDictioanaries集合。所以它开始以相反的顺序迭代这个集合,从最后到第一个或从下到上:它从Third.xaml开始并成功找到引用的ControlTemplate.

为了实例化这个模板,解析器必须解析所有模板资源。在这个模板中,解析器找到了一个需要 a 的元素Style,但Style在任何以前的合并中都没有找到ResourceDictionary。它在 First.xaml 中定义,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在第一次查找过程中由标记创建的临时表达式。

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


推荐阅读