Comment avoir une zone de liste déroulante liée enum avec un formatage de chaîne personnalisé pour les valeurs enum?

135

Dans le post Enum ToString , une méthode est décrite pour utiliser l'attribut personnalisé DescriptionAttributecomme ceci:

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

Et puis, vous appelez une fonction GetDescription, en utilisant une syntaxe comme:

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

Mais cela ne m'aide pas vraiment quand je veux simplement remplir un ComboBox avec les valeurs d'une énumération, car je ne peux pas forcer le ComboBox à appelerGetDescription .

Ce que je veux a les exigences suivantes:

  • En train de lire (HowNice)myComboBox.selectedItem renverra la valeur sélectionnée comme valeur d'énumération.
  • L'utilisateur doit voir les chaînes d'affichage conviviales, et pas seulement le nom des valeurs d'énumération. Ainsi, au lieu de voir " NotNice", l'utilisateur verrait "Not Nice At All ".
  • Espérons que la solution nécessitera des modifications minimes du code des énumérations existantes.

Évidemment, je pourrais implémenter une nouvelle classe pour chaque énumération que je crée, et la remplacer ToString(), mais c'est beaucoup de travail pour chaque énumération, et je préfère éviter cela.

Des idées?

Heck, je vais même jeter un câlin en guise de prime :-)

Shalom Craimer
la source
1
jjnguy a raison de dire que les énumérations Java résolvent bien ce problème ( javahowto.blogspot.com/2006/10/… ), mais c'est d'une pertinence discutable.
Matthew Flaschen
8
Les énumérations Java sont une blague. Peut-être qu'ils ajouteront des propriétés en 2020: /
Chad Grant
Pour une solution plus légère (mais sans doute moins robuste), consultez mon fil de discussion .
Gutblender

Réponses:

42

Vous pouvez écrire un TypeConverter qui lit les attributs spécifiés pour les rechercher dans vos ressources. Ainsi, vous obtiendrez un support multilingue pour les noms d'affichage sans trop de tracas.

Examinez les méthodes ConvertFrom / ConvertTo de TypeConverter et utilisez la réflexion pour lire les attributs de vos champs d' énumération .

sisve
la source
OK, j'ai écrit du code (voir ma réponse à cette question) - pensez-vous que cela suffit, est-ce que je manque quelque chose?
Shalom Craimer
1
Joli. Mieux vaut globalement, mais pourrait être exagéré pour votre logiciel banal qui ne sera jamais globalisé de quelque manière que ce soit. (Je sais, cette hypothèse se révélera fausse plus tard. ;-))
peSHIr
85

ComboBoxa tout ce dont vous avez besoin: la FormattingEnabledpropriété, que vous devez définir true, et l' Formatévénement, où vous devrez placer la logique de mise en forme souhaitée. Donc,

myComboBox.FormattingEnabled = true;
myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e)
    {
        e.Value = GetDescription<HowNice>((HowNice)e.Value);
    }
Anton Gogolev
la source
Cela fonctionne-t-il uniquement avec les combobox de données? Sinon, je ne peux pas déclencher l'événement Format.
SomethingBetter
le seul problème ici est que vous ne pouvez pas avoir la liste triée avec votre logique
GorillaApe
C'est une excellente solution. J'en aurais besoin pour travailler avec un DataGridComboBoxColumncependant. Une chance de le résoudre? Je ne suis pas en mesure de trouver un moyen d'obtenir l' accès au ComboBoxdu DataGridComboBoxColumn.
Soko
46

Pas! Les énumérations sont des primitives et non des objets d'interface utilisateur - les faire servir l'interface utilisateur dans .ToString () serait plutôt mauvais du point de vue de la conception. Vous essayez de résoudre le mauvais problème ici: le vrai problème est que vous ne voulez pas que Enum.ToString () apparaisse dans la liste déroulante!

Maintenant, c'est un problème très résoluble! Vous créez un objet d'interface utilisateur pour représenter vos éléments de liste déroulante:

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

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

