Wie habe ich eine Enum-gebundene Combobox mit benutzerdefinierter Zeichenfolgenformatierung für Enum-Werte?

135

In der Post Enum ToString wird eine Methode beschrieben, um das benutzerdefinierte Attribut DescriptionAttributewie folgt zu verwenden :

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

Und dann rufen Sie eine Funktion GetDescriptionmit folgender Syntax auf:

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

Aber das hilft mir nicht wirklich, wenn ich eine ComboBox einfach mit den Werten einer Aufzählung füllen möchte, da ich die ComboBox nicht zum Aufrufen zwingen kannGetDescription .

Was ich will, hat folgende Anforderungen:

  • Beim Lesen (HowNice)myComboBox.selectedItemwird der ausgewählte Wert als Aufzählungswert zurückgegeben.
  • Der Benutzer sollte die benutzerfreundlichen Anzeigezeichenfolgen sehen und nicht nur den Namen der Aufzählungswerte. Anstatt " NotNice" zu sehen, würde der Benutzer " Not Nice At All" sehen.
  • Hoffentlich erfordert die Lösung nur minimale Codeänderungen an vorhandenen Aufzählungen.

Natürlich könnte ich für jede von mir erstellte Aufzählung eine neue Klasse implementieren und diese überschreiben ToString(), aber das ist eine Menge Arbeit für jede Aufzählung, und das möchte ich lieber vermeiden.

Irgendwelche Ideen?

Verdammt, ich werde mich sogar als Kopfgeld umarmen :-)

Shalom Craimer
quelle
1
jjnguy ist richtig, dass Java-Enums dies gut lösen ( javahowto.blogspot.com/2006/10/… ), aber das ist von fraglicher Relevanz.
Matthew Flaschen
8
Java Enums sind ein Witz. Vielleicht werden sie Eigenschaften im Jahr 2020 hinzufügen: /
Chad Grant
Eine leichtere (aber wohl weniger robuste) Lösung finden Sie in meinem Thread .
Gutblender

Antworten:

42

Sie können einen TypeConverter schreiben, der bestimmte Attribute liest, um sie in Ihren Ressourcen nachzuschlagen. Auf diese Weise erhalten Sie mehrsprachige Unterstützung für Anzeigenamen ohne großen Aufwand.

Sehen Sie sich die ConvertFrom / ConvertTo-Methoden von TypeConverter an und verwenden Sie Reflection, um Attribute in Ihren Enum- Feldern zu lesen .

sisve
quelle
OK, ich habe Code geschrieben (siehe meine Antwort auf diese Frage) - denkst du, das reicht, fehlt mir etwas?
Shalom Craimer
1
Schön. Besser insgesamt, aber möglicherweise übertrieben für Ihre gewöhnliche Software, die in keiner Weise globalisiert wird. (Ich weiß, diese Annahme wird sich später als falsch herausstellen. ;-))
peSHIr
85

ComboBoxhat alles, was Sie brauchen: die FormattingEnabledEigenschaft, auf die Sie setzen sollten true, und das FormatEreignis, in dem Sie die gewünschte Formatierungslogik platzieren müssen. So,

myComboBox.FormattingEnabled = true;
myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e)
    {
        e.Value = GetDescription<HowNice>((HowNice)e.Value);
    }
Anton Gogolev
quelle
Funktioniert dies nur mit datengebundenen Comboboxen? Ich kann das Format-Ereignis sonst nicht auslösen.
SomethingBetter
Das einzige Problem hier ist, dass Sie die Liste nicht mit Ihrer Logik sortieren können
GorillaApe
Dies ist eine großartige Lösung. Ich würde es brauchen, um mit einem zu arbeiten DataGridComboBoxColumn. Irgendeine Chance, es zu lösen? Ich bin nicht in der Lage, einen Weg zu finden, um Zugang zu ComboBoxder DataGridComboBoxColumn.
Soko
46

