W jaki sposób mogę mieć skojarzony wyliczenie combobox z niestandardowym formatowaniem ciągu dla wartości wyliczenia?

135

W poście Enum ToString metoda jest opisana w celu użycia atrybutu niestandardowego w DescriptionAttributenastępujący sposób:

Enum HowNice {
  [Description("Really Nice")]
  ReallyNice,
  [Description("Kinda Nice")]
  SortOfNice,
  [Description("Not Nice At All")]
  NotNice
}

Następnie wywołujesz funkcję GetDescription, używając składni takiej jak:

GetDescription<HowNice>(NotNice); // Returns "Not Nice At All"

Ale to nie pomaga mi, gdy chcę po prostu wypełnić ComboBox wartościami wyliczenia, ponieważ nie mogę zmusić ComboBox do wywołaniaGetDescription .

To, czego chcę, ma następujące wymagania:

  • Odczyt (HowNice)myComboBox.selectedItemzwróci wybraną wartość jako wartość wyliczenia.
  • Użytkownik powinien widzieć przyjazne dla użytkownika łańcuchy wyświetlania, a nie tylko nazwy wartości wyliczenia. Zamiast więc zobaczyć „ NotNice”, użytkownik zobaczy „ Not Nice At All”.
  • Miejmy nadzieję, że rozwiązanie będzie wymagało minimalnych zmian kodu w istniejących wyliczeniach.

Oczywiście mógłbym zaimplementować nową klasę dla każdego wyliczenia, które utworzę, i zastąpić jej ToString(), ale to dużo pracy dla każdego wyliczenia i wolałbym tego uniknąć.

Jakieś pomysły?

Cholera, nawet przytulę jako nagrodę :-)

Shalom Craimer
źródło
1
jjnguy ma rację, że wyliczenia Java ładnie to rozwiązują ( javahowto.blogspot.com/2006/10/… ), ale ma to wątpliwe znaczenie.
Matthew Flaschen
8
Java Enums to żart. Może dodadzą Właściwości w 2020 r .: /
Chad Grant
Aby uzyskać lżejsze (ale prawdopodobnie mniej solidne) rozwiązanie, zobacz mój wątek .
Gutblender

Odpowiedzi:

42

Możesz napisać TypeConverter, który odczytuje określone atrybuty, aby wyszukać je w zasobach. W ten sposób bez większego kłopotu uzyskasz obsługę wielu języków dla wyświetlanych nazw.

Zapoznaj się z metodami ConvertFrom / ConvertTo TypeConverter i użyj odbicia, aby odczytać atrybuty w polach wyliczenia .

siostra
źródło
OK, napisałem jakiś kod (zobacz moją odpowiedź na to pytanie) - myślisz, że to wystarczy, czy coś mi brakuje?
Shalom Craimer
1
Niezłe. Lepsze ogólne, ale może być przesadą dla twojego zwykłego oprogramowania, które nigdy nie zostanie zglobalizowane w żaden sposób. (Wiem, że to założenie okaże się później fałszywe. ;-))
peSHIr
85

ComboBoxma wszystko, czego potrzebujesz: FormattingEnabledwłaściwość, którą powinieneś ustawić true, i Formatzdarzenie, w którym musisz umieścić żądaną logikę formatowania. A zatem,

myComboBox.FormattingEnabled = true;
myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e)
    {
        e.Value = GetDescription<HowNice>((HowNice)e.Value);
    }
Anton Gogolev
źródło
Czy działa to tylko z połączonymi skrzynkami danych? W przeciwnym razie nie mogę uruchomić zdarzenia Format.
SomethingBetter
jedynym problemem jest to, że nie możesz posortować listy według swojej logiki
GorillaApe
To świetne rozwiązanie. Potrzebowałbym tego do pracy z DataGridComboBoxColumnchociaż. Jakaś szansa na rozwiązanie tego problemu? Nie mogę znaleźć sposobu na uzyskanie dostępu do ComboBoxdomeny DataGridComboBoxColumn.
Soko
46