Et puis ajoutez simplement des instances de cette classe à la collection Items de votre zone de liste déroulante et définissez ces propriétés:

comboBox.ValueMember = "Value";
comboBox.DisplayMember = "Description";
Ponceuse
la source
1
Je suis entièrement d'accord. Vous ne devez pas non plus exposer le résultat de ToString () à l'interface utilisateur. Et vous n'obtenez pas de localisation.
Øyvind Skaar
Je sais que c'est vieux, mais en quoi est-ce différent?
nportelli
2
J'ai vu une solution similaire où au lieu d'utiliser une classe personnalisée, ils ont mappé les valeurs d'énumération sur a Dictionaryet ont utilisé les propriétés Keyet Valuecomme DisplayMemberet ValueMember.
Jeff B
42

TypeConverter. Je pense que c'est ce que je cherchais. Salut à tous Simon Svensson !

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

Tout ce que j'ai besoin de changer dans mon énumération actuelle est d'ajouter cette ligne avant leur déclaration.

[TypeConverter(typeof(EnumToStringUsingDescription))]

Une fois que je fais cela, toute énumération sera affichée en utilisant le DescriptionAttributede ses champs.

Oh, et le TypeConverterserait défini comme ceci:

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

Cela m'aide avec mon cas ComboBox, mais ne remplace évidemment pas le fichier ToString(). Je suppose que je vais me contenter de ça en attendant ...

Shalom Craimer
la source
3
Vous gérez Enum -> String, mais vous aurez également besoin Enum> InstanceDescriptor et String -> Enum si vous voulez une implémentation complète. Mais je suppose que l'afficher est suffisant pour vos besoins du moment. ;)
sisve le
1
Cette solution ne fonctionne que lorsque vos descriptions sont statiques, malheureusement.
Llyle
À propos, l'utilisation de TypeConverter n'est pas liée à des descriptions statiques, le coverter peut renseigner des valeurs provenant d'autres sources que des attributs.
Dmitry Tashkinov
3
Je me tire les cheveux depuis quelques heures maintenant, mais cela ne semble toujours pas fonctionner, même dans les applications de console simples. J'ai décoré l'énumération avec [TypeConverter(typeof(EnumToStringUsingDescription))] public enum MyEnum {[Description("Blah")] One}, essayé de le faire Console.WriteLine(MyEnum.One)et il sort toujours comme "One". Avez-vous besoin d'une magie spéciale comme TypeDescriptor.GetConverter(typeof(MyEnum)).ConvertToString(MyEnum.One)(qui fonctionne bien)?
Dav le
1
@scraimer J'ai publié une version de votre code prenant en charge les indicateurs. tous droits réservés à vous ...
Avi Turner
32

En utilisant votre exemple d'énumération:

using System.ComponentModel;

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

Créez une extension:

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

Ensuite, vous pouvez utiliser quelque chose comme ce qui suit:

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

Voir: http://www.blackwasp.co.uk/EnumDescription.aspx pour plus d'informations. Le mérite revient à Richrd Carr pour la solution

Tyler Durden
la source
J'ai suivi les détails sur le site référencé et l'ai utilisé comme suit, cela me semble simple 'string myDesc = HowNice.ReallyNice.Description ();' myDesc sortira Really Nice
Ananda
8

Vous pouvez créer une structure générique que vous pouvez utiliser pour toutes vos énumérations contenant des descriptions. Avec les conversions implicites vers et depuis la classe, vos variables fonctionnent toujours comme l'énumération, sauf pour la méthode 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;
    }

}

Exemple d'utilisation:

Described<HowNice> nice = HowNice.ReallyNice;

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

Je ne pense pas que vous puissiez le faire sans simplement vous lier à un type différent - du moins, pas de manière pratique. Normalement, même si vous ne pouvez pas contrôler ToString(), vous pouvez utiliser a TypeConverterpour faire un formatage personnalisé - mais IIRC System.ComponentModelne respecte pas cela pour les énumérations.

Vous pourriez vous lier à l'une string[]des descriptions ou à quelque chose comme une paire clé / valeur? (description / valeur) - quelque chose comme:

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

