Sunday, 5 May 2013

Data Binding a Static Resource in WPF

I was recently trying to create a data template for data binding a list of objects, each one defining an icon to display from MahApps.Metro.Resources Icons.xaml. I found a solution by Pedro Sampaio to extend StaticResourceExtension to make it bindable, but I was having some issues such as not being able to dynamically change the colour of the icons, or use the same icon multiple times.

I decided to go for a simpler solution and create a converter which would convert a string, being the resource key, to the icon's geometry. The converter has to make some assumptions to the location of the Icons.xaml file and the structure of its contents. The converter caches the icons geometry once accessed.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Shapes;
namespace MyApp.Wpf
{
public class StringToIconConverter : IValueConverter
{
private static readonly ResourceDictionary _iconResource;
private static readonly Dictionary<string, Geometry> _pathData;
static StringToIconConverter()
{
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
_iconResource = new ResourceDictionary
{
Source = new Uri("pack://application:,,,/Resources/Icons.xaml")
};
_pathData = new Dictionary<string, Geometry>();
}
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var resourceKey = value as string;
return string.IsNullOrWhiteSpace(resourceKey) ? null : GetIconPathData(resourceKey);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
/// <summary>
/// Try and get the icon path data for the given resource key.
/// </summary>
/// <remarks>
/// This method assumes Icons.xaml from MahApps.Metro is used, and that
/// the icon resource is a Canvas containing a Path.
/// </remarks>
/// <param name="resourceKey">The resource key for the icon.</param>
/// <returns>the icon path data if found, otherwise null.</returns>
private static object GetIconPathData(string resourceKey)
{
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
Geometry iconPathData;
if (!_pathData.TryGetValue(resourceKey, out iconPathData))
{
Canvas iconCanvas = _iconResource[resourceKey] as Canvas;
if (iconCanvas != null)
{
Path iconPath = iconCanvas.Children.Count > 0 ? iconCanvas.Children[0] as Path : null;
if (iconPath != null)
{
iconPathData = iconPath.Data;
}
}
_pathData.Add(resourceKey, iconPathData);
}
return iconPathData;
}
return null;
}
}
}


The objects being databound have a string property for the resource key of the icon to use, and this is bound to the a Path's Data property in the data template, which allows the Fill property to be changed dynamically.

<DataTemplate x:Key="MyDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<Path Data="{Binding Icon, Converter={StaticResource stringToIcon}}" Stretch="Uniform" Width="28" Height="28"
Fill="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}, Path=Foreground}" />
</Grid>
</DataTemplate>

No comments:

Post a Comment