Nie! Wyliczenia są prymitywami, a nie obiektami UI - uczynienie ich obsługi UI w .ToString () byłoby bardzo złe z punktu widzenia projektowania. Próbujesz tutaj rozwiązać niewłaściwy problem: prawdziwy problem polega na tym, że nie chcesz, aby Enum.ToString () pojawiało się w polu kombi!

Teraz jest to rzeczywiście bardzo możliwy do rozwiązania problem! Tworzysz obiekt UI reprezentujący elementy z pola kombi:

sealed class NicenessComboBoxItem
{
    public string Description { get { return ...; } }
    public HowNice Value { get; private set; }

    public NicenessComboBoxItem(HowNice howNice) { Value = howNice; }
}

Następnie po prostu dodaj wystąpienia tej klasy do kolekcji Items pola kombi i ustaw następujące właściwości:

comboBox.ValueMember = "Value";
comboBox.DisplayMember = "Description";
Szlifierka
źródło
1
Całkowicie się zgadzam. Nie należy też ujawniać wyniku ToString () w interfejsie użytkownika. I nie dostajesz lokalizacji.
Øyvind Skaar
Wiem, że to jest stare, ale czym się różni?
nportelli
2
Widziałem podobne rozwiązanie, w którym zamiast używać niestandardowej klasy, mapowali wartości wyliczenia na a Dictionaryi używali właściwości Keyi Valuejako DisplayMemberi ValueMember.
Jeff B,
42

TypeConverter. Myślę, że właśnie tego szukałem. Witajcie, Simon Svensson !

[TypeConverter(typeof(EnumToStringUsingDescription))]
Enum HowNice {
  [Description("Really Nice")]
  ReallyNice,
  [Description("Kinda Nice")]
  SortOfNice,
  [Description("Not Nice At All")]
  NotNice
}

Wszystko, co muszę zmienić w moim bieżącym wyliczeniu, to dodać tę linię przed ich deklaracją.

[TypeConverter(typeof(EnumToStringUsingDescription))]

Gdy to zrobię, każde wyliczenie zostanie wyświetlone przy użyciu DescriptionAttributejego pól.

Aha, i TypeConverterbyłoby to zdefiniowane w ten sposób:

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

Pomaga mi to w przypadku mojej skrzynki ComboBox, ale oczywiście w rzeczywistości nie zastępuje ToString(). W międzyczasie zgodzę się na to ...

Shalom Craimer
źródło
3
Obsługujesz Enum -> String, ale będziesz również potrzebować Enum> InstanceDescriptor i String -> Enum, jeśli chcesz mieć pełną implementację. Ale wydaje mi się, że w tej chwili wyświetlenie tego wystarczy dla Twoich potrzeb. ;)
sisve
1
To rozwiązanie działa niestety tylko wtedy, gdy twoje opisy są statyczne.
Llyle
Nawiasem mówiąc, użycie TypeConverter nie jest powiązane z opisami statycznymi, pokrywa może wypełniać wartości z innych źródeł niż atrybuty.
Dmitry Tashkinov,
3
Ciągnę za włosy już od kilku godzin, ale nadal wydaje się, że nie działają nawet w prostych aplikacjach konsolowych. Udekorowałem wyliczenie [TypeConverter(typeof(EnumToStringUsingDescription))] public enum MyEnum {[Description("Blah")] One}, próbowałem Console.WriteLine(MyEnum.One)i nadal wychodzi jako „Jeden”. Czy potrzebujesz jakiejś specjalnej magii TypeDescriptor.GetConverter(typeof(MyEnum)).ConvertToString(MyEnum.One)(która działa dobrze)?
Dav
1
@scraimer Opublikowałem wersję twojego kodu obsługującą flagi. wszelkie prawa zastrzeżone ...
Avi Turner
32

Na podstawie przykładu wyliczenia:

using System.ComponentModel;

Enum HowNice
{
    [Description("Really Nice")]
    ReallyNice,
    [Description("Kinda Nice")]
    SortOfNice,
    [Description("Not Nice At All")]
    NotNice
}

