Как указать ресурсы в модели представления MVVM?
Предположим, что я хочу показать список объектов, в которых каждый объект должен иметь имя и подходящее изображение (например, MenuItems с иконками или кнопками с текстом и изображением).
Все примеры и программы отображали изображение в модели viewmodel как путь к файлу PNG, а затем привязывали к нему Source
Image
. Но что, если я хочу использовать векторные изображения (например, как DrawingImage
в локальном ResourceDictionary
)? Выявление DrawingImage из модели представления кажется плохим, потому что мне нужно будет хранить ссылку на приложение/окно/пользовательский элемент управления /... (и рекомендуется не выставлять такие объекты XAML из моделей представлений).
Таким образом, лучшим подходом было бы использовать строковый идентификатор в модели представления, а затем каким-то образом выбрать соответствующий ресурс. Если этот идентификатор является ключом ресурса, этот фрагмент выглядит заманчивым, но не работает:
<Image Source="{StaticResource {Binding Icon}}"/>
Я нашел два обходных решения для этого, хотя они не работали для меня.
-
Первый использовал обычную привязку к значку с конвертером, который искал ресурс в Application.Current
. Это не работает, если ресурс хранится где-то еще, я думаю (и ситуация, когда я изначально столкнулся с этой проблемой, еще не запускал приложение, так как это было Окно, выбирающее приложение для запуска!).
-
Во втором обходном пути использовалось расширение разметки, полученное из StaticResourceExtension
, которое извлекло его ResourceKey
из переданного привязки:
<Image Source="{local:BindableStaticResource {Binding Icon}"/>
Этот выглядит очень аккуратно, потому что он может использовать локальные ресурсы, а также использоваться для других вещей. Но при использовании я всегда получал исключение ( "Resource с именем {FooIcon} не удалось найти.", Показывая правильный файл XAML и позицию расширения). Даже пустое расширение ресурса, полученное из StaticResourceExtension
, которое просто передало ключ ресурса базовому конструктору, не работало, и я не могу объяснить, почему. Просто использование StaticResourceExtension
отлично справилось.
Любые идеи, как я мог бы исправить второй подход или даже лучшие решения?
Изменить
Я заметил, что он работает, когда используется прямо так:
<Window>
<Window.Resources>
<DrawingImage x:Key="SomeIcon"/>
</Window.Resources>
<Image Source="{BindableStaticResource {Binding Icon}}"/>
</Window>
но неудачно, например, в DataTemplate
. Хотя обычный StaticResourceExtension
работает в обоих случаях, поэтому я озадачен тем, что происходит не так.
Ответы
Ответ 1
Первое обходное решение, которое вы упомянули, можно найти здесь: Связывание свойства строки datacontext с ключом StaticResource
Я попытался использовать вторую работу, о которой вы упоминаете (http://sweux.com/blogs/psampaio/index.php/2009/06/16/using-data-binding-with-static-resources-in-wpf/), но я никогда не работал. Он выбрал ArgumentNullException, поскольку DataContext был нулевым. Я думаю, что это связано с тем, что я использовал DataTemplate для создания своего представления из моей модели ViewModel, и каким-то образом DataContext не был установлен до вызова метода ProvideValue (в примере на этой странице DataContext установлен в класс .xaml.vb).
Итак, я начал искать обходной путь и нашел тот, который также включает в себя конвертер, но этот находит ресурс с помощью метода FrameworkElement вместо того, чтобы вызывать вокруг Application.Current. Я нашел здесь:
http://drwpf.com/blog/2007/08/18/can-my-value-converter-access-the-target-of-the-binding/
Я скопирую соответствующую информацию здесь:
Он включает ValueConverter, реализующий интерфейс IMultiValueConverter, для доступа к элементу управления, на котором установлено привязку.
Код для метода Convert следующий:
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
FrameworkElement targetObject = values[0] as FrameworkElement;
if (targetObject == null)
{
return DependencyProperty.UnsetValue;
}
return targetObject.TryFindResource(values[1]);
}
И XAML для управления содержимым будет выглядеть так:
<ContentControl>
<ContentControl.Content>
<MultiBinding Converter="{StaticResource Converter}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}" />
<Binding Path="ResourceKey" />
</MultiBinding.Bindings>
</MultiBinding>
</ContentControl.Content>
</ContentControl>
И XAML для изображения выглядит следующим образом:
<Image Height="16" Width="16">
<Image.Source>
<MultiBinding Converter="{StaticResource Converter}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}" />
<Binding Path="ResourceKey" />
</MultiBinding.Bindings>
</MultiBinding>
</Image.Source>
</Image>
Работает как шарм: D
Ответ 2
В пользовательских методах MarkupExtensions есть ошибка, которая не позволяет использовать их в таком атрибуте.
Обходной путь 1: Объявить атрибут как элемент:
<Image>
<Image.Source>
<local:BindableStaticResource Binding={Binding Icon}" />
</Image.Source>
</Image>
Обходной путь 2: Если вы предварительно скопируете MarkupExtension, поместив его в другую сборку и ссылаясь на нее, он снова работает. Возможно, поэтому вы видите, что это работает в главном окне, но не в вашем DataTemplate.