Click here to Skip to main content
15,889,442 members
Articles / Desktop Programming / WPF

Binding and Using Friendly Enums in WPF

Rate me:
Please Sign up or sign in to vote.
4.97/5 (76 votes)
27 Apr 2010CPOL4 min read 346K   4.2K   156   73
Binding and using Friendly enums in WPF

Introduction

As .NET developers, we know about and have probably used Enums before. For those that haven't used enums before, this is what MSDN has to say about them:

"The enum keyword is used to declare an enumeration, a distinct type consisting of a set of named constants called the enumerator list. Every enumeration type has an underlying type, which can be any integral type except char."

So when we use enums, we can actually do things like

C#
enum Hardware {DiskDrive=1, Keyboard, GraphicsCard, Monitor};

Now this is all well and good, but imagine if we wanted to display a list of enums within a list, and we actually wanted to have more descriptive values, friendly names if you like, but still maintain the underlying enum value selected where required.

This article will show you how to do the following, using WPF:

  • Bind to a Enumeration of enum values
  • Display friendly names for enums, to aid the user experience

Bind to a Enumeration of Enum Values

The first thing that we might want to do is display a list of all possible enum values, such that a current value may be selected within this list, or allow the user to select a new enum value. This is easily achievable using the following technique:

XML
<ObjectDataProvider x:Key="foodData"
                    MethodName="GetValues" 
                    ObjectType="{x:Type sys:Enum}">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="local:FoodTypes" />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

We can then use this to bind to within the XAML as follows:

XML
<ComboBox x:Name="cmbFoodType"  
    ItemsSource="{Binding Source={StaticResource foodData}}"
	....
	....
</ComboBox>

Where I have a demo enum declared as follows (if you could just ignore the LocalizableDescriptionAttribute for the moment, more on that later) :

C#
public enum FoodTypes : int
{
    Pizza = 1,
    Burger = 2,
    SpagBol = 3
}

This will result in the following:

Image 1

Display Friendly Names for Enums, to Aid the User Experience

Now this does half the job, but from a users' point of view, some more descriptive text may actually aid the user experience, so what can we do about that. Well as luck would have it, Reflection and Attributes have the answer. We are able to adorn our enum with a special attribute namely a derived LocalizableDescriptionAttribute which inherits from DescriptionAttribute, which may be used as follows:

C#
public enum FoodTypes : int
{
    [LocalizableDescription(@"Pizza", typeof(Resource))]
    Pizza = 1,

    [LocalizableDescription(@"Burger", typeof(Resource))]
    Burger = 2,

    [LocalizableDescription(@"SpagBol", typeof(Resource))]
    SpagBol = 3
}

I should point out that the original content of this article used the EnumMember which as several readers noted could not be localized for different cultures. Luckily one of those readers was the ultra talented Uwe Keim, who gave me some code for handling the localization of enums. This code is shown below for the LocalizableDescriptionAttribute. Thanks Uwe.

C#
using System;
using System.Resources;
using System.Reflection;
using System.Globalization;
using System.ComponentModel;

namespace FriendlyEnumValues
{
    /// <summary>
    /// Attribute for localization.
    /// </summary>
    [AttributeUsage(AttributeTargets.All,Inherited = false,AllowMultiple = true)]
    public sealed class LocalizableDescriptionAttribute : DescriptionAttribute
    {
        #region Public methods.
        // ------------------------------------------------------------------

        /// <summary>
        /// Initializes a new instance of the 
        /// <see cref="LocalizableDescriptionAttribute"/> class.
        /// </summary>
        /// <param name="description">The description.</param>
        /// <param name="resourcesType">Type of the resources.</param>
        public LocalizableDescriptionAttribute
		(string description,Type resourcesType) : base(description)
        {
            _resourcesType = resourcesType;
        }

        #endregion

        #region Public properties.