Et puis lier à EnumWrapper<HowNice>.GetValues()

Marc Gravell
la source
1
Le nom 'GetDescription' n'existe pas dans le contexte actuel. Im using .NET 4.0
Muhammad Adeel Zahid
@MuhammadAdeelZahid regarde de près le début de la question - qui vient de l'article lié: stackoverflow.com/questions/479410/enum-tostring
Marc Gravell
désolé mais je ne peux obtenir aucun indice de la question. votre méthode ne compile pas et affiche l'erreur.
Muhammad Adeel Zahid
Salut Marc, j'ai essayé ton idée. Cela fonctionne, mais theComboBox.SelectItemc'est le type de EnumWrapper<T>, au lieu de Tlui - même. Je pense que Scraimer veut Reading (HowNice)myComboBox.selectedItem will return the selected value as the enum value..
Peter Lee
5

La meilleure façon de faire est de créer un cours.

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

Je pense que c'est la meilleure façon de procéder.

Lorsqu'il est bourré dans des comboboxes, le joli ToString sera affiché, et le fait que personne ne puisse créer plus d'instances de votre classe en fait essentiellement une énumération.

ps il faudra peut-être quelques légères corrections de syntaxe, je ne suis pas très bon avec C #. (Gars de Java)

jjnguy
la source
1
Comment cela aide-t-il au problème de la liste déroulante?
peSHIr
Eh bien, maintenant, lorsque le nouvel objet est placé dans une zone de liste déroulante, sa ToString sera correctement affichée et la classe agit toujours comme une énumération.
jjnguy
1
Cela aurait aussi été ma réponse.
Mikko Rantanen
3
Et voir comment l'affiche originale ne voulait explicitement pas de cours. Je ne pense pas qu'une classe soit autant de travail. Vous pouvez résumer la description et la substitution ToString à une classe parente pour toutes les énumérations. Après cela, tout ce dont vous avez besoin est un constructeur private HowNice(String desc) : base(desc) { }et les champs statiques.
Mikko Rantanen
J'espérais éviter cela, car cela signifie que chaque énumération que je fais nécessitera sa propre classe. Pouah.
Shalom Craimer
3

Étant donné que vous préférez ne pas créer de classe pour chaque énumération, je vous recommande de créer un dictionnaire de la valeur enum / texte d'affichage et de le lier à la place.

Notez que cela a une dépendance sur les méthodes de la méthode GetDescription dans la publication d'origine.

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
la source
Bonne idée. Mais comment utiliserais-je cela avec une combobox? Une fois que l'utilisateur sélectionne un élément dans la liste déroulante, comment savoir lequel des éléments il a sélectionné? Rechercher par la chaîne Description? Cela me démange la peau ... (il pourrait y avoir une chaîne "collision" entre les chaînes Description)
Shalom Craimer
La clé de l'élément sélectionné sera la valeur d'énumération réelle. Aussi, ne heurtez pas les chaînes de description - comment l'utilisateur fera-t-il la différence?
Richard Szalay
<cont> si vous avez des chaînes de description qui entrent en collision, alors vous ne devriez pas lier les valeurs de l'énumération directement à une zone de liste déroulante.
Richard Szalay
hmmm ... Eh bien, pourriez-vous me donner un exemple de code sur la façon dont vous ajouteriez des éléments à la liste déroulante? Tout ce à quoi je peux penser, c'est "foreach (string s in descriptionsDict.Values) {this.combobox.Items.Add (s);}"
Shalom Craimer
1
ComboBox.DataSource = dictionnaire;
Richard Szalay
3

Impossible de remplacer la ToString () des énumérations en C #. Cependant, vous pouvez utiliser des méthodes d'extension;

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

Bien sûr, vous devrez faire un appel explicite à la méthode, ie;

HowNice.ReallyNice.ToString(0)

Ce n'est pas une bonne solution, avec une instruction switch et tout - mais cela devrait fonctionner et, espérons-le, sans beaucoup de réécritures ...