Nicht! Aufzählungen sind Grundelemente und keine Benutzeroberflächenobjekte. Wenn sie der Benutzeroberfläche in .ToString () dienen, ist dies vom Entwurf her ziemlich schlecht. Sie versuchen hier das falsche Problem zu lösen: Das eigentliche Problem ist, dass Enum.ToString () nicht im Kombinationsfeld angezeigt werden soll!

Dies ist in der Tat ein sehr lösbares Problem! Sie erstellen ein UI-Objekt, um Ihre Kombinationsfeldelemente darzustellen:

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

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

Fügen Sie dann einfach Instanzen dieser Klasse zur Items-Auflistung Ihres Kombinationsfelds hinzu und legen Sie die folgenden Eigenschaften fest:

comboBox.ValueMember = "Value";
comboBox.DisplayMember = "Description";
Sander
quelle
1
Ich stimme voll und ganz zu. Sie sollten das Ergebnis von ToString () auch nicht für die Benutzeroberfläche verfügbar machen. Und Sie erhalten keine Lokalisierung.
Øyvind Skaar
Ich weiß, das ist alt, aber wie ist das anders?
Nportelli
2
Ich habe eine ähnliche Lösung gesehen, bei der anstelle einer benutzerdefinierten Klasse die Aufzählungswerte a zugeordnet Dictionaryund die Eigenschaften Keyund Valueals DisplayMemberund verwendet wurden ValueMember.
Jeff B
42

TypeConverter. Ich denke, das ist es, wonach ich gesucht habe. Alle begrüßen Simon Svensson !

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

Alles, was ich in meiner aktuellen Aufzählung ändern muss, ist, diese Zeile vor ihrer Deklaration hinzuzufügen.

[TypeConverter(typeof(EnumToStringUsingDescription))]

Sobald ich das mache, wird jede Aufzählung mit den DescriptionAttributeFeldern angezeigt .

Oh, und das TypeConverterwürde so definiert werden:

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

Dies hilft mir bei meinem ComboBox-Fall, überschreibt das aber offensichtlich nicht ToString(). Ich denke, ich werde mich in der Zwischenzeit damit zufrieden geben ...

Shalom Craimer
quelle
3
Sie behandeln Enum -> String, benötigen aber auch Enum> InstanceDescriptor und String -> Enum, wenn Sie eine vollständige Implementierung wünschen. Aber ich denke, es reicht im Moment für Ihre Bedürfnisse aus. ;)
Schwester
1
Diese Lösung funktioniert leider nur, wenn Ihre Beschreibungen statisch sind.
Llyle
Übrigens ist die Verwendung von TypeConverter nicht an statische Beschreibungen gebunden. Coverter kann Werte aus anderen Quellen als Attributen auffüllen.
Dmitry Tashkinov
3
Ich ziehe jetzt seit ein paar Stunden an meinen Haaren, aber es scheint immer noch nicht zu funktionieren, selbst in einfachen Konsolen-Apps. Ich habe die Aufzählung mit dekoriert [TypeConverter(typeof(EnumToStringUsingDescription))] public enum MyEnum {[Description("Blah")] One}, es versucht Console.WriteLine(MyEnum.One)und es kommt immer noch als "Eins" heraus. Benötigen Sie spezielle Magie wie TypeDescriptor.GetConverter(typeof(MyEnum)).ConvertToString(MyEnum.One)(die gut funktioniert)?
Dav
1
@scraimer Ich habe eine Version Ihres Codes veröffentlicht, die Flags unterstützt. Alle Rechte vorbehalten ...
Avi Turner
32

Verwenden Sie Ihr Aufzählungsbeispiel:

using System.ComponentModel;

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

Erstellen Sie eine Erweiterung:

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

Dann können Sie so etwas wie das Folgende verwenden:

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

Weitere Informationen finden Sie unter: http://www.blackwasp.co.uk/EnumDescription.aspx . Der Kredit geht an Richrd Carr für die Lösung