        /// <summary>
        /// Get the string value from the resources.
        /// </summary>
        /// <value></value>
        /// <returns>The description stored in this attribute.</returns>
        public override string Description
        {
            get
            {
                if (!_isLocalized)
                {
                    ResourceManager resMan =
                         _resourcesType.InvokeMember(
                         @"ResourceManager",
                         BindingFlags.GetProperty | BindingFlags.Static |
                         BindingFlags.Public | BindingFlags.NonPublic,
                         null,
                         null,
                         new object[] { }) as ResourceManager;

                    CultureInfo culture =
                         _resourcesType.InvokeMember(
                         @"Culture",
                         BindingFlags.GetProperty | BindingFlags.Static |
                         BindingFlags.Public | BindingFlags.NonPublic,
                         null,
                         null,
                         new object[] { }) as CultureInfo;

                    _isLocalized = true;

                    if (resMan != null)
                    {
                        DescriptionValue =
                             resMan.GetString(DescriptionValue, culture);
                    }
                }

                return DescriptionValue;
            }
        }
        #endregion

        #region Private variables.

        private readonly Type _resourcesType;
        private bool _isLocalized;

        #endregion
    }
}

The basic idea here is that this LocalizableDescriptionAttribute allows you to pass in a key and a resource type to look at, so the key value will index into the resource file and get the value of the resource file. This is shown below in the small resource file that is part of the demo code attached.

Image 2

So now that we know we can do this with the enums, what about using this as a ComboBox within the XAML. Mmmm, well luckily there is another WPF trick we can use to aid here, which is IValueConverter. Let's see the revised XAML:

XML
<Window.Resources>
    <local:EnumToFriendlyNameConverter x:Key="enumItemsConverter"/>

</Window.Resources>

<StackPanel>

    <!-- Enum Combobox picker -->
    <StackPanel Orientation="Vertical" Margin="2" Grid.Row="0" Grid.Column="0"   >

        <Label Height="Auto" Content="Food Types"/>
        <ComboBox x:Name="cmbFoodType"  
            ItemsSource="{Binding Source={StaticResource foodData}}"
	   		  ....
	    	  ....
            <ComboBox.ItemTemplate>

                <DataTemplate>
                    <Label  Content="{Binding   Path=.,Mode=OneWay, 
                                        Converter={StaticResource enumItemsConverter}}"
                            Height="Auto"
                            Margin="0" 
                            VerticalAlignment="Center"/>

                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </StackPanel>

</StackPanel>

Where the EnumToFriendlyNameConverter is as follows:

C#
/// <summary>
using System;
using System.Windows.Data;
using System.Globalization;
using System.Reflection;
using System.Runtime.Serialization;

namespace FriendlyEnumValues
{
    /// <summary>
    /// This class simply takes an enum and uses some reflection to obtain
    /// the friendly name for the enum. Where the friendlier name is
    /// obtained using the LocalizableDescriptionAttribute, which holds the localized
    /// value read from the resource file for the enum
    /// </summary>
    [ValueConversion(typeof(object), typeof(String))]
    public class EnumToFriendlyNameConverter : IValueConverter
    {
        #region IValueConverter implementation

        /// <summary>
        /// Convert value for binding from source object
        /// </summary>
        public object Convert(object value, Type targetType,
                object parameter, CultureInfo culture)
        {
            // To get around the stupid WPF designer bug
            if (value != null)
            {
                FieldInfo fi = value.GetType().GetField(value.ToString());

                // To get around the stupid WPF designer bug
                if (fi != null)
                {
                    var attributes =
                        (LocalizableDescriptionAttribute[]) 
			fi.GetCustomAttributes(typeof 
			(LocalizableDescriptionAttribute), false);

                    return ((attributes.Length > 0) &&
                            (!String.IsNullOrEmpty(attributes[0].Description)))
                               ?
                                   attributes[0].Description
                               : value.ToString();
                }
            }

            return string.Empty;
        }

        /// <summary>
        /// ConvertBack value from binding back to source object
        /// </summary>
        public object ConvertBack(object value, Type targetType, 
			object parameter, CultureInfo culture)
        {
            throw new Exception("Cant convert back");
        }
        #endregion
    }
}

The actual magic happens by the use of some Reflection. So if you need to run this in an XBAP, you will need to make sure it is run in FullTrust mode.

The final step of the puzzle is to make sure that the selected value makes its way back into the source object that may use one of the enum values. I am using a simple test setup comprised of a single ViewModel and a single test class. This should be obvious from the attached demo code.

