Работая над Spring, вы, вероятно, импортируете реализацию Hibernate, так что это хороший пример использования для аннотации @Formula
(документация , ).
Определяет формулу (производное значение), которая является фрагментом SQL, который в большинстве случаев выступает в качестве альтернативы @Column.
blockquote>Аннотация
@Formula
принимает нативныйSQL
, поэтому для такого рода логики вам следует использовать функциюSWITCH
или, возможно,SQL
COALESCE
.Пример:
@Formula("case when ColumnA > 0 then ColumnA else ColumnB end") private Integer myValue;
Вы можете написать TypeConverter, который читает указанные атрибуты, чтобы искать их в ваших ресурсах. Таким образом, вы получите многоязыковую поддержку отображаемых имен без особых хлопот.
Изучите методы ConvertFrom / ConvertTo TypeConverter и используйте отражение для чтения атрибутов в ваших полях enum .
Вы можете использовать PostSharp для нацеливания на Enum.ToString и добавления нужного дополнительного кода. Это не требует никаких изменений кода.
I would write a generic class for use with any type. I've used something like this in the past:
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
}
On top of this, you could add a static "factory method" to create a list of combobox items given an enum type (pretty much the same as the GetDescriptions method you have there). This would save you of having to implement one entity per each enum type, and also provide a nice/logical place for the "GetDescriptions" helper method (personally I would call it FromEnum(T obj) ...
Невозможно переопределить ToString () перечислений в C #. Однако вы можете использовать методы расширения;
public static string ToString(this HowNice self, int neverUsed)
{
switch (self)
{
case HowNice.ReallyNice:
return "Rilly, rilly nice";
break;
...
Конечно, вам придется сделать явный вызов метода, то есть;
HowNice.ReallyNice.ToString(0)
Это не очень хорошее решение, с оператором switch и всем - но оно должно работать и, надеюсь, без многих переписывает ...
Create a collection that contains what you need (like simple objects containing a Value
property containing the HowNice
enum value and a Description
property containing GetDescription
and databind the combo to that collection.
Bit like this:
Combo.DataSource = new EnumeratedValueCollection<HowNice>();
Combo.ValueMember = "Value";
Combo.DisplayMember = "Description";
when you have a collection class like this:
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; } }
}
}
As you can see, this collection is easily customizable with lambda's to select a subset of your enumerator and/or implement a custom formatting to string
instead of using the GetDescription
function you mention.
The best way to do this is to make a class.
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!");
}
I believe that is the best way to do it.
When stuffed in comboboxes the pretty ToString will be shown, and the fact that no one can make any more instances of your class essentially makes it an enum.
p.s. there may need to be some slight syntax fixes, I'm not super good with C#. (Java guy)
Given that you'd rather not create a class for each enum, I'd recommend creating a dictionary of the enum value/display text and binding that instead.
Note that this has a dependency on the GetDescription method methods in the original post.
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;
}
Я не думаю, что вы можете сделать это без простой привязки к другому типу - по крайней мере, не удобно. Обычно, даже если вы не можете управлять ToString ()
, вы можете использовать TypeConverter
для выполнения пользовательского форматирования - но IIRC System.ComponentModel
вещи не делает Это не относится к перечислениям.
Вы можете привязать к строку []
описаний или что-то, по сути, как пара ключ / значение? (описание / значение) - что-то вроде:
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
TypeConverter. Я думаю, что это то, что я искал. Все приветствуют Саймон Свенссон !
[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 ()
. Я полагаю, что пока что согласен на это ...
Don't! Enums are primitives and not UI objects - making them serve the UI in .ToString() would be quite bad from a design standpoint. You are trying to solve the wrong problem here: the real issue is that you do not want Enum.ToString() to show up in the combo box!
Now this is a very solveable problem indeed! You create a UI object to represent your combo box items:
sealed class NicenessComboBoxItem
{
public string Description { get { return ...; } }
public HowNice Value { get; private set; }
public NicenessComboBoxItem(HowNice howNice) { Value = howNice; }
}
And then just add instances of this class to your combo box's Items collection and set these properties:
comboBox.ValueMember = "Value";
comboBox.DisplayMember = "Description";
You could make a generic struct that you could use for all of your enums that has descriptions. With implicit conversions to and from the class, your variables still works like the enum except for the ToString method:
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;
}
}
Usage example:
Described<HowNice> nice = HowNice.ReallyNice;
Console.WriteLine(nice == HowNice.ReallyNice); // writes "True"
Console.WriteLine(nice); // writes "Really Nice"
ComboBox
has everything you need: the FormattingEnabled
property, which you should set to true
, and Format
event, where you'll need to place desired formatting logic. Thus,
myComboBox.FormattingEnabled = true;
myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e)
{
e.Value = GetDescription<HowNice>((HowNice)e.Value);
}
What you need is to turn an enum into a ReadonlyCollection and bind the collection to the combobox (or any Key-Value Pair enabled control for that matter)
First off you need a class to contain the items of the list. Since all you need is the int/string pair I suggest using an interface and a base class combo so that you can implement the functionality in any object you want:
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
}
Here is the interface and a sample class that implements it.Notice that the class' Key is strongly typed to the Enum, and that the IValueDescritionItem proprties are implemented explicitely (so the class can have whatever properties and you can CHOOSE the ones that implement the Key/Value pair.
Now the EnumToReadOnlyCollection class:
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)];
}
}
}
So all you need in your code is :
private EnumToReadOnlyCollection<MyItem, HowNice> enumcol;
enumcol = new EnumToReadOnlyCollection<MyItem, HowNice>();
comboBox1.ValueMember = "Niceness";
comboBox1.DisplayMember = "NicenessDescription";
comboBox1.DataSource = enumcol;
Remember that your collection is typed with MyItem so the combobox value should return an enum value if you bind to the appropriate proprtie.
I added the T this[Enum t] property to make the collection even more usefull than a simple combo consumable, for example textBox1.Text = enumcol[HowNice.ReallyNice].NicenessDescription;
You can of course chose to turn MyItem into a Key/Value class used only for this puprose effectively skipping MyItem in the type arguments of EnumToReadnlyCollection altogether, but then you'd be forced to go with int for the key (meaning getting combobox1.SelectedValue would return int and not the enum type). You work around that if you create a KeyValueItem class to replace MyItem and so on and so forth...