Enums are simple collections of constant values, using an underlying integer to store the value. Enums implement IComparable, IFormattable and IConvertible while still being value types and give us everything we need to store and use things like status values, action codes, colours etc…
You can use enum values in both server and client code, but when the user interface displays an enum, or when the user must be able to choose enums from a combobox, we can run into complications with enums. Making enum display values more human readable and showing values in a particular order are not that easy to implement. In this article, I will show you a solution used in a WPF client consuming WCF web services.
Lets say we want to create a web service for a laundry process. To track the status of a piece of clothing, we create an enum in the data contract:
1: public enum LaundryStatus
2: {
3: [EnumMember]
4: InHamper= 0,
5: [EnumMember]
6: InLaundryBatch = 1,
7: [EnumMember]
8: WashingInProgress = 2,
9: [EnumMember]
10: WaitingForDryer = 3,
11: [EnumMember]
12: DryingInProgress = 4,
13: [EnumMember]
14: Dried = 5
15: }
Simple, right? We have 6 statuses which we can now use in both server and client code. We have to decorate the class with DataContractAttribute and each enum value with an EnumMemberAttribute to have WCF serialize and de-serialize them properly, but the code declaring the laundry statuses still looks pretty simple. We can write code that is easily understood and easy to maintain:
1: switch (item.Status)
2: {
3: case InHamper:
4: case Dried:
5: beingProcessed = false;
6: break;
7: case InLaundryBatch:
8: case WashingInProgress:
9: case WaitingForDryer:
10: beingProcessed = true;
11: break;
12: }
Improving readability
The first improvement you will want to make, is to have proper display strings. People like spaces, commas an capitals in proper places when they look at a user interface; users will want to see “Drying is in progress” rather than “DryingInProgress”. How do we accomplish that ? We can use the EnumMemberAttribute’s Value property to declare a human friendly string:
1: public enum LaundryStatus
2: {
3: [EnumMember(Value = "In Hamper")]
4: InHamper= 0,
5: [EnumMember(Value = "In Laundry Batch")]
6: InLaundryBatch = 1,
7: [EnumMember(Value = "Washing is in progress")]
8: WashingInProgress = 2,
9: [EnumMember(Value = "Washing finished, waiting for dryer")]
10: WaitingForDryer = 3,
11: [EnumMember(Value = "Drying is in progress")]
12: DryingInProgress = 4,
13: [EnumMember(Value = "Dried")]
14: Dried = 5
15: }
In the client (WPF in these examples), we use a converter to change the enum value into its human readable string:
1: /// <summary>
2: /// Enum utilities
3: /// </summary>
4: public class EnumConverter: IValueConverter
5: {
6: /// <summary>
7: /// Converts a an Enum value to it's string.
8: /// </summary>
9: public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10: {
11: return StringValue(value as Enum);
12: }
13: /// <summary>
14: /// Converts an Enum's string value to the Enum value.
15: /// </summary>
16: public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
17: {
18: return EnumValue(value as string, targetType);
19: }
20: private static string StringValue(System.Enum value)
21: {
22: if (value == null)
23: return "";
24: FieldInfo fi = value.GetType().GetField(value.ToString());
25: EnumMemberAttribute[] attributes = (EnumMemberAttribute[])fi.GetCustomAttributes(typeof(EnumMemberAttribute), false);
26: if (attributes.Length > 0)
27: {
28: return attributes[0].Value;
29: }
30: else
31: {
32: return value.ToString();
33: }
34: }
35: private static object EnumValue(string value, Type enumType)
36: {
37: string[] names = System.Enum.GetNames(enumType);
38: foreach (string name in names)
39: {
40: if (StringValue((System.Enum)System.Enum.Parse(enumType, name)).Equals(value))
41: {
42: return System.Enum.Parse(enumType, name);
43: }
44: }
45: throw new ArgumentException("The string is not a description or value of the specified enum.");
46: }
47: }
Now we can declare this converter and use it in a style:
1: <UserControl.Resources>
2: <Converters:EnumConverter x:Key="EnumConverter"/>
3: <Style x:Key="EnumCombobox" TargetType="ComboBox">
4: <Style.BasedOn>
5: <StaticResource ResourceKey="{x:Type ComboBox}" />
6: </Style.BasedOn>
7: <Setter Property="ItemTemplate">
8: <Setter.Value>
9: <DataTemplate>
10: <TextBlock Text="{Binding Path=.,Mode=OneWay, Converter={StaticResource EnumConverter}}"/>
11: </DataTemplate>
12: </Setter.Value>
13: </Setter>
14: </Style>
15: <ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type system:Enum}" x:Key="LaundryStatusValues">
16: <ObjectDataProvider.MethodParameters>
17: <x:TypeExtension TypeName="dataContract:LaundryStatus"/>
18: </ObjectDataProvider.MethodParameters>
19: </ObjectDataProvider>
20: </UserControl.Resources>
We apply the EnumComboBox style to a combobox showing our LaundryStatusValues:
1: <ComboBox Name="LaundryStatusCmb" Style="{StaticResource EnumCombobox}"
2: ItemsSource="{Binding Source={StaticResource LaundryStatusValues}, Mode=OneWay}"
3: SelectedItem="{Binding Path=CurrentItem.LaundryStatus, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
This will now show the human readable strings while still binding CurrentItem.LaundryStatus to the enum value.
Sorting
By default the enums will be shown by ascending value. In the case of our laundry types, “In Hamper” will come first because it has a value of 0, followed by “Washing is in progress” because of its value of 1 etc… How do you change the order in which these are shown ? You could of course re-arrange the enum values to accomplish this, but that could break compatibility with previous versions.
The solution is to use a Sort attribute:
1: /// <summary>
2: /// Attribute for enums to specify member sort order.
3: /// </summary>
4: public class SortAttribute: Attribute
5: {
6: public int Order { get; set; }
7: public SortAttribute(int order)
8: {
9: this.Order = order;
10: }
11: public static SortAttribute GetSortOrder(Enum e)
12: {
13: return (SortAttribute)Attribute.GetCustomAttribute(e.GetType().GetField(Enum.GetName(e.GetType(), e)),typeof(SortAttribute));
14: }
15: /// <summary>
16: /// Sort an Enum whose members have the Sort attribute.
17: /// </summary>
18: /// <typeparam name="T">Enum type to sort.</typeparam>
19: /// <returns>Sorted Enumerable of Enum values.</returns>
20: public static IEnumerable<T> SortEnum<T>() where T : struct, IComparable, IConvertible
21: {
22: if (! typeof(T).IsEnum)
23: throw new ArgumentException("T must be an enum");
24: Dictionary<int, T> orderHash = new Dictionary<int, T>();
25: foreach (T value in Enum.GetValues(typeof (T)))
26: {
27: orderHash[SortAttribute.GetSortOrder(value as Enum).Order] = value;
28: }
29: return orderHash.OrderBy(pair => pair.Key).Select(pair => pair.Value);
30: }
31: }
Now we can specify a sort order for our LaundryStatus:
1: [DataContract(Name = "LandryStatus", Namespace = "http://chasmx.com/2013"]
2: public enum LaundryStatus
3: {
4: [EnumMember(Value = "In Hamper")]
5: [Sort(6)]
6: InHamper= 0,
7: [EnumMember(Value = "In Laundry Batch")]
8: [sort(5)]
9: InLaundryBatch = 1,
10: [EnumMember(Value = "Washing is in Progress")]
11: [Sort(4)]
12: WashingInProgress = 2,
13: [EnumMember(Value = "Washing finished, waiting for dryer")]
14: [Sort(3)]
15: WaitingForDryer = 3,
16: [EnumMember(Value = "Drying is in progress")]
17: [Sort(2)]
18: DryingInProgress = 4,
19: [EnumMember(Value = "Dried")]
20: [Sort(1)]
21: Dried = 5
22: }
We display LaundryStatus values sorted by the specified sort order by adding a property to the viewmodel (or view code behind) that returns the sorted enum:
1: /// <summary>
2: /// A sorted list of status actions.
3: /// </summary>
4: public IEnumerable<LoundryStatus> SortedLaundryStatusValues
5: {
6: get
7: {
8: return SortAttribute.SortEnum<LaundryStatus>();
9: }
10: }
11: <ComboBox Name="LaundryStatusCmb" Style="{StaticResource EnumCombobox}"
12: ItemsSource="{Binding Path=SortedLaundryStatusValues, Mode=OneWay}"
13: SelectedItem="{Binding Path=CurrentItem.LaundryStatus, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
14: />
Now our combobox will show the enum values in the specified sort order and still uses human friendly strings:
This shows that by decorating your enums with the appropriate attributes you can meet all GUI needs while keeping all information about the enum values, such as sort order and visible string, in one place.