Utwórz rozszerzenie:

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

Następnie możesz użyć czegoś takiego jak:

HowNice myEnum = HowNice.ReallyNice;
string myDesc = myEnum.Description();

Więcej informacji można znaleźć pod adresem : http://www.blackwasp.co.uk/EnumDescription.aspx . Rozwiązanie należy do Richrda Carra

Tyler Durden
źródło
Śledziłem szczegóły w podanej witrynie i użyłem go w następujący sposób, wygląda to prosto dla mnie 'string myDesc = HowNice.ReallyNice.Description ();' myDesc wyświetli Really Nice
Ananda
8

Możesz stworzyć ogólną strukturę, której możesz użyć dla wszystkich swoich wyliczeń, które mają opisy. Dzięki niejawnym konwersjom do iz klasy zmienne nadal działają jak wyliczenie, z wyjątkiem metody 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;
    }

}

Przykład użycia:

Described<HowNice> nice = HowNice.ReallyNice;

Console.WriteLine(nice == HowNice.ReallyNice); // writes "True"
Console.WriteLine(nice); // writes "Really Nice"
Guffa
źródło
5

Myślę, że nie da się tego zrobić bez prostego wiązania się z innym typem - przynajmniej nie jest to wygodne. Normalnie, nawet jeśli nie możesz kontrolować ToString(), możesz użyć a, TypeConverteraby wykonać niestandardowe formatowanie - ale IIRC System.ComponentModelnie uwzględnia tego w przypadku wyliczeń.

Możesz powiązać się z jednym string[]z opisów lub czymś zasadniczo podobnym do pary klucz / wartość? (opis / wartość) - coś takiego:

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

A następnie powiąż z EnumWrapper<HowNice>.GetValues()

Marc Gravell
źródło
1
Nazwa „GetDescription” nie istnieje w obecnym kontekście. używam .NET 4.0
Muhammad Adeel Zahid,
@MuhammadAdeelZahid przyjrzyj się uważnie początkowi pytania - który pochodzi z posta, do którego prowadzi łącze: stackoverflow.com/questions/479410/enum-tostring
Marc Gravell
przepraszam, ale nie mogę wyciągnąć żadnej wskazówki z pytania. Twoja metoda nie kompiluje się i wyświetla błąd.
Muhammad Adeel Zahid
Cześć Marc, wypróbowałem twój pomysł. Działa, ale theComboBox.SelectItemjest typem EnumWrapper<T>zamiast Tsiebie. Myślę, że Scraimer chce Reading (HowNice)myComboBox.selectedItem will return the selected value as the enum value..
Peter Lee,
5

Najlepszym sposobem na to jest utworzenie klasy.

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

Uważam, że to najlepszy sposób na zrobienie tego.

Po umieszczeniu w comboboxach ładny ToString zostanie pokazany, a fakt, że nikt nie może utworzyć więcej instancji twojej klasy, zasadniczo sprawia, że ​​jest to wyliczenie.

ps może zaistnieć potrzeba drobnych poprawek składni, nie jestem zbyt dobry z C #. (Facet Java)

jjnguy
źródło
1
Jak to pomaga w rozwiązaniu problemu combobox?
peSHIr
Cóż, teraz, gdy nowy obiekt zostanie umieszczony w kombi, jego ToString będzie poprawnie wyświetlane, a klasa nadal będzie działać jak wyliczenie.
jjnguy
1
To byłaby moja odpowiedź.
Mikko Rantanen
3
I widząc, jak oryginalny plakat wyraźnie nie chciał klasy. Nie sądzę, żeby zajęcia wymagały dużo więcej pracy. Możesz wyodrębnić opis i ToString przesłonić do klasy nadrzędnej dla wszystkich wyliczeń. Po tym wszystkim potrzebujesz konstruktora private HowNice(String desc) : base(desc) { }i pól statycznych.
Mikko Rantanen
Miałem nadzieję, że tego uniknę, ponieważ oznacza to, że każde wyliczenie, które zrobię, będzie wymagało własnej klasy. Fuj.
Shalom Craimer
3

