Как создать комбинированный список с привязкой перечисления с пользовательским форматированием строки для значений перечисления?

Работая над Spring, вы, вероятно, импортируете реализацию Hibernate, так что это хороший пример использования для аннотации @Formula (документация , ).

Определяет формулу (производное значение), которая является фрагментом SQL, который в большинстве случаев выступает в качестве альтернативы @Column.

blockquote>

Аннотация @Formula принимает нативный SQL, поэтому для такого рода логики вам следует использовать функцию SWITCH или, возможно, SQL COALESCE.

Пример:

@Formula("case when ColumnA > 0 then ColumnA else ColumnB end")
private Integer myValue;

134
задан Community 23 May 2017 в 10:31
поделиться

13 ответов

Вы можете написать TypeConverter, который читает указанные атрибуты, чтобы искать их в ваших ресурсах. Таким образом, вы получите многоязыковую поддержку отображаемых имен без особых хлопот.

Изучите методы ConvertFrom / ConvertTo TypeConverter и используйте отражение для чтения атрибутов в ваших полях enum .

42
ответ дан 23 November 2019 в 23:55
поделиться

Вы можете использовать PostSharp для нацеливания на Enum.ToString и добавления нужного дополнительного кода. Это не требует никаких изменений кода.

1
ответ дан 23 November 2019 в 23:55
поделиться

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) ...

1
ответ дан 23 November 2019 в 23:55
поделиться

Невозможно переопределить 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 и всем - но оно должно работать и, надеюсь, без многих переписывает ...

3
ответ дан 23 November 2019 в 23:55
поделиться

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(Value) 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(x) function you mention.

1
ответ дан 23 November 2019 в 23:55
поделиться

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)

5
ответ дан 23 November 2019 в 23:55
поделиться

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;
}
3
ответ дан 23 November 2019 в 23:55
поделиться

Я не думаю, что вы можете сделать это без простой привязки к другому типу - по крайней мере, не удобно. Обычно, даже если вы не можете управлять 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 .GetValues ​​()

5
ответ дан 23 November 2019 в 23:55
поделиться

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 () . Я полагаю, что пока что согласен на это ...

42
ответ дан 23 November 2019 в 23:55
поделиться

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";
45
ответ дан 23 November 2019 в 23:55
поделиться

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"
8
ответ дан 23 November 2019 в 23:55
поделиться

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);
    }
85
ответ дан 23 November 2019 в 23:55
поделиться

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...

1
ответ дан 23 November 2019 в 23:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: