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 });
}
}
}