Biorąc pod uwagę, że wolisz nie tworzyć klasy dla każdego wyliczenia, polecam utworzenie słownika wartości wyliczenia / wyświetlanego tekstu i powiązanie go.

Zauważ, że ma to zależność od metod metody GetDescription w oryginalnym poście.

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;
}
Richard Szalay
źródło
Dobry pomysł. Ale jak bym tego używał z comboboxem? Skąd mam wiedzieć, które z nich wybrał, gdy użytkownik wybierze przedmiot z listy kombinacji? Wyszukać według ciągu opisu? To sprawia, że ​​swędzi mnie skóra ... (może wystąpić „zderzenie” napisów między
napisami
Klucz wybranego elementu będzie rzeczywistą wartością wyliczenia. Nie koliduj też ciągów opisu - w jaki sposób użytkownik rozpozna różnicę?
Richard Szalay
<cont> jeśli masz kolidujące ciągi opisu, to i tak nie powinieneś wiązać wartości wyliczenia bezpośrednio z combobox.
Richard Szalay
hmmm ... Czy możesz mi podać przykładowy kod, w jaki sposób można dodać elementy do skrzynki wielofunkcyjnej? Wszystko, co przychodzi mi do głowy, to „foreach (string s in descriptionsDict.Values) {this.combobox.Items.Add (s);}”
Shalom Craimer
1
ComboBox.DataSource = słownik;
Richard Szalay
3

Nie można zastąpić ToString () wyliczeń w języku C #. Możesz jednak użyć metod rozszerzających;

public static string ToString(this HowNice self, int neverUsed)
{
    switch (self)
    {
        case HowNice.ReallyNice:
            return "Rilly, rilly nice";
            break;
    ...

Oczywiście będziesz musiał wykonać jawne wywołanie metody, tj;

HowNice.ReallyNice.ToString(0)

To nie jest miłe rozwiązanie, z instrukcją przełącznika i wszystkim - ale powinno działać i, miejmy nadzieję, bez wielu przeróbek ...

Björn
źródło
Należy pamiętać, że formant, który wiąże się z wyliczeniem, nie wywoła tej metody rozszerzenia, wywoła domyślną implementację.
Richard Szalay
Dobrze. Jest to więc realna opcja, jeśli potrzebujesz gdzieś opisu, nie pomoże to w rozwiązaniu problemu combobox.
peSHIr
Większy problem polega na tym, że ta metoda nigdy nie zostanie wywołana (jako metoda rozszerzająca) - pierwszeństwo mają metody instancji, które już istnieją.
Marc Gravell
Oczywiście Marc ma rację (jak zawsze?). Moje doświadczenie w .NET jest minimalne, ale podanie fikcyjnego parametru do metody powinno załatwić sprawę. Edytowana odpowiedź.
Björn
2

Idąc za odpowiedzią @scraimer, oto wersja konwertera typu wyliczenie na ciąg, który również obsługuje flagi:

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

}

I metoda rozszerzenia do korzystania z niego:

    /// <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;
    }
Avi Turner
źródło
1

Napisałbym klasę ogólną do użytku z dowolnym typem. W przeszłości użyłem czegoś takiego:

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
}

