Binding an Enum Property to a ComboBox using customized text

The problem

I want to data bind a property on my Model to a ComboBox that allows selection from a list of Enum values. For example, my Person class has a HighestEducationLevel property of type EducationLevel.

 GridEditing

The second part of this problem is that I want to provide an optional customized text description for each of my enumeration values. For example, the enumeration value EducationLevel.PreSchool should be displayed as “Pre-school” in the ComboBox.

GridEditing - TypeConverter ComboBox

Solution 1 – Bind to Enum.GetValues()

There are plenty of blog posts and forum answers that show the following technique that can be accomplished in XAML alone. I first saw this on a post by Karl Schifflet.

You define a static resource as an ObjectDataProvider that simply uses a method call to get an array of the enumerated values.

<ObjectDataProvider x:Key="EducationLevelList" MethodName="GetValues" ObjectType="{x:Type local:EducationLevel}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:EducationLevel"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

This can then be data bound as the ItemsSource for a ComboBox – in my case its a ComboBox column of a WPF DataGrid.

<toolkit:DataGridComboBoxColumn Header="Education Level"
SelectedValueBinding="{Binding HighestEducationLevel}"
ItemsSource="{Binding Source={StaticResource EducationLevelList}}"/>

Solution 2 – Bind to Custom Method

This technique can be taken a step further by providing your own method to be used by the ObjectDataProvider rather than relying on Enum.GetValues(Type). This then provides the opportunity to provide custom sorting, filtering and text for the list of enumeration values.

One way to achieve the custom text values is to have the method return a list of “wrapper” objects that provide access to both the underlying enumeration value and the custom text.

public class EnumMapper
{
public EnumMapper( object enumValue, string enumDescription )
{
Enum = enumValue;
Description = enumDescription;
}
public object Enum { get; private set; }
public string Description { get; private set; }
}

The enum type can then have each of its members (fields) that require a custom text decorated with a custom attribute as follows.

public enum EducationLevel
{
None,
[EnumDisplayName("Pre-school")]
PreSchool,
[EnumDisplayName( "Junior school" )]
JuniorSchool,
[EnumDisplayName( "Senior School" )]
SeniorSchool,
Graduate,
[EnumDisplayName( "Post Graduate" )]
PostGraduate,
Professor
}

The new attribute class itself is trivial…

[AttributeUsage(AttributeTargets.Field, AllowMultiple=false)]
public class EnumDisplayNameAttribute : Attribute
{
public EnumDisplayNameAttribute( string displayName )
{
DisplayName = displayName;
}
public string DisplayName { get; set; }
}

The custom method to generate the list then becomes…

public static IList<EnumMapper> GetEnumDescriptions( Type enumType )
{
if ( !enumType.IsEnum )
throw new ArgumentException( "This method can only be called for enum types." );
var list = new List<EnumMapper>();
foreach ( var enumValue in Enum.GetValues( enumType ) )
list.Add( new EnumMapper( enumValue, enumType.GetDisplayName( enumValue ) ) );
return list;
}
public static string GetDisplayName( this Type enumType, object enumValue )
{
if ( !enumType.IsEnum )
throw new ArgumentException( "This method can only be called for enum types." );
var displayNameAttribute = enumType.GetField( enumValue.ToString() )
.GetCustomAttributes( typeof( EnumDisplayNameAttribute ), false )
.FirstOrDefault() as EnumDisplayNameAttribute; if ( displayNameAttribute != null ) return displayNameAttribute.DisplayName; return Enum.GetName( enumType, enumValue ); }

The XAML has to change a little. First the ObjectDataProvider must use the new method, and secondly because the ItemsSource is now a list of EnumMapper instances we must provide DisplayMemberPath and SelectedValuePath properties for the ComboBox.

<ObjectDataProvider x:Key="EducationLevelList" MethodName="GetEnumDescriptions" ObjectType="{x:Type local:BindingSupport}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:EducationList"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<toolkit:DataGridComboBoxColumn Header="Highest Education Level"
                                SelectedValueBinding="{Binding HighestEducationLevel}"
ItemsSource="{Binding Source={StaticResource EducationLevelList}}"
DisplayMemberPath="Description"
SelectedValuePath="Enum"/>

Solution 3 – Use a customized TypeConverter

Solution 2 provides quite a lot of flexibility but it does mean a couple of extra property setters are required in the XAML (though I guess these could go into a Style). The third approach is to use a TypeConverter to “magically” convert enum values to strings. The major benefit of this approach is that it will work not just for ComboBoxs but anywhere an enum is bound to text property, e.g. in a TextBlock or TextBox.

First we declare a new TypeConverter that has some special processing that allows its to generate the custom text. All enums by default use the EnumConverter anyway – we are just providing an extra lookup to check for a custom attribute.

public class EnumTypeConverter : EnumConverter
{
public EnumTypeConverter( Type enumType ) : base( enumType ) { }
public override object ConvertTo( ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType )
{
if ( destinationType == typeof(string) && value != null )
{
var enumType = value.GetType();
if ( enumType.IsEnum )
return GetDisplayName( value );
}
return base.ConvertTo( context, culture, value, destinationType );
}
    private string GetDisplayName( object enumValue )
{
var displayNameAttribute = EnumType.GetField( enumValue.ToString() )
.GetCustomAttributes( typeof( EnumDisplayNameAttribute ), false )
.FirstOrDefault() as EnumDisplayNameAttribute; if ( displayNameAttribute != null ) return displayNameAttribute.DisplayName; return Enum.GetName( EnumType, enumValue ); } }

