Как из приcвоить ImageSource объект Canvas из ResourceDictionary через код

167
18 апреля 2017, 06:34

Я заполняю ListBox, который содержит картинку и имя, с помощью класса. Как присвоить картинке Canvas из файла ресурсов и будет ли изменятся цвет этой картинке если изменять в Canvas'e заливку?

List<MenuListBoxItem> menuItems = new List<MenuListBoxItem>();        
ResourceDictionary iconDictionary = new ResourceDictionary();
iconDictionary.Source = new Uri("pack://application:,,,/Res/Vector/IconDictionary.xaml");       
menuItems.Add(new MenuListBoxItem("Search", (ImageSource)iconDictionary["appbar_radar"]));
using System.Windows.Media;
using System.Windows.Media.Imaging;
    public class MenuListBoxItem
        {
            public ImageSource Image { get; set; }
            public string Content { get; set; }
            public MenuListBoxItem(string name, ImageSource image)
            {
                this.Image = image;
                this.Content = name;
            }
            public MenuListBoxItem(string name, Uri image)
            {
                this.Image = new BitmapImage(image);
                this.Content = name;
            }
            public MenuListBoxItem(string name, BitmapImage image)
            {
                this.Image = image;
                this.Content = name;
            }
        }

EDIT 1: В общем я пытаюсь получить такой вид меню:

Чтобы список брался из класса и цвет иконок мог меняться. Код одной из иконок:

<Canvas x:Key="appbar_radar" x:Name="appbar_radar" Width="44" Height="44" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
        <Path Width="42.4159" Height="44.3333" Canvas.Left="16.1674" Canvas.Top="15.8334" Stretch="Fill" Fill="{DynamicResource ContrastBrush}" Data="..."/>
    </Canvas> 
Answer 1

Смотрите.

Один из центральных принципов WPF — разделение задач. Вы должны стараться не указывать UI-конструкции в описании ваших данных. Имеет смысл описывать в данных не то, какой UI у вашего элемента, а лишь необходимый минимум.

Кроме того, класть в ResourceDictionary куски UI неправильно. XAML — не просто «разметка», элементы создаются на самом деле, поэтому если вы кладёте визуальный фрагмент в него и используете в нескольких местах, вы получите ошибку. В качестве повторно используемых фрагментов уместно использовать различные Template'ы. Поскольку у вас повторяемая структура, уместно использовать ItemTemplate, а в параметр выносить часть, являющуюся чистыми данными, то есть Geometry.

В итоге получаем следующее:

XAML:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Test"
        x:Class="Test.MainWindow"
        mc:Ignorable="d"
        Title="Тест" Height="350" Width="525">
    <Window.Resources>
        <StreamGeometry x:Key="SettingsIcon">M0,0 L0,1 L1,1 L1,0 z</StreamGeometry>
        <StreamGeometry x:Key="HelpIcon">M0,0 L1,1 M1,0 L0,1</StreamGeometry>
        <SolidColorBrush x:Key="MenuColor">Green</SolidColorBrush>
    </Window.Resources>
    <Grid>
        <ItemsControl ItemsSource="{Binding}">
            <ItemsControl.ItemTemplate>
                <DataTemplate DataType="{x:Type local:ItemDescriptor}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="25"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Path Data="{Binding Icon}" Grid.Column="0" Stretch="Uniform"
                              Height="25" Width="25" Stroke="{DynamicResource MenuColor}"/>
                        <TextBlock Text="{Binding Content}" VerticalAlignment="Center"
                                   Grid.Column="1" Foreground="{DynamicResource MenuColor}"/>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

Понятно, что тут происходит? Мы изображаем последовательность одинаковых элементов, по одному на элемент коллекции, у каждого из них Icon и Content берётся из привязки.

Теперь code-behind:

Вспомогательный класс:

class ItemDescriptor
{
    public Geometry Icon { get; }
    public string Content { get; }
    public ItemDescriptor(Geometry icon, string content)
    {
        Icon = icon;
        Content = content;
    }
}