Oprócz tego możesz dodać statyczną „metodę fabryczną”, aby utworzyć listę elementów combobox z podanym typem wyliczenia (prawie tak samo jak metoda GetDescriptions, którą tam masz). Oszczędziłoby to konieczności implementowania jednej encji dla każdego typu wyliczenia, a także zapewniło ładne / logiczne miejsce dla metody pomocniczej „GetDescriptions” (osobiście nazwałbym to FromEnum (T obj) ...

Dan C.
źródło
1

Utwórz kolekcję zawierającą to, czego potrzebujesz (na przykład proste obiekty zawierające Valuewłaściwość zawierającą HowNicewartość wyliczenia oraz Descriptionwłaściwość zawierającą GetDescription<HowNice>(Value)i powiązaną z danymi kombi do tej kolekcji.

Trochę tak:

Combo.DataSource = new EnumeratedValueCollection<HowNice>();
Combo.ValueMember = "Value";
Combo.DisplayMember = "Description";

kiedy masz taką klasę kolekcji:

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

}

Jak widać, tę kolekcję można łatwo dostosować za pomocą lambda, aby wybrać podzbiór stringmodułu wyliczającego i / lub zaimplementować niestandardowe formatowanie zamiast używać wspomnianej GetDescription<T>(x)funkcji.

peSHIr
źródło
Świetnie, ale szukam czegoś, co wymaga jeszcze mniej pracy w kodzie.
Shalom Craimer
Nawet jeśli możesz użyć tej samej ogólnej kolekcji do tego typu rzeczy dla wszystkich swoich modułów wyliczających? Oczywiście nie sugerowałem niestandardowego pisania takiej kolekcji dla każdego wyliczenia.
peSHIr
1

Możesz użyć PostSharp do kierowania Enum.ToString i dodania dodatkowego kodu, który chcesz. Nie wymaga to żadnych zmian w kodzie.

majkinetor
źródło
1

To, czego potrzebujesz, to przekształcić wyliczenie w ReadonlyCollection i powiązać kolekcję z combobox (lub dowolną formantem z włączoną parą klucz-wartość)

Po pierwsze, potrzebujesz klasy, która będzie zawierała pozycje z listy. Ponieważ wszystko, czego potrzebujesz, to para int / string, sugeruję użycie interfejsu i kombinacji klasy bazowej, abyś mógł zaimplementować funkcjonalność w dowolnym obiekcie:

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
}

Oto interfejs i przykładowa klasa, która go implementuje. Zauważ, że klasa Key jest silnie wpisana do Enum i że właściwości IValueDescritionItem są jawnie zaimplementowane (więc klasa może mieć dowolne właściwości i możesz WYBRAĆ te, które implementują Para klucz / wartość.

Teraz klasa 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)];
        }
    }

}

Wszystko, czego potrzebujesz w swoim kodzie, to:

private EnumToReadOnlyCollection<MyItem, HowNice> enumcol;
enumcol = new EnumToReadOnlyCollection<MyItem, HowNice>();
comboBox1.ValueMember = "Niceness";
comboBox1.DisplayMember = "NicenessDescription";
comboBox1.DataSource = enumcol;

Pamiętaj, że Twoja kolekcja jest wpisywana za pomocą MyItem, więc wartość combobox powinna zwracać wartość wyliczenia, jeśli powiążesz się z odpowiednią właściwością.

Dodałem właściwość T this [Enum t], aby kolekcja była jeszcze bardziej użyteczna niż zwykłe materiały eksploatacyjne combo, na przykład textBox1.Text = enumcol [HowNice.ReallyNice] .NicenessDescription;

Możesz oczywiście zdecydować się zmienić MyItem w klasę Key / Value używaną tylko do tego puprose, skutecznie pomijając MyItem w argumentach typu EnumToReadnlyCollection, ale wtedy będziesz zmuszony przejść z int dla klucza (co oznacza uzyskanie combobox1. zwróci int, a nie typ wyliczenia). Możesz obejść ten problem, jeśli utworzysz klasę KeyValueItem, aby zastąpić MyItem i tak dalej, i tak dalej ...


źródło
1

Przepraszamy, że włączyłem ten stary wątek.

Poszedłbym w następujący sposób, aby zlokalizować wyliczenie, ponieważ może wyświetlać znaczące i zlokalizowane wartości dla użytkownika, a nie tylko opis, za pośrednictwem pola tekstowego listy rozwijanej w tym przykładzie.

Najpierw tworzę prostą metodę o nazwie OwToStringByCulture, aby pobrać zlokalizowane ciągi znaków z pliku zasobów globalnych, w tym przykładzie jest to BiBongNet.resx w folderze App_GlobalResources. W tym pliku zasobów upewnij się, że wszystkie ciągi są takie same, jak wartości wyliczenia (ReallyNice, SortOfNice, NotNice). W tej metodzie przekazuję parametr: resourceClassName, który jest zwykle nazwą pliku zasobów.

