Yet another blog about IValueConverter

Posted by codingbloke on Geeks with Blogs See other posts from Geeks with Blogs or by codingbloke
Published on Wed, 09 Jun 2010 18:50:53 GMT Indexed on 2010/06/09 20:02 UTC
Read the original article Hit count: 469

Filed under:

After my previous blog on a Generic Boolean Value Converter I thought I might as well blog up another IValueConverter implementation that I use.

The Generic Boolean Value Converter effectively converters an input which only has two possible values to one of two corresponding objects.  The next logical step would be to create a similar converter that can take an input which has multiple (but finite and discrete) values to one of multiple corresponding objects.  To put it more simply a Generic Enum Value Converter.

Now we already have a tool that can help us in this area, the ResourceDictionary.  A simple IValueConverter implementation around it would create a StringToObjectConverter like so:-

StringToObjectConverter
using System;
using System.Windows;
using System.Windows.Data;
using System.Linq;
using System.Windows.Markup;

namespace SilverlightApplication1
{
    [ContentProperty("Items")]
    public class StringToObjectConverter : IValueConverter
    {
        public ResourceDictionary Items { get; set; }
        public string DefaultKey { get; set; }
        
        public StringToObjectConverter()
        {
            DefaultKey = "__default__";
        }

        public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value != null && Items.Contains(value.ToString()))
                return Items[value.ToString()];
            else
                return Items[DefaultKey];
        }

        public virtual object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return Items.FirstOrDefault(kvp => value.Equals(kvp.Value)).Key;
        }
    }
}

There are some things to note here.  The bulk of managing the relationship between an object instance and the related string key is handled by the Items property being an ResourceDictionary.  Also there is a catch all “__default__” key value which allows for only a subset of the possible input values to mapped to an object with the rest falling through to the default.

We can then set one of these up in Xaml:-

            <local:StringToObjectConverter x:Key="StatusToBrush">
                <ResourceDictionary>
                    <SolidColorBrush Color="Red" x:Key="Overdue" />
                    <SolidColorBrush Color="Orange" x:Key="Urgent" />
                    <SolidColorBrush Color="Silver" x:Key="__default__" />
                </ResourceDictionary>
            </local:StringToObjectConverter>

You could well imagine that in the model being bound these key names would actually be members of an enum.  This still works due to the use of ToString in the Convert method.  Hence the only requirement for the incoming object is that it has a ToString implementation which generates a sensible string instead of simply the type name.

I can’t imagine right now a scenario where this converter would be used in a TwoWay binding but there is no reason why it can’t.  I prefer to avoid leaving the ConvertBack throwing an exception if that can be be avoided.  Hence it just enumerates the KeyValuePair entries to find a value that matches and returns the key its mapped to.

Ah but now my sense of balance is assaulted again.  Whilst StringToObjectConverter is quite happy to accept an enum type via the Convert method it returns a string from the ConvertBack method not the original input enum type that arrived in the Convert.  Now I could address this by complicating the ConvertBack method and examining the targetType parameter etc.  However I prefer to a different approach, deriving a new EnumToObjectConverter class instead.

EnumToObjectConverter
using System;

namespace SilverlightApplication1
{
    public class EnumToObjectConverter : StringToObjectConverter
    {
        public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string key = Enum.GetName(value.GetType(), value);
            return base.Convert(key, targetType, parameter, culture);
        }

        public override object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string key = (string)base.ConvertBack(value, typeof(String), parameter, culture);
            return Enum.Parse(targetType, key, false);
        }
    }
}

 

This is a more belts and braces solution with specific use of Enum.GetName and Enum.Parse.  Whilst its more explicit in that the a developer has to  choose to use it, it is only really necessary when using TwoWay binding, in OneWay binding the base StringToObjectConverter would serve just as well.

The observant might note that there is actually no “Generic” aspect to this solution in the end.  The use of a ResourceDictionary eliminates the need for that.

© Geeks with Blogs or respective owner