Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Using Enums in List Controls


:P
On this page:

Quite frequently in Web and Windows apps, I’ve found it necessary to display data values contained in an Enum type typically inside of a list or combobox type control.

Assume that you want to display a drop down list from this enumeration of bug status types:

public enum BugStatusTypes 
{
    Entered,
    InProcess,
    Fixed,
    ByDesign,
    NotReproducible,
    NoBug,
    None
}

You can then do something akin to this to turn that enumeration into values to use in the dropdown:

protected override void OnInit(EventArgs e)
{
    base.OnInit(e);

    string[] bugStatuses = Enum.GetNames(typeof(BugStatusTypes));
    foreach(string bugStatus in  bugStatuses)
    {
        this.cmbStatus.Items.Add(
new ListItem(StringUtils.FromCamelCase(bugStatus),
                                        bugStatus));
    }
}

You can easily retrieve all the enum ‘values’ as strings using the static Enum.GetNames method. This gives you a list of all the enum values. If all you want to do is directly display the enum values as is then you’re done: You can directly bind those enum values to the drop down’s DataSource.

If you want to display the enum values a little more nicely though you might want to fix the values up a little by converting them from Camel Case to more readable strings for display. The code above takes the original list of enum string values and creates a List of objects which hold the actual enum value and a string representation.

The code above creates a list of anonymous types with Text and Value fields that are bound to the drop down. Using the new type is nice

The result of the above looks something like this:

EnumDropDown

which looks decidedly more user friendly than just the raw enum values.

To make this work the StringUtils.FromCamelCase() function is required which does a rudimentary job of splitting apart camel case text. For kicks ToCamelCase is also listed:

/// <summary>
/// Tries to create a phrase string from CamelCase text.
/// Will place spaces before capitalized letters.
/// 
/// Note that this method may not work for round tripping 
/// ToCamelCase calls, since ToCamelCase strips more characters
/// than just spaces.
/// </summary>
/// <param name="camelCase"></param>
/// <returns></returns>
public static string FromCamelCase(string camelCase)
{
    if (camelCase == null)
        throw new ArgumentException("Null is not allowed for StringUtils.FromCamelCase");

    StringBuilder sb = new StringBuilder(camelCase.Length + 10);
    bool first = true;
    char lastChar = '\0';

    foreach (char ch in camelCase)
    {
        if ( !first && 
             ( char.IsUpper(ch) || 
               char.IsDigit(ch) && !char.IsDigit(lastChar)) )                     
            sb.Append(' ');
        
        sb.Append(ch);
        first = false;
        lastChar = ch;
    }

    return sb.ToString(); ;
}
/// <summary>
/// Takes a phrase and turns it into CamelCase text.
/// White Space, punctuation and separators are stripped
/// </summary>
/// <param name="?"></param>
public static string ToCamelCase(string phrase)
{
    if (phrase == null)
        return string.Empty;

    StringBuilder sb = new StringBuilder(phrase.Length);
    
    // First letter is always upper case
    bool nextUpper = true;

    foreach (char ch in phrase)
    {
        if (char.IsWhiteSpace(ch) || char.IsPunctuation(ch) || char.IsSeparator(ch))
        {
            nextUpper = true;
            continue;
        }

        if (nextUpper)
            sb.Append(char.ToUpper(ch));
        else
            sb.Append(char.ToLower(ch));

        nextUpper = false;
    }
    
    return sb.ToString();
}

 

This is a pretty rudimentary implementation of FromCamelCase and ToCamelCase but these come in handy in a number of situations and the enum conversion is a good example.

A little more generic, please

In the above app I actually have quite a few types, so I end up with a fair bit of code that uses selection values from Enum types. I’ve found it useful to create a generic routine that creates me a List of KeyValuePairs that I can use to load up the enum values for databinding:

/// <summary>
/// Returns a List of KeyValuePair object
/// </summary>
/// <param name="enumeration"></param>
/// <returns></returns>
public static List<KeyValuePair<string,string>> GetEnumList(Enum enumeration)
{
    string[] enumStrings = Enum.GetNames(enumeration.GetType());
    List<KeyValuePair<string,string>> items = new List<KeyValuePair<string,string>>();
    foreach(string enumString in enumStrings)
    {
        items.Add( new KeyValuePair<string,string>(enumString,StringUtils.FromCamelCase(enumString)));
    }
    return items;
}