Björn
la source
Sachez que le contrôle qui se lie à votre énumération n'appellerait pas cette méthode d'extension, il appellerait l'implémentation par défaut.
Richard Szalay
Droite. C'est donc une option viable si vous avez besoin d'une description quelque part, cela n'aide pas avec le problème de combobox posé.
peSHIr
Un plus gros problème est que cela ne sera jamais appelé (en tant que méthode d'extension) - les méthodes d'instance qui existent déjà ont toujours la priorité.
Marc Gravell
Bien sûr, Marc a raison (comme toujours?). Mon expérience .NET est minime, mais fournir un paramètre factice à la méthode devrait faire l'affaire. Réponse modifiée.
Björn
2

Suite à la réponse @scraimer, voici une version du convertisseur de type enum-to-string, qui prend également en charge les indicateurs:

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

}

Et une méthode d'extension pour l'utiliser:

    /// <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
la source
1

J'écrirais une classe générique à utiliser avec n'importe quel type. J'ai utilisé quelque chose comme ça dans le passé:

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
}

En plus de cela, vous pouvez ajouter une "méthode de fabrique" statique pour créer une liste d'éléments de combobox avec un type enum (à peu près identique à la méthode GetDescriptions que vous avez là). Cela vous éviterait d'avoir à implémenter une entité pour chaque type d'énumération, et fournirait également un endroit agréable / logique pour la méthode d'assistance "GetDescriptions" (personnellement, je l'appellerais FromEnum (T obj) ...

Dan C.
la source
1

Créez une collection qui contient ce dont vous avez besoin (comme des objets simples contenant une Valuepropriété contenant la HowNicevaleur d'énumération et une Descriptionpropriété contenant GetDescription<HowNice>(Value)et reliez le combo à cette collection.

Un peu comme ça:

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

lorsque vous avez une classe de collection comme celle-ci:

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

}

Comme vous pouvez le voir, cette collection est facilement personnalisable avec lambda pour sélectionner un sous-ensemble de votre énumérateur et / ou implémenter une mise en forme personnalisée au stringlieu d'utiliser la GetDescription<T>(x)fonction que vous mentionnez.

peSHIr
la source
Excellent, mais je recherche quelque chose qui nécessite encore moins de travail dans le code.
Shalom Craimer
Même si vous pouvez utiliser la même collection générique pour ce genre de chose pour tous vos recenseurs? Je ne suggérais pas l'écriture personnalisée d'une telle collection pour chaque énumération, bien sûr.
peSHIr
1

Vous pouvez utiliser PostSharp pour cibler Enum.ToString et ajouter tout le code souhaité. Cela ne nécessite aucune modification de code.

majkinetor
la source
1

Ce dont vous avez besoin est de transformer une énumération en ReadonlyCollection et de lier la collection à la liste déroulante (ou à tout contrôle activé par paire clé-valeur)

Tout d'abord, vous avez besoin d'une classe pour contenir les éléments de la liste. Puisque tout ce dont vous avez besoin est la paire int / string, je suggère d'utiliser une interface et un combo de classe de base afin que vous puissiez implémenter la fonctionnalité dans n'importe quel objet de votre choix:

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
}

Voici l'interface et un exemple de classe qui l'implémente.Notez que la classe 'Key est fortement typée dans Enum, et que les propriétés IValueDescritionItem sont implémentées explicitement (donc la classe peut avoir n'importe quelles propriétés et vous pouvez CHOISIR celles qui implémentent le Paire clé / valeur.

Maintenant, la classe 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)];
        }
    }

}

Donc, tout ce dont vous avez besoin dans votre code est:

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

N'oubliez pas que votre collection est tapée avec MyItem, donc la valeur de la zone de liste déroulante doit renvoyer une valeur d'énumération si vous vous liez à la propriété appropriée.

J'ai ajouté la propriété T this [Enum t] pour rendre la collection encore plus utile qu'un simple consommable combo, par exemple textBox1.Text = enumcol [HowNice.ReallyNice] .NicenessDescription;

