Click here to Skip to main content
15,896,308 members
Articles / Desktop Programming / WPF

Displaying User Friendly Enum Values in WPF

Rate me:
Please Sign up or sign in to vote.
4.94/5 (25 votes)
12 Mar 2014CPOL13 min read 105.9K   1.7K   68  
Presents a helper class to easily display user-friendly enum value representations including customizable and localizable text, images, and arbitrary XAML content. This class can also be used as a general XAML switch statement for enum and non-enum classes.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.ComponentModel;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Windows;

namespace EnumBindings
{
    public class EnumItemTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return false;
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return destinationType != typeof(UIElement);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            EnumItem item = value as EnumItem;
            if( item != null )
            {
                if( item.Value == null )
                {
                    if( value == null ) return null;
                    return null;
                }
                else if (destinationType == item.Value.GetType())
                {
                    return item.Value;
                }
                else if (destinationType == typeof(String))
                {
                    return item.ToString();
                }
                else
                {
                    TypeConverter converter = TypeDescriptor.GetConverter(destinationType);
                    if (converter.CanConvertFrom(item.Value.GetType()))
                    {
                        return converter.ConvertFrom(item.Value);
                    }
                }
                throw new InvalidOperationException("Unable to convert item value to destination type.");
            }
            throw new InvalidOperationException("EnumItemTypeConverter can only convert non-null EnumItems.");
        }
    }

    /// <summary>
    /// Enumerates the properties for EnumItem.
    /// </summary>
    public enum EnumItemsOrder
    {
        Custom,
        Value,
        DisplayValue,
        Text
    }

    /// <summary>
    /// Represents a list of enumeration values and corresponding values to be shown or used in the user interface.
    /// </summary>
    [Serializable()]
    public class EnumItemList : ObservableCollection<EnumItem>, IValueConverter
    {

    #region Constructors
        
        /// <summary>
        /// Creates a new empty EnumItemList.
        /// </summary>
        public EnumItemList()
        {
            SortBy = EnumItemsOrder.Text;
        }

        /// <summary>
        /// Creates a new EnumItemList for the given enumeration type.
        /// </summary>
        /// <param name="enumType">Type of enumeration values</param>
        public EnumItemList(Type enumType)
        {
            SortBy = EnumItemsOrder.Text;
            EnumType = enumType;
        }
        
        /// <summary>
        /// Creates a new EnumItemList. 
        /// </summary>
        /// <param name="enumType">Type of enumeration values</param>
        /// <param name="items">Collection to copy enumeration items from.</param>
        public EnumItemList(Type enumType, IEnumerable<EnumItem> items)
            : base(items)
        {
            SortBy = EnumItemsOrder.Text;
            EnumType = enumType;
        }
		 
	#endregion

    #region Private members


        private Type m_enumType;

        private TypeConverter m_converter;

        
    #endregion       
        
    #region Public methods

        /// <summary>
        /// Converts the Value of an <see cref="EnumItem"/> item to the enumeration type of this EnumItemList.
        /// </summary>
        /// <param name="item"></param>
        public void ConvertValueToEnumType(EnumItem item)
        {
            if (m_enumType != null && item.Value != null && (m_enumType.IsAssignableFrom(item.Value.GetType()) == false))
            {
                item.Value = m_converter.ConvertFrom(null, CultureInfo.InvariantCulture, item.Value);
            }
        }

        /// <summary>
        /// Finds the EnumItem corresponding to the given enumeration value
        /// </summary>
        /// <param name="enumValue">Enumeration value to search for</param>
        /// <returns>The corresponding EnumItem or <c>null</c> if no match was found.</returns>
        public EnumItem FindItemByValue(object enumValue)
        {
            int index = IndexOfValue(enumValue);
            return index >= 0 ? this[index] : null;
        }

        /// <summary>
        /// Finds the index of the given enumeration value
        /// </summary>
        /// <param name="enumValue">The enumeration value to search for.</param>
        /// <returns>The zero-based index of the value or -1 if not found.</returns>
        public int IndexOfValue(object enumValue)
        {
            for (int index = 0; index < Count; index++)
            {
                if (Object.Equals(this[index].Value, enumValue)) return index;
            }
            return -1;
        }

        /// <summary>
        /// Finds an EnumItem with the given DisplayValue.
        /// </summary>
        /// <param name="displayValue">DisplayValue to search for</param>
        /// <returns>The EnumItem with the given DisplayValue or <c>null</c> if value not found
        /// </returns>
        public EnumItem FindItemByDisplayValue(object displayValue)
        {
            for (int index = 0; index < Count; index++)
            {
                EnumItem item = this[index];
                if (Object.Equals(item.Value, displayValue)) return item;
            }
            return null;
        }
        
     #endregion

    #region Public properties

        /// <summary>
        /// Gets or sets the type of enumeration that is handled by this EnumItemList.
        /// </summary>
        /// <remarks>
        /// When setting this property all existing values will be converted to the specified type. If the type
        /// is an <c>enum</c> the list will be populated with all available enum values.
        /// This property can only be set once.
        /// </remarks>
        /// <exception cref="System.ArgumentNullException">If value is <c>null</c>.</exception>
        /// <exception cref="InvalidOperationException">If the property has already been set to another value.</exception>
        public Type EnumType
        {
            get { return m_enumType; }
            set
            {
                if (value == null) throw new ArgumentNullException();

                if (m_enumType == value) return;

                if (m_enumType != null) throw new InvalidOperationException("The EnumType value cannot be changed once set.");

                m_enumType = value;

                // Cache TypeConverter to increase performance.
                m_converter = TypeDescriptor.GetConverter(m_enumType);

                // Convert any existing item values to type.
                foreach (EnumItem item in this)
                {
                    ConvertValueToEnumType(item);
                }

                //For enum types, fill list with values and their names 
                if (m_enumType.IsEnum)
                {
                    // For all public static fields, i.e. all enum values
                    foreach (FieldInfo field in m_enumType.GetFields(BindingFlags.Public | BindingFlags.Static))
                    {
                        object fieldValue = field.GetValue(null);

                        // Look for DescriptionAttributes.
                        object[] descriptions = field.GetCustomAttributes(typeof(DescriptionAttribute), true);

                        string displayName;
                        if (descriptions.Length > 0)
                        {
                            displayName = ((DescriptionAttribute)descriptions[0]).Description;
                        }
                        else
                        {
                            try
                            {
                                // Use type converter to support enums with custom TypeConverters.
                                displayName = m_converter.ConvertToString(fieldValue);
                            }
                            catch (Exception ex)
                            {
                                displayName = field.Name;
                            }
                        }
                        EnumItem item = new EnumItem() { Value = fieldValue, DisplayValue = displayName };
                        
                        if (IndexOfValue(item.Value) < 0)   // Do not add item if it already exists.
                        {
                            Add(item);
                        }
                    }
                }
                OnPropertyChanged(new PropertyChangedEventArgs("EnumType"));
            }
        }

        public void Sort(IComparer<EnumItem> comparer)
        {
            int newIndex = 0;
            foreach (EnumItem item in Items.OrderBy(k => k, comparer))
            {
                int oldIndex = Items.IndexOf(item);
                if (oldIndex != newIndex)
                {
                    Move(oldIndex, newIndex);
                }
                newIndex++;
            }
        }

        private class EnumItemComparer : IComparer<EnumItem>
        {
            private EnumItemsOrder m_sortBy;

            public EnumItemComparer(EnumItemsOrder sortBy)
            {
                m_sortBy = sortBy;
            }

            #region IComparer<EnumItem> Members

            public int Compare(EnumItem x, EnumItem y)
            {
                switch (m_sortBy)
                {
                    case EnumItemsOrder.Value:
                        return Comparer<Object>.Default.Compare(x.Value, y.Value);
                        
                    case EnumItemsOrder.DisplayValue:
                        return Comparer<Object>.Default.Compare(x.Value, y.Value);
                        
                    case EnumItemsOrder.Text:
                        return StringComparer.CurrentCultureIgnoreCase.Compare(x.Text, y.Text);
                    default:
                        return 0;
                }
            }

            #endregion
        }

        private IComparer<EnumItem> m_comparer;

        public IComparer<EnumItem> Comparer
        {
            get { return m_comparer; }
            set {
                m_comparer = value; OnPropertyChanged(new PropertyChangedEventArgs("Comparer"));
                if (m_comparer != null)
                {
                    Sort(m_comparer);
                }
            }
        }
        

        private EnumItemsOrder m_sortBy;

        public EnumItemsOrder SortBy
        {
            get { return m_sortBy; }
            set {
                if (value == m_sortBy) return;
                Comparer = m_sortBy == EnumItemsOrder.Custom ? null : new EnumItemComparer(value);
                m_sortBy = value; 
                OnPropertyChanged(new PropertyChangedEventArgs("SortBy"));
            }
        }
         

        private String m_sortViewBy;



        /// <summary>
        /// Gets or sets the order in which the default collection view of this list is sorted.
        /// </summary>
        /// <remarks>
        /// The value must be name of a EnumItem property, optionally followed by the keyword DESC or DESCENDING
        /// to specify descending order, i.e. "Text DESC".
        /// </remarks>
        public String SortDefaultViewBy
        {
            get { return m_sortViewBy; }
            set
            {
                if (value == m_sortViewBy) return;

                ICollectionView view = CollectionViewSource.GetDefaultView(this);
                view.SortDescriptions.Clear();
                if (String.IsNullOrWhiteSpace(value) == false)
                {
                    string[] tokens = value.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
                    if (tokens.Length > 2) throw new ArgumentException("Invalid sort expression");
                    ListSortDirection direction = ListSortDirection.Ascending;
                    if (tokens.Length == 2)
                    {
                        if (tokens[1].Equals("DESC", StringComparison.InvariantCultureIgnoreCase) ||
                            tokens[1].Equals("DESCENDING", StringComparison.InvariantCultureIgnoreCase))
                        {
                            direction = ListSortDirection.Descending;
                        }
                        else
                        {
                            throw new ArgumentException("Invalid sort expression");
                        }
                    }
                    view.SortDescriptions.Add(new SortDescription(tokens[0], direction));
                }
                m_sortViewBy = value;
                OnPropertyChanged(new PropertyChangedEventArgs("SortDefaultViewBy"));
            }
        }

        private EnumItem m_defaultItem;

        /// <summary>
        /// Gets or sets the default <see cref="EnumItem"/> that is returned when no other matching item is found
        /// during conversion.
        /// </summary>
        public EnumItem DefaultItem
        {
            get { return m_defaultItem; }
            set { m_defaultItem = value; OnPropertyChanged(new PropertyChangedEventArgs("DefaultItem")); }
        }
        

     #endregion
        
    #region Protected members

        /// <summary>
        /// Called when a new item is added from XAML or code.
        /// </summary>
        /// <param name="index">Index of new item.</param>
        /// <param name="item">Item to insert</param>
        /// <remarks>
        /// This is overridden to 
        /// 1) Convert value to EnumType
        /// 2) Ensure that there is only one item for each unique value. 
        /// In case there is already an item with the same value that one will be removed. 
        /// </remarks>
        protected override void InsertItem(int index, EnumItem item)
        {
            if (item == null) throw new ArgumentNullException();

            ConvertValueToEnumType(item);

            // search for existing item with same value.
            int oldIndex = IndexOfValue(item.Value);
            // remove old item with same value.
            if (oldIndex >= 0)
            {
                
                RemoveAt(oldIndex);
                if (oldIndex < index)
                {
                    index--;
                }
            }

            if (m_comparer != null)
            {
                index = ((List<EnumItem>)Items).BinarySearch(item, m_comparer);
                if( index < 0 ) index = ~index;
            }
            
            base.InsertItem(index, item);

            
        }

	#endregion
        
    #region IValueConverter Members

        /// <summary>
        /// Converts a enumeration value to the corresponding EnumItem found in this list
        /// </summary>
        /// <param name="value"></param>
        /// <param name="targetType"></param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns></returns>
        object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value.GetType() != m_enumType && value != null && m_converter != null)
            {
                value = m_converter.ConvertFrom(null, culture, value);
            }
            EnumItem item = FindItemByValue(value);
            if (item == null)
            {
                if (DefaultItem != null)
                {
                    // Return a clone of the default item with the Value set to passed in value.
                    return new EnumItem() { Value = value, DisplayValue = DefaultItem.DisplayValue, Text = DefaultItem.Text };
                }
                else
                {
                    throw new ArgumentException(String.Format("'{0}' was not recognized as a valid enumeration value.", value));
                }
            }
            return item;
        }



        object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            EnumItem item = value as EnumItem;
            if (item != null && Contains(item)) return item.Value;
            item = FindItemByDisplayValue(value);
            if (item != null) return item.Value;
            throw new ArgumentException(String.Format("'{0}' was not recognized as a valid enumeration value.", value));
        }


        #endregion
    }

    /// <summary>
    /// A generic version of <see cref="EnumItemList"/> which provides type-safe methods to add items
    /// programmatically.
    /// </summary>
    /// <typeparam name="TEnum">Type of enumeration to be managed by this list.</typeparam>
    public class EnumItemList<TEnum> : EnumItemList
    {
        public EnumItemList()
        {
            EnumType = typeof(TEnum);
        }

        public void AddItem(TEnum value, object displayValue, string itemText)
        {
            Add(new EnumItem() { Value = value, DisplayValue = displayValue, Text = itemText });
        }

        public void AddItem(TEnum value, object displayValue)
        {
            Add(new EnumItem() { Value = value, DisplayValue = displayValue });

        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Software Developer
Sweden Sweden
Henrik Jonsson is a Microsoft Professional Certified Windows Developer (MCPD) that currently works as an IT consultant in Västerås, Sweden.

Henrik has worked in several small and large software development projects in various roles such as architect, developer, CM and tester.

He regularly reads The Code Project articles to keep updated about .NET development and get new ideas. He has contributed with articles presenting some useful libraries for Undo/Redo, Dynamic Linq Sorting and a Silverlight 5 MultiBinding solution.

Comments and Discussions