2009-08-25 10 views
19

devo un'enumerazione comeCome avere nomi userfriendly per le enumerazioni?

Enum Complexity 
{ 
    NotSoComplex, 
    LittleComplex, 
    Complex, 
    VeryComplex 
} 

e voglio usarlo in un elenco a discesa, ma non vogliono vedere tali nomi Camel nella lista (sembra davvero strano per gli utenti). Invece mi piacerebbe avere in testo normale, come Non così complessa piccolo complesso (ecc)

Inoltre, la mia domanda è multi-lang e vorrei essere in grado di visualizzare tali stringhe localizzate, e io uso un helper, TranslationHelper (string strID) che mi dà la versione localizzata per un id stringa.

ho una soluzione di lavoro, ma non molto elegante: Ho creare una classe di supporto per l'enumerazione, con una complessità membro e ToString() sovrascritto, come qui di seguito (codice semplificato)

public class ComplexityHelper 
{ 
    public ComplexityHelper(Complexity c, string desc) 
    { m_complex = c; m_desc=desc; } 

    public Complexity Complexity { get { ... } set {...} } 
    public override ToString() { return m_desc; } 

    //Then a static field like this 

    private static List<Complexity> m_cxList = null; 

    // and method that returns the status lists to bind to DataSource of lists 
    public static List<ComplexityHelper> GetComplexities() 
    { 
     if (m_cxList == null) 
     { 
      string[] list = TranslationHelper.GetTranslation("item_Complexities").Split(','); 
      Array listVal = Enum.GetValues(typeof(Complexities)); 
      if (list.Length != listVal.Length) 
       throw new Exception("Invalid Complexities translations (item_Complexities)"); 
      m_cxList = new List<Complexity>(); 
      for (int i = 0; i < list.Length; i++) 
      { 
      Complexity cx = (ComplexitylistVal.GetValue(i); 
      ComplexityHelper ch = new ComplexityHelper(cx, list[i]); 
      m_cxList.Add(ch); 
      } 
     } 
     return m_cxList; 
    } 
} 

Mentre praticabile , Non ne sono felice, dal momento che devo codificarlo in modo simile per varie enumerazioni che devo usare negli elenchi.

Qualcuno ha un suggerimento per una soluzione più semplice o più generica?

Grazie Bogdan

+0

Vedere

risposta

1

Grazie a tutti per tutte le risposte. Infine ho usato una combinazione di Rex M e adrianbanks e ho aggiunto i miei miglioramenti, per semplificare il binding a ComboBox.

Le modifiche erano necessarie perché, mentre si lavorava sul codice, mi sono reso conto a volte che devo essere in grado di escludere un elemento di enumerazione dalla combo. E.g.

Enum Complexity 
{ 
    // this will be used in filters, 
    // but not in module where I have to assign Complexity to a field 
    AllComplexities, 
    NotSoComplex, 
    LittleComplex, 
    Complex, 
    VeryComplex 
} 

Così a volte voglio l'elenco di selezione per mostrare tutti, ma AllComplexities (in add - modificare moduli) ed altro momento di mostrare tutto (nei filtri)

Ecco quello che ho fatto:

  1. Ho creato un metodo di estensione, che utilizza Descrizione attributo come chiave di ricerca di localizzazione. Se manca l'attributo Descrizione, creo la chiave di localizzazione di ricerca come EnumName_ EnumValue. Infine, se manca la traduzione, ho solo diviso il nome enum basato su Camelcase per separare le parole come mostrato da adrianbanks. BTW, TranslationHelper è un wrapper su resourceMgr.GetString (...)

Viene mostrato il codice completo di seguito

public static string GetDescription(this System.Enum value) 
{ 
    string enumID = string.Empty; 
    string enumDesc = string.Empty; 
    try 
    {   
     // try to lookup Description attribute 
     FieldInfo field = value.GetType().GetField(value.ToString()); 
     object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), true); 
     if (attribs.Length > 0) 
     { 
      enumID = ((DescriptionAttribute)attribs[0]).Description; 
      enumDesc = TranslationHelper.GetTranslation(enumID); 
     } 
     if (string.IsNullOrEmpty(enumID) || TranslationHelper.IsTranslationMissing(enumDesc)) 
     { 
      // try to lookup translation from EnumName_EnumValue 
      string[] enumName = value.GetType().ToString().Split('.'); 
      enumID = string.Format("{0}_{1}", enumName[enumName.Length - 1], value.ToString()); 
      enumDesc = TranslationHelper.GetTranslation(enumID); 
      if (TranslationHelper.IsTranslationMissing(enumDesc)) 
       enumDesc = string.Empty; 
     } 

