5

This isn't MahApps.Metro-specific, but that happens to be what I'm using. I have a set of ViewModels that have a string property representing which icon to use from a resource XAML file.

public class CommandViewModel : ViewModel
{
    public CommandViewModel(string displayName, ICommand command, string icon)
    {
        if (command == null)
            throw new ArgumentNullException("command");

        DisplayName = displayName;
        Command = command;
        Icon = icon;
    }

    public ICommand Command { get; private set; }
    public string Icon { get; set; }
}

Icon would end up being something like "appbar_add" from MahApps.Metro.Resources. These are defined in an Icons.xaml file.

How do I write this up in my ItemTemplate such that the correct resource shows. I'm either getting errors on execution (not at edit/build) or I'm getting no icons at all.

The "no icon" XAML looks like this:

<ItemsControl ItemsSource="{Binding}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Rectangle Width="20" Height="20">
                    <Rectangle.Fill>
                        <VisualBrush Visual="{DynamicResource {Binding Path=Icon}}" />
                    </Rectangle.Fill>
                </Rectangle>
                <TextBlock Margin="15,6">                            
                    <Hyperlink Command="{Binding Path=Command}">
                        <TextBlock Text="{Binding Path=DisplayName}" />
                    </Hyperlink>
                </TextBlock>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

My attempts that caused errors were using StaticResource, which I think is fundamentally incorrect.

How should I be referencing the Icon property as the name of the resource I want?

Edit

More code was requested, so here's an example of what does work:

<Rectangle Width="20" Height="20">
    <Rectangle.Fill>
        <VisualBrush Visual="{StaticResource appbar_add}" />
    </Rectangle.Fill>
</Rectangle>

What I need to do is let "appbar_add" be a value from a property on my ViewModel -- the Icon property above.

The resources are in a ResourceDictionary in a separate file (Icon.xaml) and look like the following:

<Canvas x:Key="appbar_add" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
    <Path Width="38" Height="38" Canvas.Left="19" Canvas.Top="19" Stretch="Fill" Fill="{DynamicResource BlackBrush}" Data="F1 M 35,19L 41,19L 41,35L 57,35L 57,41L 41,41L 41,57L 35,57L 35,41L 19,41L 19,35L 35,35L 35,19 Z "/>
</Canvas>
Melissa Avery-Weir
  • 1,357
  • 2
  • 16
  • 47

2 Answers2

2

You can use a converter to do the work. refer the below code. I have two style to make the + symbol in red color or black color in Icon.xaml.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Canvas x:Key="appbar_add_Black" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
        <Path Width="38" Height="38" Canvas.Left="19" Canvas.Top="19" Stretch="Fill" Fill="Black" Data="F1 M 35,19L 41,19L 41,35L 57,35L 57,41L 41,41L 41,57L 35,57L 35,41L 19,41L 19,35L 35,35L 35,19 Z "/>
    </Canvas>
    <Canvas x:Key="appbar_add_Red" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
        <Path Width="38" Height="38" Canvas.Left="19" Canvas.Top="19" Stretch="Fill" Fill="Red" Data="F1 M 35,19L 41,19L 41,35L 57,35L 57,41L 41,41L 41,57L 35,57L 35,41L 19,41L 19,35L 35,35L 35,19 Z "/>
    </Canvas>
</ResourceDictionary>

Refer the View code.

<Window.Resources>
    <local:IconConverter x:Key="conv"/>
</Window.Resources>
<Grid>
    <Rectangle Width="20" Height="20">
        <Rectangle.Fill>
            <VisualBrush Visual="{Binding Icon,Converter={StaticResource conv}}" />
        </Rectangle.Fill>
    </Rectangle>
</Grid>

ViewModel

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new CommandViewModel("Red");
    }
}

public class CommandViewModel :INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
    public CommandViewModel(string icon)
    { 
        Icon = icon;
    }

    private string icon;

    public string Icon
    {
        get { return icon; }
        set { icon = value; }
    }    
}

class IconConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string str = (string)value;
        ResourceDictionary myResourceDictionary = new ResourceDictionary();
        myResourceDictionary.Source =
            new Uri("Icon.xaml",
                UriKind.Relative);
        if (str.Equals("Black"))
        {              
            return myResourceDictionary["appbar_add_Black"];
        }
        else
        {
            return myResourceDictionary["appbar_add_Red"];
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
Brian Lacy
  • 18,785
  • 10
  • 55
  • 73
Ayyappan Subramanian
  • 5,348
  • 1
  • 22
  • 44
  • This gives an error, either because Icon is a string (and not an Image), or because I need to reference the resource differently for this case. `'TargetDefaultValueConverter' converter failed to convert value ''.` and `IOException:'System.IO.IOException: Cannot locate resource ''`. That value works just fine when it's used directly in the code I originally posted as the VisualBrush's Visual (``). I can provide more details, if need be. – Melissa Avery-Weir Apr 01 '15 at 19:52
  • Are you trying to give full path of the image? – Ayyappan Subramanian Apr 01 '15 at 19:55
  • Nope, just the name as it is in the Icons.xaml file. "appbar_add", in this case. Works fine as a StaticResource, but fails as the Image Source. Should I be trying a more complicated path reference? – Melissa Avery-Weir Apr 01 '15 at 19:57
  • Additional code added to the original question. If you need more, I'll need to know what else would help. Thanks! – Melissa Avery-Weir Apr 01 '15 at 20:15
  • Awesome! This worked really well. Without the red/black toggling, I was able to just have `if (value != null) return myResourceDictionary[value.ToString()];`, a bit more like Craig's answer. – Melissa Avery-Weir Apr 02 '15 at 14:20
2

As Ganesh says, you can use a converter. I would just change the converter a little from the one suggested:

public class FindResourceFromString: IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value != null) return Application.Current.FindResource((string) value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

This takes the string in and returns the resource (from the resource dictionary). You would probably want the converter to return a placeholder icon if a resource with the string name wasn't found.

The XAML would then look something like this:

<ItemsControl ItemsSource="{Binding}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Rectangle Width="20" Height="20">
                    <Rectangle.Fill>
                        <VisualBrush Visual="{Binding Icon, Converter={StaticResource FindResourceFromStringConverter}}" />
                    </Rectangle.Fill>
                </Rectangle>
                <TextBlock Margin="15,6">                            
                    <Hyperlink Command="{Binding Path=Command}">
                        <TextBlock Text="{Binding Path=DisplayName}" />
                    </Hyperlink>
                </TextBlock>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

You would need to register the converter in the resources section of the user control or window that the ItemsControl is in:

<UserControl.Resources>
    <vc:FindResourceFromString x:Key="FindResourceFromStringConverter" />
</UserControl.Resources>

This assumes you have the converters available from a namespace called vc.

Brian Lacy
  • 18,785
  • 10
  • 55
  • 73
Craig
  • 683
  • 1
  • 8
  • 16