which can then be used like this:

protected override void OnInit(EventArgs e)
     {
         base.OnInit(e);

         List<KeyValuePair<string, string>> statuses = Utils.GetEnumList(typeof(BugStatusTypes));         
         this.cmbStatus.DataTextField = "Value";
         this.cmbStatus.DataValueField = "Key";
         this.cmbStatus.DataSource = statuses;
this.cmbStatus.DataBind(); }

which isn’t really any less code, but maybe a little easier to remember <s>… Notice that you can bind to the Key and Value fields of the KeyValuePair structure. Notice that I used a KeyValuePair object in the list rather than a dictionary. Dictionary would be easier to use but they are a pain to use in data binding and I can never remember what fields you have to bind to for text and value in the data source.

One advantage of this approach too is that you can cache the list and just reuse it in multiple places :

public class App
{
    public static BugConfiguration Configuration = null;
   
    public static List<KeyValuePair<string,string>> BugStatusListItems
    {
        get 
        { 
            if ( _bugStatusListItems == null)  
                 _bugStatusListItems = Utils.GetEnumList( typeof(BugStatusTypes));            
            return _bugStatusListItems;
        }            
    }
    private static List<KeyValuePair<string,string>> _bugStatusListItems = null;


     static App()
    {
        Configuration = new BugConfiguration();
    }
}

where the App object is a static object that is used to store various configuration/constant information.

Then to bind:

List<KeyValuePair<string, string>> statuses = App.BugStatusListItems;

this.cmbStatus.DataSource = statuses;
this.cmbStatus.DataTextField = "Value";
this.cmbStatus.DataValueField = "Key";
this.cmbStatus.DataBind();

which is easily reusable through an application.

While it’s probably overkill to do this for short simple lists like this, I’m fond of caching repeated lookup lists like this on a static object reference where’s it’s easy to reuse.

Binding Back Enum Data

Once you have a lookup list you and allow selection from it you’ll also need to bind the enum value back to an enum type. I tend to use my own DataBinder control that handles this detail for me automatically, but if you manually do unbinding it’ll look something like this:

Bugs.Entity.Status = Enum.Parse(typeof(BugStatusTypes), this.cmbStatus.SelectedValue);

Remember that the value is the original Enum parsed string and so SelectedValue should retrieve the right value to bind back to whatever object/data item you are binding back to.

Enums as Data?

The whole concept of using Enum values direct as ‘data’ is not something that is appropriate in all situations obviously. In more complex systems this sort of ‘lookup table’ used in the UI is probably better stowed in a database lookup table. And it really only makes sense if your application primarily defines fixed enum values rather than dynamic values that can be added to a table in the database. Using Enums like this is often useful to non-data items like option lists that are not typically part of the data model itself.

However, in simple applications like this bug tracker there’s no separate lookup in the database and creating one just for the purpose of displaying the right value in the UI seems silly.

This concept is also based on the assumption that the Enumeration that’s displayed has enumeration items that are named sensibly and according to .NET Camel Case naming conventions. Obviously if you don’t have reasonably readable Enum values this is not going to produce anything very readable for your users, so be sure that the enum values are appropriate to display as final values. OTOH, I’m a strong believer in using meaningful names in variables and so for me enums almost always have names that could potentially be used in the front end – at least when I created the types…

Finally keep in mind that if localization is important enums are a really bad choice since you can’t localize them in anyway.

As I said at the outset – this is functionality that you’ll likely use only occasionally for internal apps or for a quick input form that needs to be thrown up, but when you do need it’s a nice quick and dirty way to get data to work with into the page.

Related Post:

Enums, Enum Sets, parsing and stuff
Posted in ASP.NET  CSharp  

The Voices of Reason


 

Anndrew Rea
February 20, 2009

# re: Using Enums in List Controls


Kim
February 20, 2009

# re: Using Enums in List Controls

I usually don't like to use enum values as display content. Using a custom DisplayText attribute on the enum value that is extracted in the UI is IMHO more flexible and less code. :-)

