Подтвердить что ты не робот

Как указать ресурсы в модели представления 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 работает в обоих случаях, поэтому я озадачен тем, что происходит не так.

4b9b3361

Ответ 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.