我如何有枚举值的自定义string格式的枚举绑定combobox?
在后面的Enum ToString中 ,描述了一个方法使用自定义属性DescriptionAttribute
如下所示:
Enum HowNice { [Description("Really Nice")] ReallyNice, [Description("Kinda Nice")] SortOfNice, [Description("Not Nice At All")] NotNice }
然后,你调用GetDescription
函数,使用如下语法:
GetDescription<HowNice>(NotNice); // Returns "Not Nice At All"
但是,这并不能真正帮助我,因为我不能强制ComboBox调用GetDescription
,所以我只想用一个枚举的值填充ComboBox 。
我想要的有以下要求:
- 读取
(HowNice)myComboBox.selectedItem
将返回选定的值作为枚举值。 - 用户应该看到用户友好的显示string,而不仅仅是枚举值的名称。 因此,用户不会看到“
NotNice
”,而会看到“Not Nice At All
”。 - 希望解决scheme只需要对现有枚举进行最less的代码更改。
显然,我可以为我创build的每个枚举实现一个新的类,并覆盖它的ToString()
,但是对于每个枚举来说这是很多工作,我宁愿避免这样做。
有任何想法吗?
哎呀,我甚至会抛出一个慷慨的拥抱 🙂
你可以编写一个TypeConverter来读取指定的属性来查找你的资源。 因此,你会得到多语言支持显示名称没有太多的麻烦。
查看TypeConverter的ConvertFrom / ConvertTo方法,并使用reflection读取枚举字段上的属性。
ComboBox
有你需要的一切: FormattingEnabled
属性,你应该设置为true
, Format
事件,你需要放置所需的格式化逻辑。 从而,
myComboBox.FormattingEnabled = true; myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e) { e.Value = GetDescription<HowNice>((HowNice)e.Value); }
别! 枚举是原始的,而不是UI对象 – 使得它们服务于UI.ToString()从devise的angular度来看是相当糟糕的。 你试图在这里解决错误的问题:真正的问题是,你不希望Enum.ToString()出现在combobox!
现在这确实是一个非常可以解决的问题! 您创build一个UI对象来表示您的combobox项目:
sealed class NicenessComboBoxItem { public string Description { get { return ...; } } public HowNice Value { get; private set; } public NicenessComboBoxItem(HowNice howNice) { Value = howNice; } }
然后将这个类的实例添加到combobox的Items集合中,并设置这些属性:
comboBox.ValueMember = "Value"; comboBox.DisplayMember = "Description";
types转换器。 我想这是我正在寻找的。 所有人都热衷于Simon Svensson !
[TypeConverter(typeof(EnumToStringUsingDescription))] Enum HowNice { [Description("Really Nice")] ReallyNice, [Description("Kinda Nice")] SortOfNice, [Description("Not Nice At All")] NotNice }
我现在需要改变的就是在声明之前添加这行。
[TypeConverter(typeof(EnumToStringUsingDescription))]
一旦我这样做,任何枚举将显示使用其字段的DescriptionAttribute
。
哦, TypeConverter
会被这样定义:
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的情况下,但显然实际上并没有重写ToString()
。 我想我会解决这个问题
使用您的枚举示例:
using System.ComponentModel; Enum HowNice { [Description("Really Nice")] ReallyNice, [Description("Kinda Nice")] SortOfNice, [Description("Not Nice At All")] NotNice }
创build一个扩展:
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; } }
那么你可以使用如下的东西:
HowNice myEnum = HowNice.ReallyNice; string myDesc = myEnum.Description();
有关更多信息,请参见: http : //www.blackwasp.co.uk/EnumDescription.aspx 。 Credit得到里奇卡尔的解决scheme
你可以做一个通用的结构,你可以使用所有你的枚举有描述。 使用隐式转换和类,你的variables仍然像枚举,除了ToString方法:
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; } }
用法示例:
Described<HowNice> nice = HowNice.ReallyNice; Console.WriteLine(nice == HowNice.ReallyNice); // writes "True" Console.WriteLine(nice); // writes "Really Nice"
我不认为你可以做到这一点,而不是简单地绑定到一个不同的types – 至less,不方便。 通常,即使你不能控制ToString()
,你也可以使用TypeConverter
来自定义格式 – 但是IIRC的System.ComponentModel
东西并不尊重这个枚举。
你可以绑定到描述的string[]
,或者是一个类似于键/值对的东西? (desription / value) – 类似于:
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)); } }
然后绑定到EnumWrapper<HowNice>.GetValues()
做这个最好的方法就是上课。
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!"); }
我相信这是最好的办法。
当用combobox填充时,会显示漂亮的ToString,而且没有人能够再创build类的实例,这实际上是一个枚举。
PS可能需要一些轻微的语法修复,我不是超级好的C#。 (Java的家伙)
鉴于你宁愿不为每个枚举创build一个类,我build议创build一个枚举值/显示文本和绑定,而不是字典。
请注意,这对原始文章中的GetDescription方法方法有依赖性。
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()。 但是,您可以使用扩展方法;
public static string ToString(this HowNice self, int neverUsed) { switch (self) { case HowNice.ReallyNice: return "Rilly, rilly nice"; break; ...
当然,你必须明确地调用这个方法,
HowNice.ReallyNice.ToString(0)
这不是一个很好的解决scheme,一个switch语句和所有 – 但它应该工作,并希望whitout许多重写…
跟在@scraimer答案,这里是一个版本的枚举到stringtypes转换器,它也支持标志:
/// <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("\n",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; } }
并使用它的扩展方法:
/// <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; }
我会写一个generics类用于任何types。 以前我用过这样的东西:
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 }
最重要的是,你可以添加一个静态的“工厂方法”来创build一个给定枚举types的combobox列表(与您在那里获得的GetDescriptions方法几乎相同)。 这将节省您不得不为每个枚举types实现一个实体,并且还为“GetDescriptions”帮助方法(我个人称之为FromEnum(T obj))提供了一个很好的/合理的位置。
创build一个包含你所需要的集合(比如包含一个包含HowNice
枚举值的Value
属性的简单对象和一个包含GetDescription<HowNice>(Value)
的Description
属性, GetDescription<HowNice>(Value)
该组合绑定到该集合。
有点像这样:
Combo.DataSource = new EnumeratedValueCollection<HowNice>(); Combo.ValueMember = "Value"; Combo.DisplayMember = "Description";
当你有一个这样的集合类:
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来轻松地定制,以select你的枚举器的一个子集和/或实现一个自定义的格式来string
而不是使用你提到的GetDescription<T>(x)
函数。
您可以使用PostSharp来定位Enum.ToString并添加所需的代码。 这不需要任何代码更改。
你需要的是将一个枚举变成一个ReadonlyCollection,并将集合绑定到combobox(或者任何启用了Key-Value Pair的控件)
首先,你需要一个类来包含列表中的项目。 因为所有你需要的是int / string对,我build议使用一个接口和一个基类组合,这样你就可以在你想要的任何对象中实现这个function:
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 }
这里是接口和一个实现它的示例类。注意,类的Key是强types的Enum,并且IValueDescritionItem属性是明确实现的(所以类可以有任何属性,你可以select那些实现键/值对。
现在EnumToReadOnlyCollection类:
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)]; } } }
所以你所需要的代码是:
private EnumToReadOnlyCollection<MyItem, HowNice> enumcol; enumcol = new EnumToReadOnlyCollection<MyItem, HowNice>(); comboBox1.ValueMember = "Niceness"; comboBox1.DisplayMember = "NicenessDescription"; comboBox1.DataSource = enumcol;
请记住,您的集合是使用MyItem键入的,因此如果绑定到相应的proprtie,则combobox值应该返回一个枚举值。
我添加了T [Enum t]属性,使得集合比简单的组合消耗更有用,例如textBox1.Text = enumcol [HowNice.ReallyNice] .NicenessDescription;
你当然可以select将MyItem转换成一个Key / Value类,这个类只用于这个puprose,在EnumToReadnlyCollection的types参数中完全跳过MyItem,但是这样你就不得不使用int来replace键(意思是获取combobox1.SelectedValue将返回int而不是枚举types)。 如果你创build一个KeyValueItem类来replaceMyItem等等,你可以解决这个问题…
对不起,这个老线索。
我将采用以下方法来本地化枚举,因为它可以通过本示例中的下拉列表文本字段向用户显示有意义的本地化值,而不仅仅是描述。
首先,我创build一个名为OwToStringByCulture的简单方法来从全局资源文件中获取本地化的string,本例中它是App_GlobalResources文件夹中的BiBongNet.resx。 在这个资源文件中,确保你所有的string和枚举的值相同(ReallyNice,SortOfNice,NotNice)。 在这个方法中,我传入参数:resourceClassName,通常是资源文件的名称。
接下来,我创build一个静态方法来填充一个使用枚举作为其数据源的下拉列表,称为OwFillDataWithEnum。 这个方法可以在任何枚举中使用。
然后在一个名为DropDownList1的下拉列表的页面中,我在Page_Load中设置了一行简单的代码来将枚举填充到下拉列表中。
BiBongNet.OwFillDataWithEnum<HowNice>(DropDownList1, "BiBongNet");
而已。 我想用这些简单的方法,你可以用任何枚举来填充任何列表控件,而不仅仅是描述性的值,而是显示本地化的文本。 您可以将所有这些方法作为扩展方法以便更好地使用。
希望这个帮助。 分享分享!
这里是方法:
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); } }
Enum HowNice { [Description("Really Nice")] ReallyNice, [Description("Kinda Nice")] SortOfNice, [Description("Not Nice At All")] NotNice }
为了解决这个问题,你应该使用一个扩展方法和一个像这样的string数组:
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]; } }
简单的代码和快速解码。
我尝试了这种方法,它为我工作。
我为枚举创build了一个包装类,并重载了隐式运算符,这样我就可以将它分配给枚举variables(在我的情况下,我必须将一个对象绑定到ComboBox
值)。
您可以使用reflection来设置枚举值的格式,在我的情况下我从枚举值(如果存在)中检索DisplayAttribute
。
希望这可以帮助。
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); } }
编辑:
为了以防万一,我使用下面的函数来获取我用于ComboBox
的DataSource
的enum
值
public static class Utils { public static IEnumerable<EnumItem<T>> GetEnumValues<T>() { List<EnumItem<T>> result = new List<EnumItem<T>>(); foreach (T item in Enum.GetValues(typeof(T))) { result.Add(item); } return result; } }
一旦你有GetDescription
方法(它需要是全局静态的),你可以通过一个扩展方法来使用它:
public static string ToString(this HowNice self) { return GetDescription<HowNice>(self); }
Enum HowNice { [StringValue("Really Nice")] ReallyNice, [StringValue("Kinda Nice")] SortOfNice, [StringValue("Not Nice At All")] NotNice } Status = ReallyNice.GetDescription()
You can define Enum as
Enum HowNice { [StringValue("Really Nice")] ReallyNice, [StringValue("Kinda Nice")] SortOfNice, [StringValue("Not Nice At All")] NotNice }
and then use HowNice.GetStringValue()
.