public enum BugStatusTypes 
{
    [DisplayText(Text = "Entered")]
    Entered,
    [DisplayText(Text = "In Process")]
    InProcess,
    Fixed,
    ByDesign,
    NotReproducible,
    NoBug,
    None
}


Often times this will save some cross assembly refactorings that would arise as the display text needs to be changed.

You can then extract the display text with a simple method. (Making it an extension methods enables calling it directly off the enum value)
public static string GetDisplayText(this Enum enumValue)
{
    var type = enumValue.GetType();
    MemberInfo[] memberInfo = type.GetMember(enumValue.ToString());

    if (memberInfo == null || memberInfo.Length == 0)
        return enumValue.ToString();

    object[] attributes = memberInfo[0].GetCustomAttributes(typeof(DisplayTextAttribute), false);
    if (attributes == null || attributes.Length == 0)
        return enumValue.ToString();

    return ((DisplayTextAttribute)attributes[0]).Text;
}


The calling code is dead simple.

string displayValue = BugStatusTypes.Entered.GetDisplayText();



The custom attribute
public class DisplayTextAttribute : Attribute
{
    public string Text { get; set; }
}


What do you think?

Simon Ferguson
February 20, 2009

# re: Using Enums in List Controls

Thanks for the tip. FYI, the link to the DataBinder control got mangled.

Orry Rotem
February 20, 2009

# re: Using Enums in List Controls

Nice post.

Another sample in VB can be found here: http://biasecurities.com/blog/2006/text-description-for-enum-values/

Orry Rotem
February 20, 2009

# re: Using Enums in List Controls

Ooops, well obviously not VB :)

JMP
February 20, 2009

# re: Using Enums in List Controls

I had problems with this some time ago, but then I came to the realization that there was never an instance when I really needed to do this. Since I'm either working with a DB or a configuration file for any of my given projects, I stay away from this. The only reason I use enums now is for design time and I have been hard pressed to find instances that I need that kind of support.

Before I use an enum, I try to convince myself that I shouldn't be using a full blown object. If I settle on an enum, then I look at why I shouldn't give myself and my customer the benefit of making it a list of items in a table.

Lee Timmins
February 20, 2009

# re: Using Enums in List Controls

Hi, why not try doing an EnumDataSource control. I'm slightly busy at the mo but here's an example i use to do a LoopDataSource:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Web.UI;

namespace CustomControls
{
    public class LoopDataSource : DataSourceControl
    {
        public static readonly string LoopViewName = "Loop";

        public int LoopFrom
        {
            get
            {
                object o = ViewState["LoopFrom"];
                return o != null ? (int)o : DateTime.UtcNow.Year;
            }
            set
            {
                ViewState["LoopFrom"] = value;
            }
        }

        public int LoopTo
        {
            get
            {
                object o = ViewState["LoopTo"];
                return o != null ? (int)o : DateTime.UtcNow.Year - 100;
            }
            set
            {
                ViewState["LoopTo"] = value;
            }
        }

        private LoopDataSourceView _loopView;
        private LoopDataSourceView LoopView
        {
            get
            {
                if (_loopView == null)
                    _loopView = new LoopDataSourceView(this, LoopViewName);

                return _loopView;
            }
        }

        protected override DataSourceView GetView(string viewName)
        {
            if (string.IsNullOrEmpty(viewName) || string.Compare(viewName, LoopViewName, StringComparison.OrdinalIgnoreCase) == 0)
                return this.LoopView;

            throw new ArgumentOutOfRangeException("viewName");
        }

        protected override ICollection GetViewNames()
        {
            return new string[] { LoopViewName };
        }

        private sealed class LoopDataSourceView : DataSourceView
        {
            private LoopDataSource _owner;

            public LoopDataSourceView(LoopDataSource owner, string viewName)
                : base(owner, viewName)
            {
                _owner = owner;
            }

            protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
            {
                List<string> loop = new List<string>();

                for (int i = _owner.LoopFrom; i <= _owner.LoopTo; i++)
                {
                    loop.Add(i.ToString());
                }

                return loop;
            }
        }
    }
}


Hope this helps.

Rick Strahl
February 20, 2009

# re: Using Enums in List Controls

@Kim - thanks for the pointer on the DisplayText attribute. I had no idea there was such a thing. One problem with that is that you have to implement the DisplayText explicitly. Sometimes that may not be possible such as when you're dealing with external types that you don't control so the generic routine still has merit for the more generic use case.

