Christian Harlass's Blog

neat .net

Binding Enums to a ComboBox in Silverlight

Posted by charlass on July 29, 2009

The Enum class in Silverlight does not support the handy GetValues() method – so how to convince a ComboBox to present a list of all the enum values?

A first answer is quickly found: get the enum values via Enum.GetFields(), the code is rather simple:

object[] enumValues = typeof (ExperienceLevel).GetFields()
   .Where (f => f.IsLiteral)
   .Select (f => f.GetValue (null))
   .ToArray();

But how to bind it to the ComboBox? Assuming that you use the MVVM pattern, the first impulse would be to write something like this in XAML:

<ComboBox ItemsSource="{Binding MyEnumValues, Mode=OneTime}"
                SelectedItem="{MyEnumProperty, Mode=TwoWay}"/>

However, if we were to apply it we’d soon find two issues:

  • The ComboBox will probably be blank, wrongly indicating that your property has not been set yet.
  • The ViewModel class would now contain beside the actual data properties also the collection of enum values. That doesn’t feel quite right…

The first one comes from the fact that the enum values are converted into objects. Saving the converted “enum objects” in a static field will fix this issue quickly.

The other concern can actually be very neatly resolved by introducing a generic EnumValuesConverter. The idea is that you bind your XAML to the same property as your selected item, the converter detects then the property type and returns the enum values.

/// <summary>
/// Caches the "enum objects" for the lifetime of the application.
/// </summary>
internal static class EnumValueCache
{
	private static readonly IDictionary<Type, object&#91;&#93;> Cache = new Dictionary<Type, object&#91;&#93;>();

	public static object[] GetValues (Type type)
	{
		if (!type.IsEnum)
			throw new ArgumentException ("Type '" + type.Name + "' is not an enum");

		object[] values;
		if (!Cache.TryGetValue (type, out values))
		{
			values = type.GetFields()
				.Where (f => f.IsLiteral)
				.Select (f => f.GetValue (null))
				.ToArray();
			Cache[type] = values;
		}
		return values;
	}
}

/// <summary>
/// Enum => EnumValues
/// </summary>
public class EnumValuesConverter : IValueConverter
{
	public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
	{
		if (value == null)
			return null;
		else
			return EnumValueCache.GetValues (value.GetType());
	}

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

And your XAML code would be simplified to:

...
<local:EnumValuesConverter x:Name="enumConverter"/>
...
<ComboBox ItemsSource="{Binding MyEnumProperty, Mode=OneTime, Converter={StaticResource enumConverter}}"
                SelectedItem="{Binding MyEnumProperty, Mode=TwoWay}"/>

That’s it! Quite a neat solution and even reusable without any changes. Enjoy!

5 Responses to “Binding Enums to a ComboBox in Silverlight”

  1. Aaron Dyer said

    If GetValues() were supported in Silverlight (in my case version 4) I wouldn’t be at this blog looking for an alternative solution. Nor does it support GetFields(). An Enum that can’t be enumerated…now, there’s a concept.

  2. Hi Christian,
    Nice implementation!
    How can we use the Description Attribute of an enum?

    For example, i have the following enum:

    public enum MyEnum
    {
    [Description(“Value1”)]
    someValue1 = 0,

    [Description(“Value2”)]
    someValue2 = 1
    }

    Instead of converting someValue1 and someValue2 to string, i would like to use the description. Is that possible?
    Thanks

    • charlass said

      Interesting question, initially I wasn’t sure whether SL supports that at all.

      Turns out we need sevaral things:
      1) FieldInfo.GetCustomAttributes() to retrieve the DescriptionAttribute
      2) Caching both the enum value and the description value
      3) Differentiate between the conversion targets Combobox.ItemsSource and COmbobox.SelectedItem
      4) ConvertBack needs to be implemented as well

      Was an interesting excercise, maybe I really should start blogging again 🙂
      code (rename to .zip)

  3. inar said

    Great! thanks!

  4. Kruddler said

    Here’s better way which leverages the Description custom attribute:

    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using Adapt.Utilities.Common;

    namespace Adapt.Presentation.Controls
    {
    ///
    /// Provides an item source for ComboBoxes which are bound to properties which are of an Enum type
    ///
    public class EnumProvider : IEnumerable
    {
    #region Fields

    private string _EnumTypeName;
    private List _EnumElements = new List();

    #endregion

    #region Public Properties

    ///
    /// The fully qualified type name of the Enum
    ///
    public string EnumTypeName
    {
    get
    {
    return _EnumTypeName;
    }
    set
    {
    //Get the type from the specified Enum type
    var type = ReflectionUtils.GetType(value);

    //Get the fields from the Enum type
    var fields = type.GetFields().Where(field => field.IsLiteral);

    //Iterate through fields
    foreach (var field in fields)
    {
    //Get the int value of the element
    var theValue = (int)field.GetValue(null);

    //Get the display text from the custom attribute
    var theDisplay = ((DescriptionAttribute[])field.GetCustomAttributes(typeof(DescriptionAttribute), false))[0].Description;

    //Add to the list of elements
    _EnumElements.Add(new EnumElement { Value = theValue, DisplayText = theDisplay });
    }

    _EnumTypeName = value;
    }
    }

    #endregion

    #region Public Methods
    public IEnumerator GetEnumerator()
    {
    return _EnumElements.GetEnumerator();
    }
    #endregion
    }

    ///
    /// A class which represents Enum elements for easy binding in a ComboBox
    ///
    public class EnumElement
    {
    ///
    /// Value to bind to (Use SelectedValuePath on ComboBox)
    ///
    public int Value { get; set; }

    ///
    /// Display text for item (Use DisplayMemberPath on ComboBox)
    ///
    public string DisplayText { get; set; }

    ///
    /// Equivalence method
    ///
    ///
    ///
    public override bool Equals(object obj)
    {
    return obj is EnumElement && ((EnumElement)obj).Value == Value;
    }
    }
    }

    To use this in Xaml:
    xmlns:adaptcontrols=”clr-namespace:Adapt.Presentation.Controls;assembly=Adapt.Presentation.Controls

Leave a reply to charlass Cancel reply