Tyler Durden
quelle
Ich habe die Details auf der angegebenen Site befolgt und sie wie folgt verwendet. Sieht für mich unkompliziert aus: 'string myDesc = HowNice.ReallyNice.Description ();' myDesc wird Really Nice
Ananda
8

Sie können eine generische Struktur erstellen, die Sie für alle Ihre Aufzählungen mit Beschreibungen verwenden können. Bei impliziten Konvertierungen in und aus der Klasse funktionieren Ihre Variablen mit Ausnahme der ToString-Methode weiterhin wie die Aufzählung:

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

}

Anwendungsbeispiel:

Described<HowNice> nice = HowNice.ReallyNice;

Console.WriteLine(nice == HowNice.ReallyNice); // writes "True"
Console.WriteLine(nice); // writes "Really Nice"
Guffa
quelle
5

Ich glaube nicht, dass Sie es schaffen können, ohne sich einfach an einen anderen Typ zu binden - zumindest nicht bequem. Selbst wenn Sie nicht steuern können ToString(), können Sie TypeConverternormalerweise eine benutzerdefinierte Formatierung verwenden - aber IIRC System.ComponentModelberücksichtigt dies nicht für Aufzählungen.

Sie könnten sich an eine string[]der Beschreibungen oder an etwas im Wesentlichen wie ein Schlüssel / Wert-Paar binden ? (Beschreibung / Wert) - so etwas wie:

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

Und dann binden an EnumWrapper<HowNice>.GetValues()

Marc Gravell
quelle
1
Der Name 'GetDescription' existiert im aktuellen Kontext nicht. Ich benutze .NET 4.0
Muhammad Adeel Zahid
@ MuhammadAdeelZahid schauen Sie sich den Anfang der Frage genau an - das kommt aus dem verlinkten Beitrag: stackoverflow.com/questions/479410/enum-tostring
Marc Gravell
Entschuldigung, aber ich kann keinen Hinweis auf die Frage bekommen. Ihre Methode wird nicht kompiliert und zeigt den Fehler an.
Muhammad Adeel Zahid
Hallo Marc, ich habe deine Idee ausprobiert. Es funktioniert, aber das theComboBox.SelectItemist eine Art von EnumWrapper<T>, anstatt sich Tselbst. Ich denke, Scraimer will Reading (HowNice)myComboBox.selectedItem will return the selected value as the enum value..
Peter Lee
5

Der beste Weg, dies zu tun, ist eine Klasse zu machen.

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

Ich glaube, das ist der beste Weg, dies zu tun.

In Comboboxen wird der hübsche ToString angezeigt, und die Tatsache, dass niemand mehr Instanzen Ihrer Klasse erstellen kann, macht sie im Wesentlichen zu einer Aufzählung.

ps Möglicherweise müssen einige geringfügige Syntaxkorrekturen vorgenommen werden. Ich bin nicht besonders gut mit C #. (Java-Typ)

jjnguy
quelle
1
Wie hilft dies beim Combobox-Problem?
PeSHIr
Nun, wenn das neue Objekt in eine Combobox eingefügt wird, wird sein ToString korrekt angezeigt und die Klasse verhält sich immer noch wie eine Aufzählung.
jjnguy
1
Wäre auch meine Antwort gewesen.
Mikko Rantanen
3
Und zu sehen, wie das Originalplakat ausdrücklich keine Klasse wollte. Ich glaube nicht, dass eine Klasse so viel mehr Arbeit ist. Sie können die Beschreibung abstrahieren und die ToString-Überschreibung für alle Aufzählungen in eine übergeordnete Klasse verschieben. Danach brauchen Sie nur noch einen Konstruktor private HowNice(String desc) : base(desc) { }und die statischen Felder.
Mikko Rantanen
Ich hatte gehofft, dies zu vermeiden, da dies bedeutet, dass jede Aufzählung, die ich mache, eine eigene Klasse erfordert. Pfui.
Shalom Craimer
3