Następnie tworzę statyczną metodę wypełniania listy rozwijanej wyliczeniem jako źródłem danych o nazwie OwFillDataWithEnum. Tej metody można później użyć z dowolnym wyliczeniem.

Następnie na stronie z listą rozwijaną o nazwie DropDownList1 ustawiłem w Page_Load następujący tylko jeden prosty wiersz kodu, aby wypełnić wyliczenie na liście rozwijanej.

 BiBongNet.OwFillDataWithEnum<HowNice>(DropDownList1, "BiBongNet");

Otóż ​​to. Myślę, że za pomocą kilku prostych metod, takich jak te, można wypełnić dowolną kontrolkę listy dowolnym wyliczeniem, nie tylko wartościami opisowymi, ale zlokalizowanym tekstem do wyświetlenia. Możesz uczynić wszystkie te metody jako metody rozszerzające w celu lepszego wykorzystania.

Mam nadzieję, że to pomoże. Udostępnij, aby się podzielić!

Oto metody:

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);
        }
}
BiBongNet
źródło
1
Enum HowNice {
  [Description("Really Nice")]
  ReallyNice,
  [Description("Kinda Nice")]
  SortOfNice,
  [Description("Not Nice At All")]
  NotNice
}

Aby rozwiązać ten problem, powinieneś użyć metody rozszerzenia i tablicy ciągów, takich jak:

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

Prosty kod i szybkie dekodowanie.

Sérgio
źródło
zmienna strings powinna być Static i zadeklarowana w ten sposób: Static String [] strings = new [] {...}
Sérgio
Jedyny problem z tym jest taki, że będziesz potrzebował funkcji dla każdego wyliczenia, a opis będzie poza samym wyliczeniem ...
Avi Turner
1

Wypróbowałem to podejście i zadziałało.

Utworzyłem klasę opakowującą dla wyliczeń i przeładowałem niejawny operator, aby móc przypisać go do zmiennych wyliczeniowych (w moim przypadku musiałem powiązać obiekt z ComboBoxwartością).

Możesz użyć odbicia, aby sformatować wartości wyliczenia w sposób, w jaki chcesz, w moim przypadku pobieram DisplayAttributewartości wyjściowe z wartości wyliczeniowych (jeśli istnieją).

Mam nadzieję że to pomoże.

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

EDYTOWAĆ:

Tylko w przypadku, używam następujących funkcji w celu uzyskania enumwartości, które używam dla DataSourcezComboBox

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;
    }
}
Ezequiel Moneró Thompson
źródło
0

Gdy już masz GetDescriptionmetodę (musi być globalna statyczna), możesz użyć jej za pomocą metody rozszerzającej:

public static string ToString(this HowNice self)
{
    return GetDescription<HowNice>(self);
}
groza
źródło
0
Enum HowNice {   
[StringValue("Really Nice")]   
ReallyNice,   
[StringValue("Kinda Nice")]   
SortOfNice,   
[StringValue("Not Nice At All")]   
NotNice 
}

Status = ReallyNice.GetDescription()
user1308805
źródło
3
Witamy w stackoverflow! Zawsze lepiej podać krótki opis przykładowego kodu, aby poprawić dokładność postów :)
Picrofo Software
-1

Możesz zdefiniować Enum jako

Enum HowNice {   
[StringValue("Really Nice")]   
ReallyNice,   
[StringValue("Kinda Nice")]   
SortOfNice,   
[StringValue("Not Nice At All")]   
NotNice 
} 

a następnie użyj HowNice.GetStringValue().

Vrushal
źródło
2
To się nie kompiluje (mam .NET 3.5). Gdzie zadeklarowano „StringValue”?
awe
1
Odpowiedź od @scraimer jest taka sama, z wyjątkiem tego, że używa on atrybutu spoza frameworka, podczas gdy ty używasz jakiegoś rodzaju samodzielnie zdefiniowanego atrybutu.
Oliver