Pull to refresh

Comments 12

Ну вообще в MS, очевидно, тоже столкнулись с этой проблемой. Для решения таких ситуаций MS использует не гласный паттерн. Есть такой класс — ComponentResourceKey. Если нужно получить доступ к ресурсу из code-behind, то в классе объявляется статическое поле типа ComponentResourceKey, а в XAML используется конструкция Key={x:Static local:ClassName.FieldName}. Затем доступ к ресурсу производится через Application.Current.FindResource.

namespace MyNS
{
    public class MyControl
    {
        public static readonly ComponentResourceKey MyResourceKey =
            new ComponentResourceKey(typeof(MyControl), "MyResource");

        private static Brush m_MyResourceCache;

        public static Brush MyResource
        {
            get
            {
                 if (null == m_MyResource)
                     m_MyResource = (Brush)Application.Current.FindResource(MyResourceKey);
                 return m_MyResource;
            }
        }
    }
}


Так сделано, например, в тулбаре. Есть такое поле ToolBar.ButtonStyleKey, соответственно, что бы в XAML сослаться на стиль, можно использовать конструкцию <Button Style={StaticResource {x:Static ToolBar.ButtonStyleKey}}>.
На мой взгляд XAML хорош тем, что в нем объявление ресурсов выглядит гармонично.
Тут получается зеркальная ситуация — мы объявляем ресурсы в коде и ссылаемся на них из XAML.
К сожалению, расширение разметки x:Static доступно только в WPF.
1. Классы для работы с XAML включены во фреймворк. Не пробовали читать ими, а не изобретать велосипед?

2. Вместо VSProject.Project.Properties.Item("DefaultNamespace").Value.ToString() лучше использовать CallContext.LogicalGetData("NamespaceHint"). Эта штука сама просчитает правильный путь по папкам и учтёт неймспейс из настроек скрипта.
Вы имеете в виду XamlReader? С ним получается вообще трехколесный с reflection.
По второму пункту согласен. Спасибо за совет.
Да ладно?

Пихаем ресурсы в ResourceDictionary, мержим в App.xaml. А в T4 имеем:

var res = (ResourceDictionary)XamlReader.Load(File.OpenRead(Host.ResolvePath("Res.xaml")));
var str = string.Join("\n", res.OfType<DictionaryEntry>().Select(i => string.Format("{0} {1}", i.Value.GetType(), i.Key)));

Плюс перечисление сборок, если в ресурсах свои типы. Я что-то упустил?
Первая проблема — мы не можем загрузить второй instance класса Application, при вызове:
XamlReader.Load(File.OpenRead(Host.ResolvePath("/App.xaml")));

выдается исключение:
System.InvalidOperationException: Cannot create more than one System.Windows.Application instance in the same AppDomain

Изначально у меня была идея вообще не парсить xaml, а выковырять все из Application.Current, но я не смог найти подходящего решения.
Плюс такой подход не решает второй проблемы — ресурсы могут быть объявлены не только у корневого элемента, они могут быть и у Grid, который объявлен как дочерний элемент страницы, и у Custom элемента из сторонней библиотеки.
Мне показалось, что xpath справляется с задачей найти ресурсы на любом уровне вложенности вполне гибко.

Подход с загрузкой через XamlReader годится для разбора отдельныx файлов c ресурсами, корневым элементом которых является ResourceDictionary.
Э… Сгенерированный из скрипта T4 исполняемый файл создаёт WPF Application? Если из WPF приложения пытаться загрузить XAML с Application в корне — это да, упадёт.

Так или иначе, держать общие для разных компонентов приложения ресурсы в отдельном resource dictionary, а в App.xaml мержить все нужные словари — это разумная практика. Причин держать ресурсы напрямую в App.xaml, в общем-то, нет.

Ресурсы-то могут лежать во вложенных элементах, но, во-первых, это обычно подразумевает, что ресурсы используются в одном месте, и для расшаривания не предназначены. Хочется дать общий доступ — положите в именованный словарь, будет какая-то организация, а не свалка. Во-вторых, с XPath вы тоже не отловите все случаи, потому что элемент ResourceDictionary может быть неявным, потому что ключи могут использоваться не только для ресурсов, потому что имена могут конфликтовать, потому что нужно понимать типы свойств, потому что достать ресурсы из кода будет закатом солнца вручную и т.п. По сравнению с этим пройтись по понятному построенному из XAML дереву объектов будет гораздо проще (хоть и не просто, но хоть реализуемо).
Хочу рассказать, как максимально удобно работать с XAML-ресурсами из Code-Behind. В этой статье мы разберемся, как работают пространства имен XAML, узнаем о XmlnsDefinitionAttribute, используем Т4-шаблоны и сгенерируем статичный класс для доступа к XAML-ресурсам.

Хотя бы кратко опишите достаточно частый кейс, в котором такое решение необходимо. Мне за все время работы с WPF лишь в единичных случаях приходилось доставать в Code-Behind из контрола ресурс по ключу и подсовывать его в динамически создаваемый новый контрол. С оглядкой на те случаи, сам себе признаю, что был явный Design Flaw.
В случае с проектом, над которым я сейчас работаю, такое решение удобно, когда в проекте есть несколько глобальных настроек, которые не меняются, например:
  • основной цвет
  • класс с командами навигации
  • класс с командами, которые должны быть доступны на любом экране (например logout)


В случае с командами это удобно, т.к. я могу либо использовать StaticResource во View, либо вызвать команду из Code-Behind.

Вы всегда можете передать такие настройки и через DependencyProperty, но зачем?

Резюмирую: полезно использовать для всего глобального скоупа, который может использоваться в Code-Behind, чтобы не плодить для каждого такого свойства DependencyProperty и использовать Binding, т.к в этом случае это просто лишний код.
Вы всегда можете передать такие настройки и через DependencyProperty, но зачем?

Вопрос неверно поставлен. Зачем через код экстрактить ресурсы и перекладывать их куда-либо, если это можно сделать в XAML.
1. Если у Вас не все ресурсы и/или не во всех случаях возможно получить через ссылки вида StaticResource (или в крайнем случае DynamicResource), то у Вас что-то не так с архитектурой.
2. Из Вашего резюме ничего не понятно. Зачем в коде получать доступ к цветам? Код должен содержать логику, которую легко протестировать без надобности загрузки ресурсов XAML.
3. Чтобы расшарить ViewModel с глобальными командами, очень просто инстанциировать MainViewModel как ресурс в App.xaml. Или использовать ObjectDataProvider + Unity Container. В библиотеке MVVMLight есть к тому же реализация паттерна Service Locator, с помощью которой можно легко расшаривать VM с командами.

Ресурсам в коде делать нечего как минимум с точки зрения тестопригодности.
все равно непонятно. Для этого же стили существуют
Sign up to leave a comment.

Articles