Da Sie lieber nicht für jede Aufzählung eine Klasse erstellen möchten, würde ich empfehlen, ein Wörterbuch mit dem Aufzählungswert / Anzeigetext zu erstellen und diese stattdessen zu binden.

Beachten Sie, dass dies von den Methoden der GetDescription-Methode im ursprünglichen Beitrag abhängt.

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
quelle
Gute Idee. Aber wie würde ich das mit einer Combobox verwenden? Woher weiß ich, welche der von ihm ausgewählten Elemente ausgewählt wurden, nachdem der Benutzer ein Element aus der Combobox ausgewählt hat? Suche nach der Beschreibung Zeichenfolge? Das bringt meine Haut zum Jucken ... (es könnte eine "Kollision" zwischen den Beschreibungszeichenfolgen geben)
Shalom Craimer
Der Schlüssel des ausgewählten Elements ist der tatsächliche Aufzählungswert. Kollidieren Sie auch nicht mit den Beschreibungszeichenfolgen - wie erkennt der Benutzer den Unterschied?
Richard Szalay
<cont> Wenn Sie Beschreibungszeichenfolgen haben, die kollidieren, sollten Sie die Werte der Aufzählung ohnehin nicht direkt an eine Combobox binden.
Richard Szalay
hmmm ... Könnten Sie mir einen Beispielcode geben, wie Sie der Combobox Elemente hinzufügen würden? Alles, was ich mir vorstellen kann, ist "foreach (Zeichenfolge s in descriptionDict.Values) {this.combobox.Items.Add (s);}"
Shalom Craimer
1
ComboBox.DataSource = Wörterbuch;
Richard Szalay
3

Es ist nicht möglich, ToString () von Aufzählungen in C # zu überschreiben. Sie können jedoch Erweiterungsmethoden verwenden.

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

Natürlich müssen Sie die Methode explizit aufrufen, dh;

HowNice.ReallyNice.ToString(0)

Dies ist keine schöne Lösung, mit einer switch-Anweisung und allem - aber es sollte funktionieren und hoffentlich ohne viele Umschreibungen ...

Björn
quelle
Beachten Sie, dass das Steuerelement, das an Ihre Enumeration gebunden ist, diese Erweiterungsmethode nicht aufruft, sondern die Standardimplementierung.
Richard Szalay
Richtig. Dies ist also eine praktikable Option, wenn Sie irgendwo eine Beschreibung benötigen. Sie hilft nicht bei dem gestellten Combobox-Problem.
PeSHIr
Ein größeres Problem ist, dass dies niemals aufgerufen wird (als Erweiterungsmethode) - Instanzmethoden, die bereits existieren, haben immer Priorität.
Marc Gravell
Natürlich hat Marc recht (wie immer?). Meine .NET-Erfahrung ist minimal, aber das Bereitstellen eines Dummy-Parameters für die Methode sollte den Trick tun. Antwort bearbeitet.
Björn
2

Nach der Antwort von @scraimer finden Sie hier eine Version des Konverters vom Typ Enum-to-String, der auch Flags unterstützt:

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

}

Und eine Erweiterungsmethode für die Verwendung:

    /// <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
quelle
1

Ich würde eine generische Klasse für jeden Typ schreiben. Ich habe in der Vergangenheit so etwas benutzt:

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
}