     // try to format CamelCase to proper names 
     if (string.IsNullOrEmpty(enumDesc)) 
     { 
      Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled); 
      enumDesc = capitalLetterMatch.Replace(value.ToString(), " $&"); 
     } 
    } 
    catch (Exception) 
    { 
     // if any error, fallback to string value 
     enumDesc = value.ToString(); 
    } 

    return enumDesc; 
} 

ho creato una classe di supporto generica sulla base di Enum, che consentono di impegnare l'enum facilmente DataSource

public class LocalizableEnum 
{ 
    /// <summary> 
    /// Column names exposed by LocalizableEnum 
    /// </summary> 
    public class ColumnNames 
    { 
     public const string ID = "EnumValue"; 
     public const string EntityValue = "EnumDescription"; 
    } 
} 

public class LocalizableEnum<T> 
{ 

    private T m_ItemVal; 
    private string m_ItemDesc; 

    public LocalizableEnum(T id) 
    { 
     System.Enum idEnum = id as System.Enum; 
     if (idEnum == null) 
      throw new ArgumentException(string.Format("Type {0} is not enum", id.ToString())); 
     else 
     { 
      m_ItemVal = id; 
      m_ItemDesc = idEnum.GetDescription(); 
     } 
    } 

    public override string ToString() 
    { 
     return m_ItemDesc; 
    } 

    public T EnumValue 
    { 
     get { return m_ID; } 
    } 

    public string EnumDescription 
    { 
     get { return ToString(); } 
    } 

} 

Poi ho creato un metodo statico generico che restituisce un elenco>, come sotto

public static List<LocalizableEnum<T>> GetEnumList<T>(object excludeMember) 
{ 
    List<LocalizableEnum<T>> list =null; 
    Array listVal = System.Enum.GetValues(typeof(T)); 
    if (listVal.Length>0) 
    { 
     string excludedValStr = string.Empty; 
     if (excludeMember != null) 
      excludedValStr = ((T)excludeMember).ToString(); 

     list = new List<LocalizableEnum<T>>(); 
     for (int i = 0; i < listVal.Length; i++) 
     { 
      T currentVal = (T)listVal.GetValue(i); 
      if (excludedValStr != currentVal.ToString()) 
      { 
       System.Enum enumVal = currentVal as System.Enum; 
       LocalizableEnum<T> enumMember = new LocalizableEnum<T>(currentVal); 
       list.Add(enumMember); 
      } 
     } 
    } 
    return list; 
} 

e un involucro per tornare lista con tutti i membri

public static List<LocalizableEnum<T>> GetEnumList<T>() 
{ 
     return GetEnumList<T>(null); 
} 

È ora di mettere insieme tutte le cose e si legano a combo attuale:

// in module where we want to show items with all complexities 
// or just filter on one complexity 

comboComplexity.DisplayMember = LocalizableEnum.ColumnNames.EnumValue; 
comboComplexity.ValueMember = LocalizableEnum.ColumnNames.EnumDescription; 
comboComplexity.DataSource = EnumHelper.GetEnumList<Complexity>(); 
comboComplexity.SelectedValue = Complexity.AllComplexities; 

// .... 
// and here in edit module where we don't want to see "All Complexities" 
comboComplexity.DisplayMember = LocalizableEnum.ColumnNames.EnumValue; 
comboComplexity.ValueMember = LocalizableEnum.ColumnNames.EnumDescription; 
comboComplexity.DataSource = EnumHelper.GetEnumList<Complexity>(Complexity.AllComplexities); 
comboComplexity.SelectedValue = Complexity.VeryComplex; // set default value 

a leggere selezionato il valore e utilizzarlo, io uso il codice, come di seguito

Complexity selComplexity = (Complexity)comboComplexity.SelectedValue; 
60

nomi friendly base

Utilizzare i Description attribute: *

enum MyEnum 
{ 
    [Description("This is black")] 
    Black, 
    [Description("This is white")] 
    White 
} 

E un metodo di estensione portata di mano per le enumerazioni:

public static string GetDescription(this Enum value) 
{ 
    FieldInfo field = value.GetType().GetField(value.ToString()); 
    object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), true); 
    if(attribs.Length > 0) 
    { 
     return ((DescriptionAttribute)attribs[0]).Description; 
    } 
    return string.Empty; 
} 

Usato in questo modo:

MyEnum val = MyEnum.Black; 
Console.WriteLine(val.GetDescription()); //writes "This is black" 

(notare che questo non è esattamente funziona per flag di bit ...)

Per la localizzazione

C'è un modello ben consolidata nel .NET per la gestione di più lingue per valore stringa - utilizzare un resource file, ed espandere il metodo di estensione per leggere dal file di risorse:

public static string GetDescription(this Enum value) 
{ 
    FieldInfo field = value.GetType().GetField(value.ToString()); 
    object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), true)); 
    if(attribs.Length > 0) 
    { 
     string message = ((DescriptionAttribute)attribs[0]).Description; 
     return resourceMgr.GetString(message, CultureInfo.CurrentCulture); 
    } 
    return string.Empty; 
} 

Ogni volta che possiamo sfruttare la funzionalità BCL esistente per ottenere ciò che vogliamo, questa è sicuramente la prima strada da esplorare. Ciò minimizza la complessità e utilizza modelli già noti a molti altri sviluppatori.

Mettere tutto insieme

Per ottenere questo per l'associazione a una DropDownList, probabilmente vogliamo monitorare i valori reali enum nel nostro controllo e limitare il, nome descrittivo tradotto allo zucchero visivo.Siamo in grado di farlo utilizzando un tipo anonimo e le proprietà DataField della lista:

<asp:DropDownList ID="myDDL" 
        DataTextField="Description" 
        DataValueField="Value" /> 

myDDL.DataSource = Enum.GetValues(typeof(MyEnum)).OfType<MyEnum>().Select(
    val => new { Description = val.GetDescription(), Value = val.ToString() }); 

myDDL.DataBind(); 

Analizziamo quella linea DataSource:

  • Prima che chiamiamo Enum.GetValues(typeof(MyEnum)), che ci ottiene un loosely-digitato Array dei valori
  • Successivo chiamiamo OfType<MyEnum>() che converte la matrice a una IEnumerable<MyEnum>
  • Poi chiamiamo Select() e fornendo un lambda che proietta un nuovo oggetto con due campi, Descrizione e Valore.

Le proprietà DataTextField e DataValueField vengono valutate in modo riflessivo al momento del database, quindi cercheranno i campi su DataItem con nomi corrispondenti.

- Nota nell'articolo principale, l'autore ha scritto la propria classe DescriptionAttribute che non è necessaria, come già esistente nelle librerie standard di .NET. -

+5

Per la localizzazione, è possibile ignorare gli attributi descrizione e basta usare "MyEnum.Blac k "e" MyEnum.White "come nomi di risorse. – stevemegson

+0

@stevemegson che funzionerebbe sicuramente! Sono un grande purificatore SoC/SRP, e per me raddoppiare l'enumerazione sia come valore programmatico che come chiave risorsa multi-lingua scatena campane d'allarme :) –

+0

Ciao, Quello che vorrei realizzare è usare un'associazione come comboComplexity.DataSource = Enum.GetValues ​​(typeof (Complexity)); Ma usando questo io ottenere nel combo i nomi dei valori di enumerazione ancora, e non le loro descrizioni Inoltre, ho rinominato GetDescription a ToString(), nella speranza che combo uso che per nome enum, ma senza fortuna Qualsiasi suggerimento? Grazie – bzamfir

1

Io uso la seguente classe

public class EnumUtils 
    { 
    /// <summary> 
    ///  Reads and returns the value of the Description Attribute of an enumeration value. 
    /// </summary> 
    /// <param name="value">The enumeration value whose Description attribute you wish to have returned.</param> 
    /// <returns>The string value portion of the Description attribute.</returns> 
    public static string StringValueOf(Enum value) 
    { 
     FieldInfo fi = value.GetType().GetField(value.ToString()); 
     DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); 
     if (attributes.Length > 0) 
     { 
      return attributes[0].Description; 
     } 
     else 
     { 
      return value.ToString(); 
     } 
    } 

    /// <summary> 
    ///  Returns the Enumeration value that has a given Description attribute. 
    /// </summary> 
    /// <param name="value">The Description attribute value.</param> 
    /// <param name="enumType">The type of enumeration in which to search.</param> 
    /// <returns>The enumeration value that matches the Description value provided.</returns> 
    /// <exception cref="ArgumentException">Thrown when the specified Description value is not found with in the provided Enumeration Type.</exception> 
    public static object EnumValueOf(string value, Type enumType) 
    { 
     string[] names = Enum.GetNames(enumType); 
     foreach (string name in names) 
     { 
      if (StringValueOf((Enum)Enum.Parse(enumType, name)).Equals(value)) 
      { 
       return Enum.Parse(enumType, name); 
      } 
     } 

     throw new ArgumentException("The string is not a description or value of the specified enum."); 
    } 

Che recita un attributo chiamato descrizione

public enum PuppyType 
{ 
    [Description("Cute Puppy")] 
    CutePuppy = 0, 
    [Description("Silly Puppy")] 
    SillyPuppy 
} 
4

L'uso di attributi come nelle altre risposte è un buon modo per andare, ma se si voglio solo utilizzare il testo dai valori dell'enumerazione, il seguente codice verrà suddiviso in base al camel-case del valore:

