Как использовать имена пользователей для перечислений?
У меня есть перечисление вроде
Enum Complexity
{
NotSoComplex,
LittleComplex,
Complex,
VeryComplex
}
И я хочу использовать его в раскрывающемся списке, но не хочу видеть такие имена Camel в списке (выглядит очень странно для пользователей). Вместо этого я хотел бы иметь нормальную формулировку, например
Не так сложно
Маленький комплекс (и т.д.)
Кроме того, мое приложение является многоязычным, и я хотел бы иметь возможность отображать те строки, которые были локализованы, и я использую хелпер, TranslationHelper (string strID), который дает мне локализованную версию для идентификатора строки.
У меня есть рабочее решение, но не очень элегантное:
Я создаю вспомогательный класс для перечисления, с одним членом Complexity и ToString(), перезаписанным, как показано ниже (упрощен код)
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;
}
}
Несмотря на то, что я работоспособен, я не доволен этим, так как мне нужно скомпоновать его для разных перечислений, которые мне нужно использовать в picklists.
Есть ли у кого-нибудь предложение для более простого или более общего решения?
Спасибо
Богдан
Ответы
Ответ 1
Спасибо всем за ответы.
Наконец, я использовал комбинацию из Rex M и adrianbanks и добавил свои собственные улучшения, чтобы упростить привязку к ComboBox.
Изменения были необходимы, потому что, работая над кодом, я иногда понимал, что должен быть в состоянии исключить один элемент перечисления из комбо.
Например.
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
}
Поэтому иногда я хочу, чтобы список выбора отображал все, кроме AllComplexities (в дополнительных модулях редактирования) и в другое время, чтобы показать все (в фильтрах)
Вот что я сделал:
- Я создал метод расширения, который использует атрибут описания как ключ поиска локализации. Если атрибут описания отсутствует, я создаю ключ локализации поиска как EnumName_
EnumValue. Наконец, если перевод отсутствует, я просто разбил имя enum на основе camelcase для разделения слов, как показано adrianbanks. BTW, TranslationHelper - обертка вокруг resourceMgr.GetString(...)
Полный код показан ниже
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;
}
Я создал общий вспомогательный класс на основе Enum, который позволяет легко связывать перечисление с 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(); }
}
}
Затем я создал общий статический метод, который возвращает List > , как показано ниже
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;
}
и обертка для возврата списка со всеми членами
public static List<LocalizableEnum<T>> GetEnumList<T>()
{
return GetEnumList<T>(null);
}
Теперь давайте поместим все вещи и привязаем их к фактическому комбо:
// 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
Чтобы прочитать выбранное значение и использовать его, я использую код ниже
Complexity selComplexity = (Complexity)comboComplexity.SelectedValue;
Ответ 2
Основные дружественные имена
Используйте Описание атрибута: *
enum MyEnum
{
[Description("This is black")]
Black,
[Description("This is white")]
White
}
И удобный метод расширения для перечислений:
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;
}
Используется так:
MyEnum val = MyEnum.Black;
Console.WriteLine(val.GetDescription()); //writes "This is black"
(Обратите внимание, что это точно не работает для битовых флагов...)
Для локализации
В .NET существует хорошо установленный шаблон для обработки нескольких языков на строковое значение - используйте файл ресурсов и разверните метод расширения для чтения из файла ресурсов:
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;
}
Каждый раз, когда мы можем использовать существующую функциональность BCL для достижения того, чего хотим, это определенно первый путь для изучения. Это минимизирует сложность и использует шаблоны, уже знакомые многим другим разработчикам.
Объединяя все это
Чтобы привязать это к DropDownList, мы, вероятно, хотим отслеживать реальные значения перечисления в нашем элементе управления и ограничивать переведенное, дружественное имя визуальным сахаром. Мы можем сделать это, используя анонимный тип и свойства DataField в списке:
<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();
Позвольте сломать эту строку DataSource:
- Сначала мы вызываем
Enum.GetValues(typeof(MyEnum))
, что дает нам слабо типизированный Array
значений
- Затем мы вызываем
OfType<MyEnum>()
, который преобразует массив в IEnumerable<MyEnum>
- Затем мы вызываем
Select()
и предоставляем лямбду, которая проектирует новый объект с двумя полями: Описание и значение.
Свойства DataTextField и DataValueField оцениваются рефлексивно во время привязки данных, поэтому они будут искать поля в DataItem с соответствующими именами.
-Примечание в основной статье автор написал собственный класс DescriptionAttribute
, который не нужен, поскольку он уже существует в стандартных библиотеках .NET.-
Ответ 3
Использование атрибутов, как и в других ответах, является хорошим способом, но если вы просто хотите использовать текст из значений перечисления, следующий код будет разделен на основе верблюжьей оболочки значения:
public static string GetDescriptionOf(Enum enumType)
{
Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled);
return capitalLetterMatch.Replace(enumType.ToString(), " $&");
}
Вызов GetDescriptionOf(Complexity.NotSoComplex)
вернет Not So Complex
. Это можно использовать с любым значением перечисления.
Чтобы сделать его более полезным, вы можете сделать его методом расширения:
public static string ToFriendlyString(this Enum enumType)
{
Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled);
return capitalLetterMatch.Replace(enumType.ToString(), " $&");
}
Теперь вы вызываете его с помощью Complexity.NotSoComplex.ToFriendlyString()
для возврата Not So Complex
.
РЕДАКТИРОВАТЬ: просто заметил, что в вашем вопросе вы упомянули, что вам нужно локализовать текст. В этом случае я бы использовал атрибут, чтобы содержать ключ для поиска локализованного значения, но по умолчанию используется метод дружественных строк в качестве последнего средства, если локализованный текст не может быть найден. Вы бы определили, что перечислены следующим образом:
enum Complexity
{
[LocalisedEnum("Complexity.NotSoComplex")]
NotSoComplex,
[LocalisedEnum("Complexity.LittleComplex")]
LittleComplex,
[LocalisedEnum("Complexity.Complex")]
Complex,
[LocalisedEnum("Complexity.VeryComplex")]
VeryComplex
}
Вам также понадобится этот код:
[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;
}
}
Чтобы получить локализованные описания, вы должны вызвать:
Complexity.NotSoComplex.ToLocalisedString()
Это имеет несколько резервных случаев:
- если перечисление имеет атрибут
LocalisedEnum
, он будет использовать ключ для поиска переведенного текста
- если перечисление имеет атрибут
LocalisedEnum
, но локализованный текст не найден, по умолчанию используется метод разметки на верблюдах
- Если в перечислении не указан атрибут
LocalisedEnum
, он будет использовать метод разбиения на верблюжку
- при любой ошибке, по умолчанию используется значение ToString значения enum
Ответ 4
Я использую следующий класс
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.");
}
Что читает атрибут, называемый описанием
public enum PuppyType
{
[Description("Cute Puppy")]
CutePuppy = 0,
[Description("Silly Puppy")]
SillyPuppy
}