XAML Playground
about XAML and other Amenities

A generic enum converter to map values...

2010-09-03T16:38:49+01:00 by Andrea Boschin

Many times I have to deal with enumerated values with databinding, and often this requires  the writing of a converter to map the enum to something else. As an example you can have a Status enum and you need to transform it to a color (or a brush) in the UI, or you have a series of values from the enumerator and need to change it to an image when it is presented to the user.

Finally these days I found a generic solution to these problems that let me create a mapping from a value (an enumerator, a series of integers, of also a boolean) to an instance of another object. To give you an example think at having this enumerator:

   1: public enum MessageType
   2: {
   3:     Normal,
   4:     Urgent,
   5:     Critical
   6: }

We want to map the values to a series of colors having White for Normal, Orange for Urgent and Red for Critical. Since usually you can override IValueConverter and write a specific converter that handles this enum I found useful to create a parametric value converter that lets me write the code below:

   1: <UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   2:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   3:              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
   4:              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   5:              xmlns:cv="clr-namespace:SilverlightPlayground.Converters;assembly=SilverlightPlayground"
   6:              mc:Ignorable="d" x:Class="SilverlightPlayground.MyApplication.Shell" d:DesignWidth="456">
   7:     <UserControl.Resources>
   8:         <cv:EnumConverter x:Key="mapTypeToBrush">
   9:             <cv:EnumConverter.Items>
  10:                 <SolidColorBrush Color="#00000000" />
  11:                 <SolidColorBrush Color="#FFFF9900" />
  12:                 <SolidColorBrush Color="#FFFF0000" />
  13:             </cv:EnumConverter.Items>
  14:         </cv:EnumConverter>
  15:     </UserControl.Resources>
  16:     <Grid x:Name="LayoutRoot">
  17:         <Rectangle Fill="{Binding ReveivedMessageType, Converter={StaticResource mapTypeToBrush}}" Width="100" Height="100" />
  18:     </Grid>
  19: </UserControl>

The beautiful of this converter is that with a single converter you can create a series of mappings in the App.xaml resources and share them across all the applications and centralize the conversion of application wide enums in a single position. The code of the converter is really simple and straightforward:

   1: public class EnumConverter : IValueConverter
   2: {
   3:     private List<object> items;
   4:  
   5:     public List<object> Items 
   6:     { 
   7:         get
   8:         {
   9:             if (items == null)
  10:                 items = new List<object>();
  11:  
  12:             return items;
  13:         }
  14:     }
  15:  
  16:     public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  17:     {
  18:         if (value == null)
  19:             throw new ArgumentNullException("value");
  20:         else if (value is bool)
  21:             return this.Items.ElementAtOrDefault(System.Convert.ToByte(value));
  22:         else if (value is byte)
  23:             return this.Items.ElementAtOrDefault(System.Convert.ToByte(value));
  24:         else if (value is short)
  25:             return this.Items.ElementAtOrDefault(System.Convert.ToInt16(value));
  26:         else if (value is int)
  27:             return this.Items.ElementAtOrDefault(System.Convert.ToInt32(value));
  28:         else if (value is long)
  29:             return this.Items.ElementAtOrDefault(System.Convert.ToInt32(value));
  30:         else if (value is Enum)
  31:             return this.Items.ElementAtOrDefault(System.Convert.ToInt32(value));
  32:  
  33:         throw new InvalidOperationException(string.Format("Invalid input value of type '{0}'", value.GetType()));
  34:     }
  35:  
  36:     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  37:     {
  38:         if (value == null)
  39:             throw new ArgumentNullException("value");
  40:  
  41:         return this.Items.Where(b => b.Equals(value)).Select((a, b) => b);
  42:     }
  43: }

The converter has a list of items it use to make the conversion of integer values. It maps the integer to the index of the list and returns the n-th element found in the array. I didn't handled the out of bounds exception just because the errore returned to the user by the converter is already good to understand what it happen. The converter itself can handle both the directions if the Equals method is correctly overrided.

Finally, I  supported boolean values so it is possible to pass to the converter true/false and having it select the items from a list of two elements. This let me also map the Visibility to boolean values (that is a pretty frequent situation):

   1: <cv:EnumConverter x:Key="bool2Visibility">
   2:     <cv:EnumConverter.Items>
   3:         <Visibility>Collapsed</Visibility>
   4:         <Visibility>Visible</Visibility>
   5:     </cv:EnumConverter.Items>
   6: </cv:EnumConverter>

As you can see the trick is really simple but as usual simple things are the most beautiful and useful. I hope someone can judge this tip good to spare time. It has been very great for me.