23

我有一个 WPF .net 4.5 应用程序,在合并资源字典时遇到问题。

我有与This SO questionThis Question完全相同的问题,但接受的解决方案对我不起作用。

我在 app.xaml 中声明了一个资源字典,如下所示(为清楚起见进行了简化):

<Application.Resources>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Skin/ResourceLibrary.xaml" />              
            <ResourceDictionary Source="Skin/Brushes/ColorStyles.xaml" />               
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>               
</Application.Resources>

问题: 应用程序可以“看到”在 app.xaml 中列出的 ColorStyles 字典,但如果我将其移动/嵌套在 ResourceLibrary.xaml 中,则应用程序不会“看到”ColorStyles.xaml 并且有关缺少静态资源的错误出现。

这是我创建 ResourceLibrary.xaml 字典(简化)的方法:

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <ResourceDictionary.MergedDictionaries>

        <!--  BRUSHES AND COLORS  -->
        <ResourceDictionary Source="Brushes/ColorStyles.xaml" />

    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

更改原因:我当前的资源字典组织很糟糕,我需要更改它(因为我不止一次创建对象)。我想在“皮肤”文件夹中有一个资源字典,然后是用于组织剩余样式字典的子文件夹,这些字典将全部合并到 ResourceLibrary.xaml 文件中,该文件又将在 app.xaml 中调用。

我尝试了什么: 是的,我确实尝试使用上面链接中的解决方案:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Skin/ResourceLibrary.xaml"/>
        </ResourceDictionary.MergedDictionaries>
        <!-- Dummy Style, anything you won't use goes -->
        <Style TargetType="{x:Type Rectangle}" />
    </ResourceDictionary>
</Application.Resources>

但我在虚拟样式行收到以下错误:

错误 2 属性元素不能位于元素内容的中间。它们必须在内容之前或之后。

由于 lisp 注释,将代码更改为以下代码消除了上面的错误:

<Application.Resources>
    <ResourceDictionary>
        <!--Global View Model Locator-->
        <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />

        <!-- Dummy Style, anything you won't use goes -->
        <Style TargetType="{x:Type Rectangle}" />

        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Skin/ResourceLibrary.xaml"></ResourceDictionary>             
            <ResourceDictionary Source="Skin/Brushes/ColorStyles.xaml" />
            <ResourceDictionary Source="Skin/NamedStyles/AlertStyles.xaml" />

        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>               
</Application.Resources>

但资源库仍未被调用。

我还尝试将所有文​​件路径更改为打包 URI,但这也没有解决问题。

我尝试将 resourceLibrary.xaml 和其他资源字典移动到不同的类库项目中(使用与上述相同的文件夹结构和文件)。然后我使用了以下 URI,但我仍然无法访问在 ResourceLibrary.xaml 文件中声明的资源。

<ResourceDictionary Source="pack://application:,,,/FTC.Style;component/ResourceLibrary.xaml" />

但同样,如果我将每个资源字典添加到 App.Xaml 文件中,使用上面的 UIR 格式,这些资源是可用的。

错误消失了,但我仍然无法使用作为 ResourceLibrary.xaml 文件中合并字典一部分的资源。我倾向于同意 dowhilefor 关于我是否应该使用这种方法的评论,但我想弄清楚这一点,因为这个问题的最常见解决方案(参见本文顶部的链接)不起作用,也许这个解决方案可以帮助别人。

问题: 为什么 ResourceLibrary.xaml 文件被忽略?

4

3 回答 3

28

我对 MergedDictionaries 有一个大问题,我相信你的问题是一样的。我希望我的 ResourceDictionaries 组织得当,这对我来说意味着有单独的 Buttons.xaml、TextBoxes.xaml、Colors.xaml 等。我将它们合并到 Theme.xaml 中,通常所有样式都在一个单独的程序集中(以便我可以轻松切换主题)。我的 ApplicationResources 如下:

<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="/DefaultTheme;component/Theme.xaml" />
    </ResourceDictionary.MergedDictionaries>
    <Style TargetType="{x:Type Ellipse}"/>
  </ResourceDictionary>
</Application.Resources>

并且在主应用程序程序集中定义的 .xamls 中的每个 StaticResource 都可以正常工作,默认样式都可以使用虚拟样式。 不起作用的是 Theme 内 .xamls 之间的 StaticResources。如果我在 Buttons.xaml 中定义一个使用 Colors.xaml 中的 StaticResource 的样式,则会收到有关 StaticResources 和 UnsetValue 的错误。如果我将 Colors.xaml 添加到 Application MergedDictionaries,它将起作用。

解决方案 0

放弃组织。将所有内容放在一个 .xaml 中。我相信这就是通常应该使用 ResourceDictionaries 的方式,因为 MergedDictionaries 存在所有“问题”(对我来说,这将是一场噩梦)。

解决方案 1

将主题内的所有跨 xaml StaticResource 引用更改为 DynamicResource。它可以工作,但要付出代价,因为 DynamicResources 比 StaticResources 更“重”。

解决方案 2