Darüber hinaus können Sie eine statische "Factory-Methode" hinzufügen, um eine Liste von Combobox-Elementen mit einem Aufzählungstyp zu erstellen (ziemlich genau wie die GetDescriptions-Methode, die Sie dort haben). Dies erspart Ihnen die Implementierung einer Entität pro Aufzählungstyp und bietet auch einen schönen / logischen Platz für die Hilfsmethode "GetDescriptions" (persönlich würde ich sie FromEnum (T obj) nennen ...

Dan C.
quelle
1

Erstellen Sie eine Sammlung, die das enthält, was Sie benötigen (z. B. einfache Objekte, die eine ValueEigenschaft mit dem HowNiceAufzählungswert und eine DescriptionEigenschaft mit enthaltenGetDescription<HowNice>(Value) die Kombination mit dieser Sammlung und mit einer Datenbank verknüpft.

Etwas wie folgt:

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

Wenn Sie eine Sammlungsklasse wie diese haben:

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

}

Wie Sie sehen können, kann diese Sammlung mit Lambdas leicht angepasst werden, um eine Teilmenge Ihres Enumerators auszuwählen und / oder eine benutzerdefinierte Formatierung zu implementieren, stringanstatt die von GetDescription<T>(x)Ihnen erwähnte Funktion zu verwenden.

peSHIr
quelle
Ausgezeichnet, aber ich suche etwas, das noch weniger Arbeit im Code erfordert.
Shalom Craimer
Auch wenn Sie für all Ihre Enumeratoren dieselbe generische Sammlung für diese Art von Dingen verwenden können? Ich habe natürlich nicht vorgeschlagen, eine solche Sammlung für jede Aufzählung individuell zu schreiben.
PeSHIr
1

Sie können PostSharp verwenden, um auf Enum.ToString abzuzielen und zusätzlichen Code hinzuzufügen. Dies erfordert keine Codeänderungen.

Majkinetor
quelle
1

Sie müssen eine Aufzählung in eine ReadonlyCollection umwandeln und die Auflistung an die Combobox (oder ein beliebiges Steuerelement mit Schlüssel-Wert-Paar) binden.

Zunächst benötigen Sie eine Klasse, die die Elemente der Liste enthält. Da Sie lediglich das Int / String-Paar benötigen, empfehle ich die Verwendung einer Schnittstelle und einer Basisklassenkombination, damit Sie die Funktionalität in jedem gewünschten Objekt implementieren können:

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
}

Hier ist die Schnittstelle und eine Beispielklasse, die sie implementiert. Beachten Sie, dass der Schlüssel der Klasse stark in die Aufzählung eingegeben ist und dass die Eigenschaften von IValueDescritionItem explizit implementiert sind (sodass die Klasse alle Eigenschaften haben kann und Sie diejenigen auswählen können, die die implementieren Schlüssel / Wert-Paar.

Jetzt die EnumToReadOnlyCollection-Klasse:

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

}

Alles, was Sie in Ihrem Code benötigen, ist:

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

Denken Sie daran, dass Ihre Sammlung mit MyItem eingegeben wurde, sodass der Combobox-Wert einen Aufzählungswert zurückgeben sollte, wenn Sie an die entsprechende Eigenschaft binden.

Ich habe die Eigenschaft T this [Enum t] hinzugefügt, um die Sammlung noch nützlicher zu machen als ein einfaches Combo-Verbrauchsmaterial, zum Beispiel textBox1.Text = enumcol [HowNice.ReallyNice] .NicenessDescription;

Sie können MyItem natürlich in eine Schlüssel- / Wertklasse umwandeln, die nur für diese Puprose verwendet wird, und MyItem in den Typargumenten von EnumToReadnlyCollection insgesamt effektiv überspringen, aber dann wären Sie gezwungen, den Schlüssel mit int zu verwenden (was bedeutet, combobox1.SelectedValue zu erhalten) würde int zurückgeben und nicht den Aufzählungstyp). Sie umgehen das, wenn Sie eine KeyValueItem-Klasse erstellen, um MyItem zu ersetzen, und so weiter und so fort ...


quelle
1

Tut mir leid, dass ich diesen alten Thread erstellt habe.

Ich würde den folgenden Weg gehen, um die Aufzählung zu lokalisieren, da sie dem Benutzer aussagekräftige und lokalisierte Werte und nicht nur die Beschreibung über ein Dropdown-Listen-Textfeld in diesem Beispiel anzeigen kann.

