среда, 12 апреля 2017 г.

Привязка к ресурсам в зависимости от данных

Столкнулся с интересной задачей. Есть некоторая коллекция данных, которую надо отобразить в ItemsControl. У элементов данных есть два свойства: первое просто текст, а второе имя ресурса с картинкой которую необходимо показать. Т.е. в зависимости от данных в модели должен подгружаться тот или иной ресурс. Поискав решение быстро наткнулся на применение конвертора который ищет ресурс по имени следующим способом:

return Application.Current.FindResource(resourceName) as BitmapImage;

Но это не работает, если ресурсы подгружены не в приложение, а например, в окно или контрол. Под катом интересный метод, который позволяет получить доступ к ресурсу где бы он не находился, главное, чтобы был доступен контролу.


В рамках примера, я буду использовать простой класс с двумя свойствами:
public class Model
{
    public string Title { getset; }
 
    public string ResourceName { getset; }
}
Вот разметка формы:
<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication4"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <BitmapImage x:Key="plus" UriSource="plus.png" />
        <BitmapImage x:Key="delete" UriSource="delete.png" />
        <local:NameToStyleConverter x:Key="NameToStyleConverter" />
    </Window.Resources>
    <Grid>
        <ItemsControl x:Name="icDemo">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Image Style="{Binding ResourceName,Converter={StaticResource NameToStyleConverter}}" />
                        <TextBlock Text="{Binding Title}" />
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>
А вот заполнение данными списка при загрузке:
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += MainWindow_Loaded;
    }
 
    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        List<Model> items = new List<Model>();
        items.Add(new Model() { Title = "Один", ResourceName = "plus" });
        items.Add(new Model() { Title = "Два", ResourceName = "plus" });
        items.Add(new Model() { Title = "Три", ResourceName = "delete" });
        items.Add(new Model() { Title = "Четыре", ResourceName = "plus" });
        items.Add(new Model() { Title = "Пять", ResourceName = "delete" });
        icDemo.ItemsSource = items;
    }
}
Как можно видеть из разметки, у контрола Image свойство Source не присваивается, а присваивается... Стиль! Ну а дальше все просто, получая имя ресурсы, мы конвертором создаем динамически стиль, в котором свойству Source присваиваем DynamicResource. Вот так:
public class NameToStyleConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Style result = null;
        var name = value?.ToString();
        if (!string.IsNullOrWhiteSpace(name))
        {
            result = new Style(typeof(Image));
 
            var dynamicResource = new DynamicResourceExtension(name);
 
            var setter = new Setter()
            {
                Property = Image.SourceProperty,
                Value = dynamicResource
            };
            result.Setters.Add(setter);
 
        }
        return result;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
Ну и вот так это выглядит:
Как я уже сказал, таким образом надо поступать, если в конверторе нельзя просто извлечь ресурсы из Application. Так, кстати, можно и другие свойства задавать, необязательно привязку к ресурсам.

2 комментария:

  1. как то была подобная задача, в программе использовал локализацию через динамические ресурсы, для быстрой смены языка. Да и вообще все строковые константы приятнее хранить в ResourceDirctiory, удобно разбивать на файлы, редактировать, искать по сравнению с resx файлами.

    ОтветитьУдалить
  2. Алексей, посмотрел видео с Вашим уроком (Уроки WPF. Таблицы и списки). Теперь ломаю голову как сделать динамическое расширение списка. Например передавая день месяца и его соответствие дню недели. (lbName.ItemsSource = new[] { new { FirstColumn = DayOfMonth[], SecondColumn = DayWeekName[] } };)

    ОтветитьУдалить