在使用来自另一个 .xaml 的 StaticResources 的每个主题 .xaml 中,将这个另一个 ResourceDictionary 添加到 MergedDictionaries。这意味着 Buttons.xaml、TextBoxes.xaml 和其他将在其 MergedDictionaries 中包含 Colors.xaml。这将导致 Colors ResourceDictionary 以多个副本存储在内存中。为避免这种情况,您可能需要查看SharedResourceDictionary

解决方案 3

通过不同的 ResourceDictionaries 设置,不同的嵌套我想出了一个理论

如果在上面的同一 .xaml 或此 ResourceDictionary 的 MergedDictionaries 中未找到 StaticResource,则会在其他顶级 MergedDictionaries中搜索它。

我宁愿只向 ApplicationResources 添加一个 .xaml,但我通常最终使用两个.xaml 。您不必将 Theme 中的每个 .xaml 添加到 ApplicationResources,只需 - 例如 - Controls.xaml(任何类型的 MergedDictionaries 嵌套,但不允许 Controls.xaml 的字典之间的交叉引用)和 Common.xaml其中包含所有常见的控件资源。如果 Common.xaml 嵌套也是允许的,但没有交叉引用,则不能有单独的 Colors.xaml 和 Brushes.xaml 使用颜色作为静态资源 - 那么您必须将 3 个 .xaml 添加到 Application MergedDictionaries。

现在我总是使用第三种解决方案,但我认为它并不完美,仍然想知道是否有更好的方法。我希望我正确地解释了您所描述的与我相同的问题。

于 2013-06-13T09:14:25.230 回答
1

我不得不将主题引入我们的应用程序并面临这些确切的问题。

简短的回答是: 如果资源字典在 App.xaml 中位于它们之前,则它们可以“查看”其他资源字典。如果您尝试在不是 App.xaml 的文件中使用 MergedDictiories,资源字典将不会“看到”彼此。

对于 Generic.xaml 中的默认资源:您可以将 App.xaml 中定义的资源或 App.xaml 中的合并字典仅用作DynamicResource。您可以将 Generic.xaml 中定义的资源用作 StaticResource,但前提是您的样式是在 Generic.xaml 本身中定义的,而不是在 Generic.xaml 内的合并字典中

对于完整的答案,我在我的博客中有一篇关于这个问题的详细帖子

我建议的解决方案: 创建所需的任何 XAML 层次结构,并将文件放在具有.txaml扩展名的文件夹中。我创建了一个简单的小程序(在下面的 GitHub 中提供),它将作为预构建事件运行并将您的 .txaml 文件合并到一个长的 .XAML 文件中。

这允许您根据需要构建资源文件夹和文件,而不受 WPF 的限制。StaticResource 和设计器将始终工作。这是您可以在多个文件中拥有 CustomControl 样式的唯一解决方案,而不仅仅是一个长的 Generic.xaml。

这也将解决多个 XAML 文件创建的任何性能问题。

GitHub中的Xaml合并程序

于 2017-01-01T21:57:32.130 回答
0

除了@lisp answer,我还编写了 tt 模板,它从 Default.xaml 中获取所有文件,找到它们并加入一个文件,我们可以在 app.xaml 中使用它

所以我们可以构建文件,拥有性能,静态资源将工作......

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".xaml" #>

<#
    IDictionary<string, XNamespace> GetNamespaces(XDocument doc)
    {
        return doc.Root.Attributes()
                    .Where(a => a.IsNamespaceDeclaration)
                    .GroupBy(a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName, a=>XNamespace.Get(a.Value))
                    .ToDictionary(g => g.Key, g => g.First());
    }

    XDocument GetFlattenResourceDocument(string path)
    {
        var xFilePath = this.Host.ResolvePath(path);
        var doc = XDocument.Load(xFilePath);

        var defaultNs = doc.Root.GetDefaultNamespace();

        var mergedDictElement = doc.Root.Elements(defaultNs + "ResourceDictionary.MergedDictionaries").SingleOrDefault();
        if (mergedDictElement == null)
            return doc;

        var rootNamespaces = GetNamespaces(doc);

        var mergedResourceDictionaries = mergedDictElement.Elements(defaultNs + "ResourceDictionary");
        var addAfterElement = mergedDictElement as XNode;

        foreach(var resourceDict in mergedResourceDictionaries)
        {
            var sourcePath = resourceDict.Attribute("Source").Value;
            var flattenDoc = GetFlattenResourceDocument(sourcePath);

            var flatNamespaces = GetNamespaces(flattenDoc);

            foreach(var key in flatNamespaces.Keys)
            {
                if(!rootNamespaces.ContainsKey(key))
                {
                    var curNamespace = flatNamespaces[key];
                    doc.Root.Add(new XAttribute(XNamespace.Xmlns + key, curNamespace.ToString()));
                    rootNamespaces.Add(key, curNamespace);
                }
            }

            var startComment = new XComment($"Merged from file {sourcePath}");
            var endComment = new XComment($"");

            var list = new List<XNode>();
            list.Add(startComment);
            list.AddRange(flattenDoc.Root.Elements());
            list.Add(endComment);
            addAfterElement.AddAfterSelf(list);

            addAfterElement = endComment;

        }

        mergedDictElement.Remove();

        return doc;
    }
#>
<#= GetFlattenResourceDocument("Default.xaml").ToString() #>
于 2017-11-28T09:16:28.820 回答