public static string GetDescriptionOf(Enum enumType) 
{ 
    Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled); 
    return capitalLetterMatch.Replace(enumType.ToString(), " $&"); 
} 

La chiamata GetDescriptionOf(Complexity.NotSoComplex) restituirà Not So Complex. Può essere utilizzato con il valore di enumero qualsiasi.

Per renderlo più utile, si potrebbe rendere un metodo di estensione:

public static string ToFriendlyString(this Enum enumType) 
{ 
    Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled); 
    return capitalLetterMatch.Replace(enumType.ToString(), " $&"); 
} 

cal È ora chiami utilizzando Complexity.NotSoComplex.ToFriendlyString() di tornare Not So Complex.


EDIT: appena notato che nella sua domanda si menziona che è necessario localizzare il testo. In tal caso, utilizzerei un attributo per contenere una chiave per cercare il valore localizzato, ma per impostazione predefinita il metodo della stringa amichevole come ultima risorsa se non è possibile trovare il testo localizzato.Si definirebbe si enumerazioni in questo modo:

enum Complexity 
{ 
    [LocalisedEnum("Complexity.NotSoComplex")] 
    NotSoComplex, 
    [LocalisedEnum("Complexity.LittleComplex")] 
    LittleComplex, 
    [LocalisedEnum("Complexity.Complex")] 
    Complex, 
    [LocalisedEnum("Complexity.VeryComplex")] 
    VeryComplex 
} 

Si sarebbe anche bisogno di questo codice:

[AttributeUsage(AttributeTargets.Field, AllowMultiple=false, Inherited=true)] 
public class LocalisedEnum : Attribute 
{ 
    public string LocalisationKey{get;set;} 

    public LocalisedEnum(string localisationKey) 
    { 
     LocalisationKey = localisationKey; 
    } 
} 

public static class LocalisedEnumExtensions 
{ 
    public static string ToLocalisedString(this Enum enumType) 
    { 
     // default value is the ToString(); 
     string description = enumType.ToString(); 

     try 
     { 
      bool done = false; 

      MemberInfo[] memberInfo = enumType.GetType().GetMember(enumType.ToString()); 

      if (memberInfo != null && memberInfo.Length > 0) 
      { 
       object[] attributes = memberInfo[0].GetCustomAttributes(typeof(LocalisedEnum), false); 

       if (attributes != null && attributes.Length > 0) 
       { 
        LocalisedEnum descriptionAttribute = attributes[0] as LocalisedEnum; 

        if (description != null && descriptionAttribute != null) 
        { 
         string desc = TranslationHelper.GetTranslation(descriptionAttribute.LocalisationKey); 

         if (desc != null) 
         { 
          description = desc; 
          done = true; 
         } 
        } 
       } 
      } 

      if (!done) 
      { 
       Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled); 
       description = capitalLetterMatch.Replace(enumType.ToString(), " $&"); 
      } 
     } 
     catch 
     { 
      description = enumType.ToString(); 
     } 

     return description; 
    } 
} 

Per ottenere le descrizioni localizzate, si sarebbe quindi chiamare:

Complexity.NotSoComplex.ToLocalisedString() 

Questo ha diversi casi di riserva:

  • se l'enu m ha un attributo LocalisedEnum definita, userà la chiave per cercare il testo tradotto
  • se l'enumerazione ha un attributo LocalisedEnum definito ma nessun testo localizzato viene trovato, il valore predefinito utilizzando il metodo split cammello caso
  • se enum non ha un attributo LocalisedEnum definita, utilizzerà il metodo split cammello caso
  • su ogni errore, il valore di default per il ToString del valore enum
+0

Ciao Grazie per la risposta. Ho provato il tuo suggerimento e ho cercato di associare il combo a enum con codice come questo comboComplexity.DataSource = Enum.GetValues ​​(typeof (Complexity)); Ma questo ha reso l'elenco per visualizzare solo nomi enum predefiniti Ho rinominato anche ToLocalizedString() a ToString() (sapendo che questo è effettivamente chiamato da Combo) ma ancora non ha funzionato. Qualche suggerimento? Vorrei un binding semplice come questo, poiché con un enum con 20+ valori, aggiungere tutti i valori uno ad uno sarà un dolore Grazie – bzamfir

+0

Rinominando ToLocalizedString() to ToString(), non hai sovrascritto ToString() metodo dell'enum. Invece, hai creato un metodo di estensione con lo stesso nome del metodo ToString() già esistente dell'enumerazione.Chiamando ToString() chiamerà il metodo esistente e non il metodo di estensione. Dovrai ottenere le stringhe localizzate per ogni valore enum e database per quel set di stringhe. – adrianbanks

Problemi correlati