Click here to Skip to main content
6,822,613 members and growing! (15,147 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Windows Presentation Foundation » General     Intermediate License: The Code Project Open License (CPOL)

Binding and using Friendly enums in WPF

By Sacha Barber

Binding and using Friendly enums in WPF
C# (C#3.0), .NET (.NET3.0, .NET3.5), WPF, Architect, Dev, Design
Posted:19 Sep 2008
Views:25,756
Bookmarked:46 times
Unedited contribution
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
32 votes for this article.
Popularity: 7.22 Rating: 4.80 out of 5

1
1 vote, 3.1%
2
1 vote, 3.1%
3
2 votes, 6.3%
4
28 votes, 87.5%
5

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

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

Now this is all well and good, but imagine 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

<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:

<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) :

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

This will result in the following

Display friendly names for enums, to aid the user experience

Now this does 1/2 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:

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.

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.

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

<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

/// <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 EnumMemberAttribute, using the static method
/// within the EnumHelper class
/// </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)
    {
            FieldInfo fi = value.GetType().GetField(value.ToString());

            LocalizableDescriptionAttribute[] attributes =
                (LocalizableDescriptionAttribute[])fi.GetCustomAttributes
					(typeof(LocalizableDescriptionAttribute), false);

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


    }

    /// <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 clas recieves 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

<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

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

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 that out at Andrews blog, the post is available using the following 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)

About the Author

Sacha Barber


Member
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 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

Occupation: Software Developer (Senior)
Location: United Kingdom United Kingdom

Other popular Windows Presentation Foundation articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 23 of 23 (Total in Forum: 23) (Refresh)FirstPrevNext
GeneralHow to pass the VALUE of Enum as a parameter in WPF ObjectDataProvider: PinmemberMember 6599127:52 1 Oct '09  
GeneralHow to pass the VALUE of Enum as a parameter in WPF ObjectDataProvider: PinmemberMember 6599127:41 1 Oct '09  
QuestionIt does not work correctly when ComboBox is placed in ListView/GridView. PinmemberMaksAnt4:25 18 Jun '09  
QuestionLocalizing works fine, but styling..... [modified] PinmemberHardy Wang9:46 2 Jan '09  
AnswerRe: Localizing works fine, but styling..... PinmvpSacha Barber1:52 4 Jan '09  
QuestionDesign time exception PinmemberSuperCriller5:03 25 Sep '08  
AnswerRe: Design time exception PinmvpSacha Barber5:35 25 Sep '08  
GeneralRe: Design time exception PinmemberChristian Holm Maagaard1:33 26 Sep '08  
GeneralRe: Design time exception PinmvpSacha Barber2:10 26 Sep '08  
GeneralAn existing (simpler) solution PinmemberGrant Frisken15:25 24 Sep '08  
GeneralRe: An existing (simpler) solution PinmvpSacha Barber23:37 24 Sep '08  
GeneralWhy not use a DataTemplate? Pinmembernmosafi9:16 20 Sep '08  
GeneralRe: Why not use a DataTemplate? PinmemberColin Eberhardt10:08 20 Sep '08  
GeneralRe: Why not use a DataTemplate? Pinmembernmosafi10:23 20 Sep '08  
GeneralRe: Why not use a DataTemplate? PinmvpSacha Barber21:24 20 Sep '08  
GeneralRe: Why not use a DataTemplate? PinmvpSacha Barber21:23 20 Sep '08  
GeneralWho Is The Man?? PinmvpKarl Shifflett7:28 20 Sep '08  
GeneralRe: Who Is The Man?? PinmvpSacha Barber9:05 20 Sep '08  
GeneralLocalization PinmvpJosh Smith7:50 19 Sep '08  
GeneralRe: Localization PinsitebuilderUwe Keim8:59 19 Sep '08  
GeneralExample usage PinsitebuilderUwe Keim9:04 19 Sep '08  
GeneralRe: Localization PinmvpSacha Barber20:57 19 Sep '08  
GeneralRe: Localization PinmvpSacha Barber20:58 19 Sep '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

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

PermaLink | Privacy | Terms of Use
Last Updated: 19 Sep 2008
Editor:
Copyright 2008 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2010
Web18 | Advertise on the Code Project