Vous pouvez bien sûr choisir de transformer MyItem en une classe Key / Value utilisée uniquement pour cette puprose en sautant efficacement MyItem dans les arguments de type de EnumToReadnlyCollection, mais vous seriez alors obligé d'utiliser int pour la clé (ce qui signifie obtenir combobox1. retournerait int et non le type enum). Vous contourner ce problème si vous créez une classe KeyValueItem pour remplacer MyItem et ainsi de suite ...


la source
1

Désolé d'avoir récupéré ce vieux fil de discussion.

J'irais de la manière suivante pour localiser enum, car il peut afficher des valeurs significatives et localisées à l'utilisateur, pas seulement une description, via un champ de texte de liste déroulante dans cet exemple.

Tout d'abord, je crée une méthode simple appelée OwToStringByCulture pour obtenir des chaînes localisées à partir d'un fichier de ressources globales, dans cet exemple, il s'agit de BiBongNet.resx dans le dossier App_GlobalResources. Dans ce fichier de ressources, assurez-vous que toutes les chaînes sont identiques aux valeurs de l'énumération (ReallyNice, SortOfNice, NotNice). Dans cette méthode, je passe le paramètre: resourceClassName qui est généralement le nom du fichier de ressources.

Ensuite, je crée une méthode statique pour remplir une liste déroulante avec enum comme source de données, appelée OwFillDataWithEnum. Cette méthode peut être utilisée avec n'importe quelle énumération plus tard.

Ensuite, dans la page avec une liste déroulante appelée DropDownList1, j'ai défini dans le Page Load ce qui suit juste une simple ligne de code pour remplir l'énumération de la liste déroulante.

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

C'est tout. Je pense qu'avec quelques méthodes simples comme celles-ci, vous pouvez remplir n'importe quel contrôle de liste avec n'importe quelle énumération, avec non seulement des valeurs descriptives, mais un texte localisé à afficher. Vous pouvez utiliser toutes ces méthodes comme méthodes d'extension pour une meilleure utilisation.

J'espère que cette aide. Partagez pour être partagé!

Voici les méthodes:

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

Pour résoudre ce problème, vous devez utiliser une méthode d'extension et un tableau de chaînes comme ceci:

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

Code simple et décodage rapide.

Sérgio
la source
la variable strings doit être Static et déclarée comme suit: Static String [] strings = new [] {...}
Sérgio
Le seul problème avec cela, c'est que vous aurez besoin d'avoir une fonction pour chaque énumération, et la description sera en dehors de l'énumération elle-même ...
Avi Turner
1

J'ai essayé cette approche et cela a fonctionné pour moi.

J'ai créé une classe wrapper pour les enums et surchargé l'opérateur implicite afin que je puisse l'affecter à des variables enum (dans mon cas, je devais lier un objet à une ComboBoxvaleur).

Vous pouvez utiliser la réflexion pour mettre en forme les valeurs d'énumération comme vous le souhaitez, dans mon cas, je récupère les DisplayAttributevaleurs d'énumération (le cas échéant).

J'espère que cela t'aides.

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

ÉDITER:

Au cas où, j'utilise la fonction suivante pour obtenir les enumvaleurs que j'utilise pour DataSourceleComboBox

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
la source
0

Une fois que vous avez la GetDescriptionméthode (elle doit être statique globale), vous pouvez l'utiliser via une méthode d'extension:

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

Status = ReallyNice.GetDescription()
user1308805
la source
3
Bienvenue dans stackoverflow! Il est toujours préférable de fournir une brève description d'un exemple de code pour améliorer la précision de la publication :)
Picrofo Software
-1

Vous pouvez définir Enum comme

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

puis utilisez HowNice.GetStringValue().

Vrushal
la source
2
Cela ne compile pas (j'ai .NET 3.5). Où est déclarée «StringValue»?
awe le
1
La réponse de @scraimer est la même, sauf qu'il utilise un attribut hors du cadre, alors que vous utilisez une sorte d'attribut auto-défini.
Oliver