@JMP - Configuration settings is usually my most common use case for using Enums in the UI. I use my configuration classes that typically have a few enums associated with them. These values do not exist in a database (usually they are persisted in .config files) and so using the enum for display is a reasonable option IMHO.

If a database is already used and lookup tables exist then it might make sense, but if you end up only duplicating the values that are already definied as Enums (because they DO have to match the enums in the data/configuration model) then what's the point of redefining them? Just one more thing that has to be kept in sync.

Michael Teper
February 20, 2009

# re: Using Enums in List Controls

Funny, I was just reviewing similar code as this popped into my reader. There's already a Framework attribute that can be used for this.

        /// <summary>
        ///        Returns descriptions (if available) or enum names (if not) for all
        ///        values in the specified Enum.
        /// </summary>
        /// <param name="enumType">The Enum type.</param>
        public static string[] GetDescriptions(Type enumType)
        {
            List<string> values = new List<string>();
            FieldInfo[] fieldInfos = enumType.GetFields(BindingFlags.Public | BindingFlags.Static);
            foreach (FieldInfo fieldInfo in fieldInfos)
            {
                DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (attributes.Length > 0)
                {
                    values.Add(attributes[0].Description);
                }
                else
                {
                    values.Add(fieldInfo.GetValue(fieldInfo.Name).ToString());
                }
            }
            return values.ToArray();
        }

        /// <summary>
        ///        Get a dictionary containing Id/Description pairs for the supplied enum type.
        /// </summary>
        /// <param name="enumType"></param>
        /// <returns></returns>
        public static IDictionary<object, string> GetIdDescriptionPairs(Type enumType)
        {
            Array values = Enum.GetValues(enumType);
            string[] descriptions = GetDescriptions(enumType);

            Debug.Assert(values.Length == descriptions.Length);

            Dictionary<object, string> pairs = new Dictionary<object, string>(values.Length);
            for (int i = 0; i < values.Length; i++)
            {
                pairs.Add(values.GetValue(i), descriptions[i]);
            }
            return pairs;
        }

Mark
February 21, 2009

# re: Using Enums in List Controls

Nice article, though I'd probably prefer the solution using attributes.

I wonder: How did "NoBug" become "Not a bug" ? There seems more to be going on than only camelcase transformation...

Speaking of the DataBinder: I use a patched up version of the DataBinder in some projects. Do you think about publishing an updated version ?

Richard
February 22, 2009

# re: Using Enums in List Controls

Why not resources?
If you use Enum-name+Enum-value as the resourcekey, you'll get even less code and the added benefits of allowing multi lingual applications.

Rick Strahl
February 22, 2009

# re: Using Enums in List Controls

@Richard - I suppose you mean doing resource lookups for the list items right? Resources are good if you are localizing anyway, but a pain if you're not. Also getting resources is not really generic unless you use a solid convention to retrieve the resources consistently (a pattern as you mentioned).

Jerome Laban
February 22, 2009

# re: Using Enums in List Controls

This is an good technique if you're not planning to localize your application, which in the case of an enterprise application, *always* comes sooner or later.

As a best practice, I've come to not use reflected names in any way to display them to the user, mostly because updating a whole application that uses that kind of technique at multiple places is kind of a pain... Also, remember that if your assemblies will ever be obfuscated, you'll be facing the same issues, and you might have to selectively obfuscate.

I'd suggest the same technique Richard did, using a mix of reflection and resources.

Rick Strahl
February 22, 2009

# re: Using Enums in List Controls

@Mark - you caught me :-}. There are multiple places where the enum dropdown is used and when I took the screen shot I didn't realize that I accidentally picked one I hadn't converted yet - in the example the text indeed as manual values I had defined in the page. This has since been turned over into the same App.BugStatusListItems.

Incidentally it takes a little more work to do this in a ListView as above where you manually have to bind each item to the list OnItemCreated:

       protected void lstBugList_ItemCreated(object sender, ListViewItemEventArgs e)
        {
            ListViewDataItem dataItem = e.Item as ListViewDataItem;

int moreCode = DropDownList list = dataItem.FindControl("lstStatus") as DropDownList; list.DataSource = App.BugStatusListItems; list.DataTextField = "Value"; list.DataValueField = "Key"; list.DataBind(); list.SelectedValue = (dataItem.DataItem as wwa_BugReport).Status as string; }

