How do I have an enum bound combobox with custom string formatting for enum values?
在post-enum-toString中,描述了使用自定义属性
1 2 3 4 5 6 7 8 | Enum HowNice { [Description("Really Nice")] ReallyNice, [Description("Kinda Nice")] SortOfNice, [Description("Not Nice At All")] NotNice } |
然后,使用如下语法调用函数
1 | GetDescription<HowNice>(NotNice); // Returns"Not Nice At All" |
但当我只想用枚举的值填充ComboBox时,这并不能真正帮助我,因为我不能强制ComboBox调用
我想要的有以下要求:
- 读取
(HowNice)myComboBox.selectedItem 将返回所选值作为枚举值。 - 用户应该看到用户友好的显示字符串,而不仅仅是枚举值的名称。因此,用户不会看到"
NotNice ",而是看到"Not Nice At All "。 - 希望,解决方案将需要对现有枚举进行最少的代码更改。
显然,我可以为我创建的每个枚举实现一个新的类,并重写它的
有什么想法吗?
见鬼,我甚至会给你一个拥抱作为奖励。
1 2 3 4 5 | myComboBox.FormattingEnabled = true; myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e) { e.Value = GetDescription<HowNice>((HowNice)e.Value); } |
不要!枚举是基元,而不是UI对象-从设计的角度来看,使它们为.toString()中的UI提供服务是非常糟糕的。您试图在这里解决错误的问题:真正的问题是您不希望Enum.ToString()出现在组合框中!
现在这确实是一个非常可解决的问题!您可以创建一个UI对象来表示组合框项:
1 2 3 4 5 6 7 | sealed class NicenessComboBoxItem { public string Description { get { return ...; } } public HowNice Value { get; private set; } public NicenessComboBoxItem(HowNice howNice) { Value = howNice; } } |
然后只需将此类的实例添加到组合框的项集合中,并设置这些属性:
1 2 | comboBox.ValueMember ="Value"; comboBox.DisplayMember ="Description"; |
TypeConverter。我想这就是我要找的。西蒙·斯文森万岁!
1 2 3 4 5 6 7 8 9 | [TypeConverter(typeof(EnumToStringUsingDescription))] Enum HowNice { [Description("Really Nice")] ReallyNice, [Description("Kinda Nice")] SortOfNice, [Description("Not Nice At All")] NotNice } |
在当前枚举中,我只需要在声明之前添加此行。
1 |
一旦我这样做,任何枚举都将使用其字段的
噢,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | public class EnumToStringUsingDescription : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return (sourceType.Equals(typeof(Enum))); } public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return (destinationType.Equals(typeof(String))); } public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { return base.ConvertFrom(context, culture, value); } public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { if (!destinationType.Equals(typeof(String))) { throw new ArgumentException("Can only convert to string.","destinationType"); } if (!value.GetType().BaseType.Equals(typeof(Enum))) { throw new ArgumentException("Can only convert an instance of enum.","value"); } string name = value.ToString(); object[] attrs = value.GetType().GetField(name).GetCustomAttributes(typeof(DescriptionAttribute), false); return (attrs.Length > 0) ? ((DescriptionAttribute)attrs[0]).Description : name; } } |
这对我的ComboBox案件有帮助,但显然并没有覆盖
您可以编写一个类型转换器来读取指定的属性,以便在资源中查找它们。因此,您可以获得对显示名称的多语言支持,而不需要太多麻烦。
查看typeconverter的convertfrom/convertto方法,并使用反射读取枚举字段上的属性。
使用枚举示例:
1 2 3 4 5 6 7 8 9 10 11 | using System.ComponentModel; Enum HowNice { [Description("Really Nice")] ReallyNice, [Description("Kinda Nice")] SortOfNice, [Description("Not Nice At All")] NotNice } |
创建扩展名:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public static class EnumExtensions { public static string Description(this Enum value) { var enumType = value.GetType(); var field = enumType.GetField(value.ToString()); var attributes = field.GetCustomAttributes(typeof(DescriptionAttribute), false); return attributes.Length == 0 ? value.ToString() : ((DescriptionAttribute)attributes[0]).Description; } } |
然后您可以使用如下内容:
1 2 | HowNice myEnum = HowNice.ReallyNice; string myDesc = myEnum.Description(); |
有关详细信息,请参阅:http://www.blackwasp.co.uk/enumdescription.aspx。这一问题的答案将归功于Richrd Carr。
您可以创建一个通用结构,用于具有描述的所有枚举。通过与类之间的隐式转换,除了ToString方法外,变量的工作方式与枚举类似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public struct Described<T> where T : struct { private T _value; public Described(T value) { _value = value; } public override string ToString() { string text = _value.ToString(); object[] attr = typeof(T).GetField(text) .GetCustomAttributes(typeof(DescriptionAttribute), false); if (attr.Length == 1) { text = ((DescriptionAttribute)attr[0]).Description; } return text; } public static implicit operator Described<T>(T value) { return new Described<T>(value); } public static implicit operator T(Described<T> value) { return value._value; } } |
使用实例:
1 2 3 4 | Described<HowNice> nice = HowNice.ReallyNice; Console.WriteLine(nice == HowNice.ReallyNice); // writes"True" Console.WriteLine(nice); // writes"Really Nice" |
做这件事最好的方法是做一个班。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class EnumWithToString { private string description; internal EnumWithToString(string desc){ description = desc; } public override string ToString(){ return description; } } class HowNice : EnumWithToString { private HowNice(string desc) : base(desc){} public static readonly HowNice ReallyNice = new HowNice("Really Nice"); public static readonly HowNice KindaNice = new HowNice("Kinda Nice"); public static readonly HowNice NotVeryNice = new HowNice("Really Mean!"); } |
我相信这是最好的方法。
当填充在组合框中时,将显示漂亮的toString,并且没有人可以再创建类的任何实例这一事实实际上使它成为一个枚举。
另外,可能需要进行一些轻微的语法修正,我对C不太擅长。(爪哇家伙)
我认为你不需要简单地绑定到一个不同的类型就可以做到这一点——至少,不方便。通常,即使不能控制
您可以绑定到描述中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class EnumWrapper<T> where T : struct { private readonly T value; public T Value { get { return value; } } public EnumWrapper(T value) { this.value = value; } public string Description { get { return GetDescription<T>(value); } } public override string ToString() { return Description; } public static EnumWrapper<T>[] GetValues() { T[] vals = (T[])Enum.GetValues(typeof(T)); return Array.ConvertAll(vals, v => new EnumWrapper<T>(v)); } } |
然后绑定到EDOCX1[14]
考虑到您不希望为每个枚举创建类,我建议您创建一个枚举值/显示文本的字典,并将其绑定。
请注意,这依赖于原始日志中的getDescription方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public static IDictionary<T, string> GetDescriptions<T>() where T : struct { IDictionary<T, string> values = new Dictionary<T, string>(); Type type = enumerationValue.GetType(); if (!type.IsEnum) { throw new ArgumentException("T must be of Enum type","enumerationValue"); } //Tries to find a DescriptionAttribute for a potential friendly name //for the enum foreach (T value in Enum.GetValues(typeof(T))) { string text = value.GetDescription(); values.Add(value, text); } return values; } |
无法重写c_中枚举的ToString()。但是,您可以使用扩展方法;
1 2 3 4 5 6 7 8 | public static string ToString(this HowNice self, int neverUsed) { switch (self) { case HowNice.ReallyNice: return"Rilly, rilly nice"; break; ... |
当然,您必须对该方法进行显式调用,即:
1 | HowNice.ReallyNice.ToString(0) |
这不是一个很好的解决方案,有一个switch语句和all,但是它应该可以工作,并且有希望被许多重写…
下面是@scramer answer的一个版本的枚举到字符串类型转换器,它也支持标志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | /// <summary> /// A drop-in converter that returns the strings from /// <see cref="System.ComponentModel.DescriptionAttribute"/> /// of items in an enumaration when they are converted to a string, /// like in ToString(). /// </summary> public class EnumToStringUsingDescription : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return (sourceType.Equals(typeof(Enum))); } public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return (destinationType.Equals(typeof(String))); } public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { return base.ConvertFrom(context, culture, value); } public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { if (destinationType.Equals(typeof(String))) { string name = value.ToString(); Type effectiveType = value.GetType(); if (name != null) { FieldInfo fi = effectiveType.GetField(name); if (fi != null) { object[] attrs = fi.GetCustomAttributes(typeof(DescriptionAttribute), false); return (attrs.Length > 0) ? ((DescriptionAttribute)attrs[0]).Description : name; } } } return base.ConvertTo(context, culture, value, destinationType); } /// <summary> /// Coverts an Enums to string by it's description. falls back to ToString. /// </summary> /// <param name="value">The value.</param> /// <returns></returns> public string EnumToString(Enum value) { //getting the actual values List<Enum> values = EnumToStringUsingDescription.GetFlaggedValues(value); //values.ToString(); //Will hold results for each value List<string> results = new List<string>(); //getting the representing strings foreach (Enum currValue in values) { string currresult = this.ConvertTo(null, null, currValue, typeof(String)).ToString();; results.Add(currresult); } return String.Join(" ",results); } /// <summary> /// All of the values of enumeration that are represented by specified value. /// If it is not a flag, the value will be the only value retured /// </summary> /// <param name="value">The value.</param> /// <returns></returns> private static List<Enum> GetFlaggedValues(Enum value) { //checking if this string is a flaged Enum Type enumType = value.GetType(); object[] attributes = enumType.GetCustomAttributes(true); bool hasFlags = false; foreach (object currAttibute in attributes) { if (enumType.GetCustomAttributes(true)[0] is System.FlagsAttribute) { hasFlags = true; break; } } //If it is a flag, add all fllaged values List<Enum> values = new List<Enum>(); if (hasFlags) { Array allValues = Enum.GetValues(enumType); foreach (Enum currValue in allValues) { if (value.HasFlag(currValue)) { values.Add(currValue); } } } else//if not just add current value { values.Add(value); } return values; } } |
以及使用它的扩展方法:
1 2 3 4 5 6 7 8 9 10 11 | /// <summary> /// Converts an Enum to string by it's description. falls back to ToString /// </summary> /// <param name="enumVal">The enum val.</param> /// <returns></returns> public static string ToStringByDescription(this Enum enumVal) { EnumToStringUsingDescription inter = new EnumToStringUsingDescription(); string str = inter.EnumToString(enumVal); return str; } |
您需要的是将枚举转换为readOnlyCollection,并将集合绑定到组合框(或为此启用的任何键值对控件)。
首先,您需要一个类来包含列表中的项。因为您所需要的只是int/string对,所以我建议您使用接口和基类组合,以便在任何需要的对象中实现该功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public interface IValueDescritionItem { int Value { get; set;} string Description { get; set;} } public class MyItem : IValueDescritionItem { HowNice _howNice; string _description; public MyItem() { } public MyItem(HowNice howNice, string howNice_descr) { _howNice = howNice; _description = howNice_descr; } public HowNice Niceness { get { return _howNice; } } public String NicenessDescription { get { return _description; } } #region IValueDescritionItem Members int IValueDescritionItem.Value { get { return (int)_howNice; } set { _howNice = (HowNice)value; } } string IValueDescritionItem.Description { get { return _description; } set { _description = value; } } #endregion } |
下面是实现它的接口和示例类。请注意,类的键是强类型化到枚举的,并且IValueDescriptionItem属性是明确实现的(因此类可以具有任何属性,您可以选择实现键/值对的属性)。
现在,EnumToReadOnlyCollection类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class EnumToReadOnlyCollection<T,TEnum> : ReadOnlyCollection<T> where T: IValueDescritionItem,new() where TEnum : struct { Type _type; public EnumToReadOnlyCollection() : base(new List<T>()) { _type = typeof(TEnum); if (_type.IsEnum) { FieldInfo[] fields = _type.GetFields(); foreach (FieldInfo enum_item in fields) { if (!enum_item.IsSpecialName) { T item = new T(); item.Value = (int)enum_item.GetValue(null); item.Description = ((ItemDescription)enum_item.GetCustomAttributes(false)[0]).Description; //above line should be replaced with proper code that gets the description attribute Items.Add(item); } } } else throw new Exception("Only enum types are supported."); } public T this[TEnum key] { get { return Items[Convert.ToInt32(key)]; } } } |
因此,您的代码中所需要的就是:
1 2 3 4 5 | private EnumToReadOnlyCollection<MyItem, HowNice> enumcol; enumcol = new EnumToReadOnlyCollection<MyItem, HowNice>(); comboBox1.ValueMember ="Niceness"; comboBox1.DisplayMember ="NicenessDescription"; comboBox1.DataSource = enumcol; |
请记住,集合是用MyItem键入的,因此如果绑定到适当的属性,则组合框值应返回枚举值。
我添加了t this[enum t]属性,使集合比简单的组合可消费的更有用,例如textbox 1.text=enumcol[hownice.reallynice].nicenessdescription;
当然,您可以选择将myitem转换为只用于此purpose的key/value类,有效地跳过了enumtoreadnlycollection的类型参数中的myitem,但随后您将被迫使用int来获取key(意味着获取combobx1.selectedvalue将返回int,而不是枚举类型)。如果您创建一个keyValueItem类来替换myItem等等,您就可以解决这个问题…
我试过这种方法,它对我很管用。
我为枚举创建了一个包装类,并重载隐式运算符,以便将其赋给枚举变量(在我的例子中,我必须将对象绑定到
您可以使用反射来按照您希望的方式格式化枚举值,在我的例子中,我从枚举值(如果存在)中检索
希望这有帮助。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public sealed class EnumItem<T> { T value; public override string ToString() { return Display; } public string Display { get; private set; } public T Value { get; set; } public EnumItem(T val) { value = val; Type en = val.GetType(); MemberInfo res = en.GetMember(val.ToString())?.FirstOrDefault(); DisplayAttribute display = res.GetCustomAttribute<DisplayAttribute>(); Display = display != null ? String.Format(display.Name, val) : val.ToString(); } public static implicit operator T(EnumItem<T> val) { return val.Value; } public static implicit operator EnumItem<T>(T val) { return new EnumItem<T>(val); } } |
编辑:
为了以防万一,我使用以下函数来获取我用于
1 2 3 4 5 6 7 8 9 10 11 12 |
很抱歉把这条旧线弄好了。
我将按照以下方式本地化枚举,因为它可以通过本例中的DropDownList文本字段向用户显示有意义和本地化的值,而不仅仅是描述。
首先,我创建了一个名为owToStringByCulture的简单方法,从全局资源文件中获取本地化字符串,在本例中,它是app_GlobalResources文件夹中的bibongnet.resx。在这个资源文件中,确保所有字符串都与枚举的值相同(reallynice、sortoflice、notnice)。在这个方法中,我传递参数:resourceclassname,它通常是资源文件的名称。
接下来,我创建一个静态方法来用枚举作为数据源填充DropDownList,称为owFillDataWithEnum。此方法稍后可用于任何枚举。
然后在一个名为DropDownList1的DropDownList的页面中,我在页面中设置了只加载以下一行简单代码,以将枚举填充到DropDownList。
1 | BiBongNet.OwFillDataWithEnum<HowNice>(DropDownList1,"BiBongNet"); |
就是这样。我认为,使用类似这些简单方法,可以用任何枚举填充任何列表控件,不仅可以作为描述性值,还可以显示本地化文本。您可以将所有这些方法作为扩展方法来更好地使用。
希望这有帮助。分享,分享!
方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | public class BiBongNet { enum HowNice { ReallyNice, SortOfNice, NotNice } /// <summary> /// This method is for filling a listcontrol, /// such as dropdownlist, listbox... /// with an enum as the datasource. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="ctrl"></param> /// <param name="resourceClassName"></param> public static void OwFillDataWithEnum<T>(ListControl ctrl, string resourceClassName) { var owType = typeof(T); var values = Enum.GetValues(owType); for (var i = 0; i < values.Length; i++) { //Localize this for displaying listcontrol's text field. var text = OwToStringByCulture(resourceClassName, Enum.Parse(owType, values.GetValue(i).ToString()).ToString()); //This is for listcontrol's value field var key = (Enum.Parse(owType, values.GetValue(i).ToString())); //add values of enum to listcontrol. ctrl.Items.Add(new ListItem(text, key.ToString())); } } /// <summary> /// Get localized strings. /// </summary> /// <param name="resourceClassName"></param> /// <param name="resourceKey"></param> /// <returns></returns> public static string OwToStringByCulture(string resourceClassName, string resourceKey) { return (string)HttpContext.GetGlobalResourceObject(resourceClassName, resourceKey); } } |
我将编写一个用于任何类型的通用类。我以前用过类似的东西:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | public class ComboBoxItem<T> { /// The text to display. private string text =""; /// The associated tag. private T tag = default(T); public string Text { get { return text; } } public T Tag { get { return tag; } } public override string ToString() { return text; } // Add various constructors here to fit your needs } |
除此之外,您还可以添加一个静态的"工厂方法"来创建一个给定枚举类型的组合框项列表(与您在那里使用的getDescriptions方法基本相同)。这将使您不必为每种枚举类型实现一个实体,并为"GetDescriptions"帮助器方法提供一个好的/逻辑的位置(我个人将从Enum(t obj)调用它)。
1 2 3 4 5 6 7 8 | Enum HowNice { [Description("Really Nice")] ReallyNice, [Description("Kinda Nice")] SortOfNice, [Description("Not Nice At All")] NotNice } |
要解决此问题,应使用扩展方法和字符串数组,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Enum HowNice { ReallyNice = 0, SortOfNice = 1, NotNice = 2 } internal static class HowNiceIsThis { const String[] strings = {"Really Nice","Kinda Nice","Not Nice At All" } public static String DecodeToString(this HowNice howNice) { return strings[(int)howNice]; } } |
简单的代码和快速的解码。
创建包含所需内容的集合(例如,包含包含
有点像:
1 2 3 | Combo.DataSource = new EnumeratedValueCollection<HowNice>(); Combo.ValueMember ="Value"; Combo.DisplayMember ="Description"; |
当您有这样的集合类时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | using System; using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; namespace Whatever.Tickles.Your.Fancy { public class EnumeratedValueCollection<T> : ReadOnlyCollection<EnumeratedValue<T>> { public EnumeratedValueCollection() : base(ListConstructor()) { } public EnumeratedValueCollection(Func<T, bool> selection) : base(ListConstructor(selection)) { } public EnumeratedValueCollection(Func<T, string> format) : base(ListConstructor(format)) { } public EnumeratedValueCollection(Func<T, bool> selection, Func<T, string> format) : base(ListConstructor(selection, format)) { } internal EnumeratedValueCollection(IList<EnumeratedValue<T>> data) : base(data) { } internal static List<EnumeratedValue<T>> ListConstructor() { return ListConstructor(null, null); } internal static List<EnumeratedValue<T>> ListConstructor(Func<T, string> format) { return ListConstructor(null, format); } internal static List<EnumeratedValue<T>> ListConstructor(Func<T, bool> selection) { return ListConstructor(selection, null); } internal static List<EnumeratedValue<T>> ListConstructor(Func<T, bool> selection, Func<T, string> format) { if (null == selection) selection = (x => true); if (null == format) format = (x => GetDescription<T>(x)); var result = new List<EnumeratedValue<T>>(); foreach (T value in System.Enum.GetValues(typeof(T))) { if (selection(value)) { string description = format(value); result.Add(new EnumeratedValue<T>(value, description)); } } return result; } public bool Contains(T value) { return (Items.FirstOrDefault(item => item.Value.Equals(value)) != null); } public EnumeratedValue<T> this[T value] { get { return Items.First(item => item.Value.Equals(value)); } } public string Describe(T value) { return this[value].Description; } } [System.Diagnostics.DebuggerDisplay("{Value} ({Description})")] public class EnumeratedValue<T> { private T value; private string description; internal EnumeratedValue(T value, string description) { this.value = value; this.description = description; } public T Value { get { return this.value; } } public string Description { get { return this.description; } } } } |
如您所见,这个集合很容易用lambda自定义,以选择枚举器的子集和/或实现对
可以使用postsharp以enum.toString为目标,并添加所需的附加代码。这不需要任何代码更改。
一旦您有了
1 2 3 4 | public static string ToString(this HowNice self) { return GetDescription<HowNice>(self); } |
1 2 3 4 5 6 7 8 9 10 | Enum HowNice { [StringValue("Really Nice")] ReallyNice, [StringValue("Kinda Nice")] SortOfNice, [StringValue("Not Nice At All")] NotNice } Status = ReallyNice.GetDescription() |
可以将枚举定义为
1 2 3 4 5 6 7 8 | Enum HowNice { [StringValue("Really Nice")] ReallyNice, [StringValue("Kinda Nice")] SortOfNice, [StringValue("Not Nice At All")] NotNice } |
然后使用