И окно:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new[]
        {
            new ItemDescriptor((Geometry)Resources["SettingsIcon"], "Settings"),
            new ItemDescriptor((Geometry)Resources["HelpIcon"], "Help"),
            new ItemDescriptor((Geometry)Resources["HelpIcon"], "Udp Multicast")
        };
    }
}

Нужный цвет фона и отступы добавить по вкусу.

Добавка для эстетов. Если вы хотите, чтобы класс ItemDescriptor не зависел от UI и не содержал Geometry (например, вы захотите передавать его из VM), нужна небольшая переделка. Во-первых, мы заменяем в ItemDescriptor Icon на IconName:

class ItemDescriptor
{
    public string IconName { get; }
    public string Content { get; }
    public ItemDescriptor(string iconName, string content)
    {
        IconName = iconName;
        Content = content;
    }
}

Соответственно, конструктор будет выглядеть проще:

        DataContext = new[]
        {
            new ItemDescriptor("SettingsIcon", "Settings"),
            new ItemDescriptor("HelpIcon", "Help"),
            new ItemDescriptor("HelpIcon", "Udp Multicast")
        };

Теперь XAML: в нём мы должны установить DynamicResource, а ключ взять из привязки! Из коробки {DynamicResource ResourceKey={Binding Key}} не работает, нам нужен вспомогательный класс.

Я пользуюсь таким трюком:

public class IndirectDynamicResource : FrameworkElement
{
    static IndirectDynamicResource()
    {
        WidthProperty.OverrideMetadata(typeof(IndirectDynamicResource),
                                       new FrameworkPropertyMetadata(0.0));
        HeightProperty.OverrideMetadata(typeof(IndirectDynamicResource),
                                        new FrameworkPropertyMetadata(0.0));
        FocusableProperty.OverrideMetadata(typeof(IndirectDynamicResource),
                               new FrameworkPropertyMetadata(false));
    }
    #region object dp ResourceKey, change callback ValueUpdateCallback
    public object ResourceKey
    {
        get { return GetValue(ResourceKeyProperty); }
        set { SetValue(ResourceKeyProperty, value); }
    }
    public static readonly DependencyProperty ResourceKeyProperty =
        DependencyProperty.Register(
            "ResourceKey", typeof(object), typeof(IndirectDynamicResource),
            new PropertyMetadata(ValueUpdateCallback));
    #endregion
    static protected void ValueUpdateCallback(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var self = (IndirectDynamicResource)d;
        self.SetResourceReference(IndirectDynamicResource.ValueProperty,
                                  self.ResourceKey ?? string.Empty);
    }
    #region object dp Value
    public object Value
    {
        get { return GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register(
            "Value", typeof(object), typeof(IndirectDynamicResource));
    #endregion
}

Имея это в арсенале, код в XAML меняем так:

<local:IndirectDynamicResource ResourceKey="{Binding IconName}" x:Name="D"/>
<Path Data="{Binding Value, ElementName=D}" Grid.Column="0" Stretch="Uniform"
      Height="25" Width="25" Stroke="{DynamicResource MenuColor}"/>

(Другие решения этой же проблемы см. тут.)

Всё!

READ ALSO
TimeSpan в Settings проекта

TimeSpan в Settings проекта

Добавил в настройки проекта параметр с типом TypeSpan, но почему-то я не вижу этот параметр через PropertiesSettings

196
Перезаписать текст в файле

Перезаписать текст в файле

Написал код для вставки запятой перед словамиКак перезаписать текст в файле на текст с расставленными запятыми?

201
Как сделать таймеры в C#

Как сделать таймеры в C#

ЗдравствуйтеПередо мной стоит задача - мне необходимо сделать простенькую игру: когда начинаешь игру, через некоторое время необходимо нажать...

246
Почему не распознается свойство HasValue?

Почему не распознается свойство HasValue?

Не распознается свойство HasValueВ чем проблема? Не могу понять :/ В System

269