Anyway, the part that ensures the test class receives the actual enum value, and not the friendly name, which it would not know what to do with, is a simple case of more databinding in the XAML. This is as follows:

XML
<ComboBox x:Name="cmbFoodType"  
    ItemsSource="{Binding Source={StaticResource foodData}}"
    SelectedItem="{Binding Path=TestableClass.FoodType, Mode=TwoWay}" Height="Auto">
    <ComboBox.ItemTemplate>

        <DataTemplate>
            <Label  Content="{Binding   Path=.,Mode=OneWay, 
                                Converter={StaticResource enumItemsConverter}}"
                    Height="Auto"
                    Margin="0" 
                    VerticalAlignment="Center"/>

        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Where this now includes a binding to the SelectedItem, which is a TwoWay binding to the actual enum value within the test class.

So putting it all together, we now have a bound ComboBox which shows friendly values to the user, but maintains the correct enum values within the bound object, for the selected item.

Image 3

And here is the test classes selected value, notice that it is the correct enum value:

Image 4

I think this aids the user experience, a bit, hope it helps you out, as it has me.

Alternative Approach

Since I wrote this article, the legendary Andrew Smith (Infragistics....(Josh Smith even calls him a guru)), sent me an email with an alternative approach where he creates a MarkupExtension that does that same as this, so you might like to check out that at Andrews blog. The post is available using the link, http://agsmith.wordpress.com/2008/09/19/accessing-enum-members-in-xaml/, thanks Andrew.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
Questionproblem with xaml addressing Pin
Ehsan.Bahrani21-Apr-19 6:57
Ehsan.Bahrani21-Apr-19 6:57 
QuestionViewModel solution? Pin
Claudio Giuggia3-Apr-18 5:35
Claudio Giuggia3-Apr-18 5:35 
QuestionObjectDataProvider with Enum in static class Pin
Member 125211514-Dec-16 23:02
Member 125211514-Dec-16 23:02 
QuestionComboboxes getting too high (SOLVED) Pin
D4rth B4n310-Feb-15 5:18
D4rth B4n310-Feb-15 5:18 
AnswerRe: Comboboxes getting too high Pin
Sacha Barber10-Feb-15 5:28
Sacha Barber10-Feb-15 5:28 
GeneralRe: Comboboxes getting too high (SOLVED) Pin
D4rth B4n310-Feb-15 21:11
D4rth B4n310-Feb-15 21:11 
GeneralRe: Comboboxes getting too high (SOLVED) Pin
Sacha Barber11-Feb-15 0:40
Sacha Barber11-Feb-15 0:40 
QuestionThank you :) :) Pin
Member 1004092922-Oct-14 7:22
Member 1004092922-Oct-14 7:22 
GeneralExcellent article Pin
Sylwester Sobkowicz27-Aug-13 10:52
Sylwester Sobkowicz27-Aug-13 10:52 
QuestionMy vote of 5 Pin
Kenneth Haugland25-May-13 16:48
mvaKenneth Haugland25-May-13 16:48 
Although I found the article really useful, I must be honest and say that I dont really like any of implementations as they seem overtly complicated when binding an Enum to a ComboBox. Hence I have some questions.

I currently used the Markup to create the bindings:
VB
Imports System.Windows.Markup

Public Class EnumerationExtension
    Inherits MarkupExtension
    Private _enumType As Type


    Public Sub New(enumType__1 As Type)
        If enumType__1 Is Nothing Then
            Throw New ArgumentNullException("enumType")
        End If

        EnumType = enumType__1
    End Sub

    Public Property EnumType() As Type
        Get
            Return _enumType
        End Get
        Private Set(value As Type)
            If _enumType = value Then
                Return
            End If

            Dim enumType__1 = If(Nullable.GetUnderlyingType(value), value)

            If enumType__1.IsEnum = False Then
                Throw New ArgumentException("Type must be an Enum.")
            End If

            _enumType = value
        End Set
    End Property

    Public Overrides Function ProvideValue(serviceProvider As IServiceProvider) As Object
        Dim enumValues = [Enum].GetValues(EnumType)

        Return (From enumValue In enumValues Select New EnumerationMember() With { _
          .Value = enumValue, _
          .Description = GetDescription(enumValue) _
        }).ToArray()
    End Function

    Private Function GetDescription(enumValue As Object) As String
        Dim descriptionAttribute = TryCast(EnumType.GetField(enumValue.ToString()).GetCustomAttributes(GetType(ComponentModel.DescriptionAttribute), False).FirstOrDefault(), ComponentModel.DescriptionAttribute)


        Return If(descriptionAttribute IsNot Nothing, descriptionAttribute.Description, enumValue.ToString())
    End Function

    Public Class EnumerationMember
        Public Property Description() As String
            Get
                Return m_Description
            End Get
            Set(value As String)
                m_Description = value
            End Set
        End Property
        Private m_Description As String
        Public Property Value() As Object
            Get
                Return m_Value
            End Get
            Set(value As Object)
                m_Value = value
            End Set
        End Property
        Private m_Value As Object
    End Class