Xerxes
February 22, 2009

# re: Using Enums in List Controls

I've blogged about this problem in the past, and have an entirely different solution to the problem of strongly-typing your domain values. It provides the flexibility of allowing for textual descriptions to your values, without the nasty string parsing.

http://www.xerxesb.com/2008/strongly-typing-your-domain-values/

The attribute solution is also quite neat, but then requires reflection on the other side to extract the description from the values.

AC
February 22, 2009

# re: Using Enums in List Controls

A good bit of code, but have to agree with @Richard that it's probably better in the long run to localize them with a resource dll and be done with it. I agree that localization is initially a little more painful, but I just made a call LocalizedEnum( Enum value ) which does the trick and is in reality a lot less code.

It also helps when the stakeholders want a different name for it. You can end up in a tail-wagging-the-dog situation where you construct your enums so that the print well in the UI, or renaming enums to suit changing business lingo. That might not be a bad thing really, but it does make for more pain if that enum is public.

Depechie
February 27, 2009

# re: Using Enums in List Controls

Hey Rick... Great post! This could help out for much of my 'resource' linking with enums ( that are just dump string replacements of the enum itself ;)
One small tip, you can use generics to parse strings back to the original Enum to get nicer code!

    /// <summary>
    /// Now you can use Generics to have cleaner code when enum parsing!
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <example>
    /// CT.Organ organNewParse = Enum<CT.Organ>.Parse("LENS");
    /// </example>
    public static class Enum<T>
    {
        public static T Parse(string value)
        {
            return (T)Enum.Parse(typeof(T), value);
        }

        public static T Parse(string value, bool ignoreCase)
        {
            return (T)Enum.Parse(typeof(T), value, ignoreCase);
        }

        public static IList<T> GetValues()
        {
            IList<T> list = new List<T>();
            foreach (object value in Enum.GetValues(typeof(T)))
            {
                list.Add((T)value);
            }
            return list;
        }
    }

Alan
April 11, 2009

# re: Using Enums in List Controls

Hi,

A method I have used and found practical is to use resource files. If the Enumeration is the key in the resource file then you can easily look up the value in the Resource files-this also has the advantage that if your application goes global it is very easy to add translations into your project for other languages.

Keep up the excellent blog Rick.

Thanks,

Alan

Rick Strahl
April 13, 2009

# re: Using Enums in List Controls

@Alan - Great point to make about using Resources to allow UI customization.

I was just reminded of this the other day as I was working on a localization project. Using resources is an easy way to customize the UI somewhat dynamically especially if you're using a data driven resource provider that doesn't require static resources that need to get compiled.

Kyi
August 07, 2009

# re: Using Enums in List Controls

Hi,

This is a great article for enum and I enjoy reading it. Also I learned the stuff of how to paly around with Enum from your post. But I found one problem with your code. I'm not so sure whether you got the same error or not. When you call the GetEnumList(Enum enumeration) method to return list of key and value pairs of objects, you are passing the Type like this, Utils.GetEnumList(typeof(BugStatusTypes)) by using typeof(). It gives me runtime error : cannot convert from System.Type to System.Enum. I notice that you pass the Type and receive with specific Enum type. So i changed the method as follow and it works after that.

public static List<KeyValuePair<string, string>> GetEnumList(Type enumeration)
        {
            string[] enumStrings = Enum.GetNames(enumeration);
            List<KeyValuePair<string, string>> items = new List<KeyValuePair<string, string>>();
            foreach (string enumString in enumStrings)
            {
                items.Add(new KeyValuePair<string, string>(enumString, StringUtils.FromCamelCase(enumString)));
            }
            return items;
        }


Correct me if i'm wrong and i am still a newbie in c# and asp.net.

Tony
February 15, 2010

# re: Using Enums in List Controls

http://tonesdotnetblog.wordpress.com/2010/02/14/string-enumerations-and-resource-files-in-c/ has an alternative solution involving passing the resource file and resource string name constant via attributes to enumerations, and these can then be read in as strings using extension methods.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024