The next step is to make sure that all our enums use the new TypeConverter. This is done by decorating the enum with a TypeConverter attribute.

[TypeConverter(typeof(EnumTypeConverter))]
public enum EducationLevel
{
None,
[EnumDisplayName("Pre-school")]
PreSchool,
[EnumDisplayName( "Junior school" )]
JuniorSchool,
[EnumDisplayName( "Senior School" )]
SeniorSchool,
Graduate,
[EnumDisplayName( "Post Graduate" )]
PostGraduate,
Professor
}

Now we can change our XAML back to its original simplified form (as per Solution 1) and yet we still get our customized text appearing.

<ObjectDataProvider x:Key="EducationLevelList" MethodName="GetValues" ObjectType="{x:Type local:EducationLevel}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:EducationLevel"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<toolkit:DataGridComboBoxColumn Header="Education Level"

                               
SelectedValueBinding="{Binding HighestEducationLevel
}"

                               
ItemsSource="{Binding Source={StaticResource EducationLevelList}}"/>

Other Considerations

In the example here I’ve just used hard-coded strings for the EnumDisplayName attributes. However, there is no reason these couldn’t be resource IDs or the like and the GetDisplayName method changed accordingly.

Also, if we want the TypeConverter to have the ability to ConvertFrom the customized text then we need to do a little more work. This is handy in the scenarios where the user may be able to type (as opposed to select from a list) the enum values. So the EnumTypeConverter changes to the following.

public class EnumTypeConverter : EnumConverter
{
private IEnumerable<EnumMapper> _mappings;
public EnumTypeConverter( Type enumType ) : base( enumType )
{
_mappings = from object enumValue in Enum.GetValues(enumType)
select new EnumMapper( enumValue, GetDisplayName(enumValue) );
}
public override object ConvertTo( ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType )
{
if ( destinationType == typeof(string) && value != null )
{
var enumType = value.GetType();
if ( enumType.IsEnum )
return GetDisplayName( value );
}
return base.ConvertTo( context, culture, value, destinationType );
}
public override object ConvertFrom( ITypeDescriptorContext context, CultureInfo culture, object value )
{
if ( value is string )
{
var match = _mappings.FirstOrDefault( mapping => string.Compare( mapping.Description, (string) value, true, culture ) == 0 );
if ( match != null )
return match.Enum;
}
return base.ConvertFrom( context, culture, value );
}

private string
GetDisplayName( object enumValue )
{
var displayNameAttribute = EnumType.GetField( enumValue.ToString() )
.GetCustomAttributes( typeof( EnumDisplayNameAttribute ), false )
.FirstOrDefault() as EnumDisplayNameAttribute;

if ( displayNameAttribute != null )
return displayNameAttribute.DisplayName;

return Enum.GetName( EnumType, enumValue );
} }

14 thoughts on “Binding an Enum Property to a ComboBox using customized text”

  1. Nice write up. I like the type converter solution. It’s a bit of a pity that you can’t just link directly to the enum without having to write one though. There is already an attribute (Description?) for display purposes on a type. Perhaps there is a type converter deep in the bowels as well?

  2. Thanks Ben. I originally wanted to use the System.ComponentModel.DisplayNameAttribute but the AttributeUsage flags don’t allow it to be used on Fields (such as enum members), only properties, events and public parameterless methods. DisplayName is often used by controls that can consume class metadata, for example to provide default column headings for a bound property in a grid. Description is used in conjunction with Category for use by type editors. You’re right though – it would be nice if this feature were “built-in”, much like DisplayName and Description attributes get bound to PropertyDescriptor.DisplayName and PropertyDescriptor.Description.

  3. Hi,

    I have followed this blog and am using solution 3 but have a slight problem in that nothing appears in my combobox? I don’t actually get any exceptions, and from what I can tell from my logging in the EnumMapper/Atribute/EnumTypeConverter these classes never get called.

    I have setup the ObjectDataProvider as such:

    ….
    xmlns:gatewayShared=”clr-namespace:Gateway.Shared;assembly=Gateway.Shared”>

    and am using the ComboBox as:

  4. I had the same problem until I realized that I had left the Type Converter attribute off my enum:

    [TypeConverter(typeof(EnumTypeConverter))]

  5. Awesome article.

    I’ve modified your code locally to use System.ComponentModel.DescriptionAttribute instead with no problems.

    It’s also handy to make your GetDisplayName() method public so you can convert values in code should you need to.

    Carl.

  6. How would you use this with a plain old ComboBox? I’m having trouble getting the syntax right. There is no SelectedValueBinding for combox box.
    -Dave

  7. Very nice code, it works perfectly after translating it on VB.
    I just wonder how to display this new attribute, for example, on a textbox, instead of the enum ‘classical’ value.
    Maybe a newbie question, but not sure !

  8. This is a great post!!
    I have another question, what if i want the displayName to only be shown when you open the combobox but when it closed i want the original enum value ?

  9. Very useful and clear post.
    I have used the 3rd option and it works well using the predefined Description attribute.

  10. Hey, thanks for that! I find databinding in WPF everything but trivial and have been struggling for hours. But this article is a really nice tutorial. Thanks again!

Comments are closed.