Wpf объединенный словарь ресурсов не распознается из app.xaml
У меня есть приложение WPF.net 4.5, где мне сложно сменить ресурсные словари.
У меня есть такая же проблема, как Этот вопрос SO и Этот вопрос, но принятое решение не работа для меня.
У меня есть ресурсные словари, объявленные в моем приложении .xaml следующим образом (упрощен для ясности):
<Application.Resources>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skin/ResourceLibrary.xaml" />
<ResourceDictionary Source="Skin/Brushes/ColorStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Проблема:
Приложение может "ПОСМОТРЕТЬ" диктатор ColorStyles, если он указан в app.xaml, но если я перемещаю его/гнездо внутри 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 Элементы свойств не могут находиться в середине элемента содержание. Они должны быть до или после содержимого.
РЕДАКТИРОВАТЬ 1:
изменение кода на следующее устранено выше, благодаря комментарию 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>
но библиотека ресурсов по-прежнему не вызывается.
END EDIT 1:
Я также попытался изменить все пути к файлам, чтобы упаковать URI, но это также не решило проблему.
ИЗМЕНИТЬ 2:
Я попытался переместить ресурсLibrary.xaml и другие ресурсные словари в другой проект библиотеки классов (используя ту же структуру папок и файлы, что и выше). Затем я использовал следующий URI, но я до сих пор не могу получить доступ к ресурсам, объявленным в файле ResourceLibrary.xaml.
<ResourceDictionary Source="pack://application:,,,/FTC.Style;component/ResourceLibrary.xaml" />
Но опять же, если я добавлю каждый ресурсный словарь в файл App.Xaml, используя вышеприведенный формат UIR, ресурсы можно использовать.
END EDIT 2
После редактирования 1 ошибка исчезла, но я все еще не могу использовать ресурсы, которые являются частью объединенного словаря в файле ResourceLibrary.xaml. Я склонен согласиться с комментарием dowhile относительно того, следует ли мне использовать этот подход, но я хочу понять это, потому что наиболее распространенное решение этой проблемы (см. Ссылки в верхней части этого сообщения) не работает и, возможно, это решение может помочь кому-то другому.
Вопрос: Почему файл ResourceLibrary.xaml игнорируется?
Ответы
Ответ 1
У меня большая проблема с 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>
И каждый StaticResource в .xamls, определенный в основной работе сборки приложения, стили по умолчанию работают благодаря фиктивному стилю.
Что не работает, это StaticResources между .xamls внутри темы. Если я определяю стиль в Buttons.xaml, который использует StaticResource из Colors.xaml, я получаю сообщение об ошибке StaticResources и UnsetValue. Он работает, если я добавляю Colors.xaml в приложение MergedDictionaries.
Решение 0
Отказ от организации. Поместите все в один .xaml. Я считаю, что именно так, как правило, должны были использоваться ResourceDictionaries из-за всех "проблем" с MergedDictionaries (для меня это был бы кошмар).
Решение 1
Измените все ссылки на StaticResource для перекрестных ссылок внутри темы на DynamicResource. Он работает, но поставляется с ценой, поскольку DynamicResources "тяжелее", чем StaticResources.
Решение 2
В каждой теме .xaml, которая использует StaticResources из другого .xaml, добавьте этот другой ResourceDictionary в MergedDictionaries. Это означает, что Buttons.xaml, TextBoxes.xaml и другие будут иметь Colors.xaml в своих MergedDictionaries. Это приведет к тому, что Colors ResourceDictionary будет храниться в памяти в нескольких экземплярах. Чтобы избежать этого, вы можете захотеть заглянуть в SharedResourceDictionary.
Решение 3
По разным установкам ResourceDictionaries, разные вложения я придумал теорию:
Если StaticResource не найден выше в том же .xaml или в MergedDictionaries этого ResourceDictionary, он выполняется поиск в других MergedDictionaries верхнего уровня.
Я бы предпочел добавить в ApplicationResources только один .xaml, но обычно я использую два. Вам не нужно добавлять к ApplicationResources каждый .xaml, который у вас есть в Theme, просто - например, Controls.xaml(с любым вложением MergedDictionaries, но перекрестные ссылки между словарями Controls.xaml не разрешены) и Common.xaml который содержит все общие ресурсы управления. В случае, если Common.xaml также допускается вложенность, но не имеет перекрестных ссылок, не может быть отдельного Colors.xaml и Brushes.xaml, который использует Colors как StaticResources - тогда вам нужно будет добавить 3.xamls в Application MergedDictionaries.
Теперь я всегда использую третье решение, но я не считаю его идеальным и все еще хотел бы знать, есть ли лучший способ. Надеюсь, я правильно интерпретировал то, что вы назвали той же проблемой, что и моя.
Ответ 2
Мне пришлось вводить темы в наше приложение и сталкиваться с этими точными проблемами.
Короткий ответ таков:
Словари ресурсов могут "видеть" другие словари ресурсов, если они появляются в них в App.xaml. Если вы попытаетесь использовать MergedDictiories в файле, который не является App.xaml, словари ресурсов не будут "видеть" друг друга.
Для ресурсов по умолчанию в 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.
Программа слияния Xaml в GitHub
Ответ 3
В дополнение к @ 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() #>