Using Enums in List Controls
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:
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 stuffOther Posts you might also like
The Voices of Reason
# re: Using Enums in List Controls
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?
# re: Using Enums in List Controls
# re: Using Enums in List Controls
Another sample in VB can be found here: http://biasecurities.com/blog/2006/text-description-for-enum-values/
# re: Using Enums in List Controls
# re: Using Enums in List Controls
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.
# re: Using Enums in List Controls
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.
# re: Using Enums in List Controls
@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.
# re: Using Enums in List Controls
/// <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; }
# re: Using Enums in List Controls
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 ?
# re: Using Enums in List Controls
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.
# re: Using Enums in List Controls
# re: Using Enums in List Controls
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.
# re: Using Enums in List Controls
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; }
# re: Using Enums in List Controls
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.
# re: Using Enums in List Controls
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.
# re: Using Enums in List Controls
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; } }
# re: Using Enums in List Controls
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
# re: Using Enums in List Controls
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.
# re: Using Enums in List Controls
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.
# re: Using Enums in List Controls
http://weblogs.asp.net/andrewrea/archive/2008/11/03/a-custom-dropdownlist-using-an-enum-type-as-a-datasource.aspx