End Class

Just a VB code of the original. And attached the Enum like this:
HTML
<ComboBox Name="cmbBox"
                          SelectionChanged="cmbBox_SelectionChanged"
                          ItemsSource="{Binding Source={local:Enumeration {x:Type local:MyClass+MyEnum}}}"
                          DisplayMemberPath="Description"
                          SelectedValue="{Binding CurrentStatus}"
                          SelectedValuePath="Value"></ComboBox>

This kindof works, as I have a nested enum[^] inside MyClass,however it makes the designer to crumble in design mode but It works in the release, and seems to have been an Issue since 2006:
MSDN[^]
However the documentation seems to Ignore this possibility:
MSDN documentation[^]
This is indeed my first question, It should work if I put the Enum inside a Namespace and reffer to this in the XAML right? (I couldnt make this work properly, so perhaps I forgot something?)

I also found a solution that uses a ValueConverter[^], but the one solution that I could not find is one that used some kind of attached property as Bea Stollnitz[^] used to create a internal drag drop that changes the index. Is it not possible to create a simular attached property called EnumItemsSource or simular and just attach it to the ComboBox? And what will I have to do in order to achieve this or is it not possible?

Kind Regards
Kenneth
SuggestionGood, But why so complicated? Pin
Coddioz3-Nov-12 20:03
Coddioz3-Nov-12 20:03 
GeneralRe: Good, But why so complicated? Pin
Sacha Barber4-Nov-12 4:03
Sacha Barber4-Nov-12 4:03 
GeneralRe: Good, But why so complicated? Pin
Sacha Barber10-Feb-15 5:36
Sacha Barber10-Feb-15 5:36 
QuestionXaml namespace definitions Pin
camyyssa17-Oct-12 3:10
camyyssa17-Oct-12 3:10 
AnswerRe: Xaml namespace definitions Pin
Sacha Barber17-Oct-12 4:03
Sacha Barber17-Oct-12 4:03 
GeneralRe: Xaml namespace definitions Pin
camyyssa17-Oct-12 4:58
camyyssa17-Oct-12 4:58 
QuestionHow to reduce verbosity? Pin
Bernhard Hiller6-Jun-12 0:06
Bernhard Hiller6-Jun-12 0:06 
AnswerRe: How to reduce verbosity? Pin
Sacha Barber6-Jun-12 0:49
Sacha Barber6-Jun-12 0:49 
QuestionHow to convert bzck? Pin
Goran _3-Mar-12 6:53
Goran _3-Mar-12 6:53 
AnswerRe: How to convert bzck? Pin
Sacha Barber3-Mar-12 21:25
Sacha Barber3-Mar-12 21:25 
Questionusing locbaml + msbuild example Pin
iceball126-Feb-12 6:10
iceball126-Feb-12 6:10 
AnswerRe: using locbaml + msbuild example Pin
Sacha Barber6-Feb-12 6:12
Sacha Barber6-Feb-12 6:12 
QuestionNice work - one question Pin
Goran _1-Feb-12 23:16
Goran _1-Feb-12 23:16 
AnswerRe: Nice work - one question Pin
Sacha Barber1-Feb-12 23:26
Sacha Barber1-Feb-12 23:26 
GeneralRe: Nice work - one question Pin
Goran _2-Feb-12 0:04
Goran _2-Feb-12 0:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.