Zuerst erstelle ich eine einfache Methode namens OwToStringByCulture, um lokalisierte Zeichenfolgen aus einer globalen Ressourcendatei abzurufen. In diesem Beispiel handelt es sich um BiBongNet.resx im Ordner App_GlobalResources. Stellen Sie in dieser Ressourcendatei sicher, dass alle Zeichenfolgen mit den Werten der Aufzählung übereinstimmen (ReallyNice, SortOfNice, NotNice). Bei dieser Methode übergebe ich den Parameter: resourceClassName, der normalerweise der Name der Ressourcendatei ist.

Als Nächstes erstelle ich eine statische Methode zum Füllen einer Dropdown-Liste mit enum als Datenquelle namens OwFillDataWithEnum. Diese Methode kann später mit jeder Aufzählung verwendet werden.

Dann habe ich auf der Seite mit einer Dropdown-Liste namens DropDownList1 in Page Load die folgende einfache Codezeile festgelegt, um die Aufzählung der Dropdown-Liste zu füllen.

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

Das ist es. Ich denke, mit einigen einfachen Methoden wie diesen können Sie jedes Listensteuerelement mit einer beliebigen Aufzählung füllen, nicht nur als beschreibende Werte, sondern als lokalisierten Text, der angezeigt werden soll. Sie können alle diese Methoden als Erweiterungsmethoden zur besseren Verwendung festlegen.

Ich hoffe das hilft. Teilen, um geteilt zu werden!

Hier sind die Methoden:

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
quelle
1
Enum HowNice {
  [Description("Really Nice")]
  ReallyNice,
  [Description("Kinda Nice")]
  SortOfNice,
  [Description("Not Nice At All")]
  NotNice
}

Um dies zu beheben, sollten Sie eine Erweiterungsmethode und ein Array von Zeichenfolgen wie folgt verwenden:

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

Einfacher Code und schnelle Dekodierung.

Sérgio
quelle
Die Zeichenfolgenvariable sollte statisch sein und wie folgt deklariert werden: Statische Zeichenfolge [] strings = new [] {...}
Sérgio
Das einzige Problem dabei ist, dass Sie für jede Aufzählung eine Funktion benötigen und die Beschreibung von der Aufzählung selbst getrennt ist ...
Avi Turner
1

Ich habe diesen Ansatz ausprobiert und er hat bei mir funktioniert.

Ich habe eine Wrapper-Klasse für Enums erstellt und den impliziten Operator überladen, damit ich ihn Enum-Variablen zuweisen kann (in meinem Fall musste ich ein Objekt an einen ComboBoxWert binden ).

Sie können Reflection verwenden, um die Aufzählungswerte so zu formatieren, wie Sie möchten. In meinem Fall DisplayAttributerufe ich die Aufzählungswerte ab (falls vorhanden).

Hoffe das hilft.

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

BEARBEITEN:

Nur für den Fall, ich benutze die folgende Funktion, um die enumWerte zu erhalten, die ich für die DataSourcederComboBox

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
quelle
0

Sobald Sie die GetDescriptionMethode haben (sie muss global statisch sein), können Sie sie über eine Erweiterungsmethode verwenden:

public static string ToString(this HowNice self)
{
    return GetDescription<HowNice>(self);
}
Scheu
quelle
0
Enum HowNice {   
[StringValue("Really Nice")]   
ReallyNice,   
[StringValue("Kinda Nice")]   
SortOfNice,   
[StringValue("Not Nice At All")]   
NotNice 
}

Status = ReallyNice.GetDescription()
user1308805
quelle
3
Willkommen bei stackoverflow! Es ist immer besser, eine kurze Beschreibung für einen Beispielcode bereitzustellen, um die Post-Genauigkeit zu verbessern :)
Picrofo Software
-1

Sie können Enum als definieren

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

und dann verwenden HowNice.GetStringValue().

Vrushal
quelle
2
Dies wird nicht kompiliert (ich habe .NET 3.5). Wo wird ´StringValue´ deklariert?
Ehrfurcht
1
Die Antwort von @scraimer ist dieselbe, außer dass er ein Attribut außerhalb des Frameworks verwendet, während Sie eine Art selbst definiertes Attribut verwenden.
Oliver