以下面的代码为例:
public enum ExampleEnum { FooBar, BarFoo }
public class ExampleClass : INotifyPropertyChanged
{
private ExampleEnum example;
public ExampleEnum ExampleProperty
{ get { return example; } { /* set and notify */; } }
}
我想把属性ExampleProperty绑定到一个组合框,这样它就显示了选项“FooBar”和“BarFoo”,并在模式双向工作。最理想的情况下,我希望我的ComboBox定义看起来像这样:
<ComboBox ItemsSource="What goes here?" SelectedItem="{Binding Path=ExampleProperty}" />
目前我有处理程序的组合框。SelectionChanged和ExampleClass。在我手动绑定的窗口中安装了PropertyChanged事件。
有没有更好的或者某种权威的方法?你通常会使用转换器,你将如何填充组合框与正确的值?我现在甚至不想从i18n开始。
Edit
因此,一个问题得到了回答:我如何用正确的值填充组合框。
通过ObjectDataProvider从静态Enum中检索Enum值作为字符串列表。getvalue方法:
<Window.Resources>
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type sys:Enum}"
x:Key="ExampleEnumValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="ExampleEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
这可以作为我的组合框的ItemsSource:
<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"/>
您可以创建自定义标记扩展。
用法示例:
enum Status
{
[Description("Available.")]
Available,
[Description("Not here right now.")]
Away,
[Description("I don't have time right now.")]
Busy
}
在你的XAML顶部:
xmlns:my="clr-namespace:namespace_to_enumeration_extension_class
然后……
<ComboBox
ItemsSource="{Binding Source={my:Enumeration {x:Type my:Status}}}"
DisplayMemberPath="Description"
SelectedValue="{Binding CurrentStatus}"
SelectedValuePath="Value" />
而实施……
public class EnumerationExtension : MarkupExtension
{
private Type _enumType;
public EnumerationExtension(Type enumType)
{
if (enumType == null)
throw new ArgumentNullException("enumType");
EnumType = enumType;
}
public Type EnumType
{
get { return _enumType; }
private set
{
if (_enumType == value)
return;
var enumType = Nullable.GetUnderlyingType(value) ?? value;
if (enumType.IsEnum == false)
throw new ArgumentException("Type must be an Enum.");
_enumType = value;
}
}
public override object ProvideValue(IServiceProvider serviceProvider) // or IXamlServiceProvider for UWP and WinUI
{
var enumValues = Enum.GetValues(EnumType);
return (
from object enumValue in enumValues
select new EnumerationMember{
Value = enumValue,
Description = GetDescription(enumValue)
}).ToArray();
}
private string GetDescription(object enumValue)
{
var descriptionAttribute = EnumType
.GetField(enumValue.ToString())
.GetCustomAttributes(typeof (DescriptionAttribute), false)
.FirstOrDefault() as DescriptionAttribute;
return descriptionAttribute != null
? descriptionAttribute.Description
: enumValue.ToString();
}
public class EnumerationMember
{
public string Description { get; set; }
public object Value { get; set; }
}
}
基于ageektrapped提供的已被接受但现已删除的答案,我创建了一个精简版,没有一些更高级的功能。这里包含的所有代码都允许您复制粘贴它,而不会被链接失效阻塞。
我使用System.ComponentModel.DescriptionAttribute,它实际上用于设计时描述。如果你不喜欢使用这个属性,你可以创建自己的属性,但我认为使用这个属性真的可以完成工作。如果不使用该属性,则名称将默认为代码中枚举值的名称。
public enum ExampleEnum {
[Description("Foo Bar")]
FooBar,
[Description("Bar Foo")]
BarFoo
}
下面是用作项目源的类:
public class EnumItemsSource : Collection<String>, IValueConverter {
Type type;
IDictionary<Object, Object> valueToNameMap;
IDictionary<Object, Object> nameToValueMap;
public Type Type {
get { return this.type; }
set {
if (!value.IsEnum)
throw new ArgumentException("Type is not an enum.", "value");
this.type = value;
Initialize();
}
}
public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
return this.valueToNameMap[value];
}
public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) {
return this.nameToValueMap[value];
}
void Initialize() {
this.valueToNameMap = this.type
.GetFields(BindingFlags.Static | BindingFlags.Public)
.ToDictionary(fi => fi.GetValue(null), GetDescription);
this.nameToValueMap = this.valueToNameMap
.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
Clear();
foreach (String name in this.nameToValueMap.Keys)
Add(name);
}
static Object GetDescription(FieldInfo fieldInfo) {
var descriptionAttribute =
(DescriptionAttribute) Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute));
return descriptionAttribute != null ? descriptionAttribute.Description : fieldInfo.Name;
}
}
你可以像这样在XAML中使用它:
<Windows.Resources>
<local:EnumItemsSource
x:Key="ExampleEnumItemsSource"
Type="{x:Type local:ExampleEnum}"/>
</Windows.Resources>
<ComboBox
ItemsSource="{StaticResource ExampleEnumItemsSource}"
SelectedValue="{Binding ExampleProperty, Converter={StaticResource ExampleEnumItemsSource}}"/>
我不喜欢在UI中使用enum的名称。我更喜欢使用不同的值用户(DisplayMemberPath)和不同的值(enum在这种情况下)(SelectedValuePath)。这两个值可以打包到KeyValuePair中并存储在字典中。
XAML
<ComboBox Name="fooBarComboBox"
ItemsSource="{Binding Path=ExampleEnumsWithCaptions}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Path=ExampleProperty, Mode=TwoWay}" >
C#
public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions { get; } =
new Dictionary<ExampleEnum, string>()
{
{ExampleEnum.FooBar, "Foo Bar"},
{ExampleEnum.BarFoo, "Reversed Foo Bar"},
//{ExampleEnum.None, "Hidden in UI"},
};
private ExampleEnum example;
public ExampleEnum ExampleProperty
{
get { return example; }
set { /* set and notify */; }
}
编辑:与MVVM模式兼容。
下面是一个使用helper方法的通用解决方案。
它还可以处理任何底层类型的枚举(byte、sbyte、uint、long等)。
辅助方法:
static IEnumerable<object> GetEnum<T>() {
var type = typeof(T);
var names = Enum.GetNames(type);
var values = Enum.GetValues(type);
var pairs =
Enumerable.Range(0, names.Length)
.Select(i => new {
Name = names.GetValue(i)
, Value = values.GetValue(i) })
.OrderBy(pair => pair.Name);
return pairs;
}//method
视图模型:
public IEnumerable<object> EnumSearchTypes {
get {
return GetEnum<SearchTypes>();
}
}//property
下拉列表框:
<ComboBox
SelectedValue ="{Binding SearchType}"
ItemsSource ="{Binding EnumSearchTypes}"
DisplayMemberPath ="Name"
SelectedValuePath ="Value"
/>
这是DevExpress基于Gregor S.投票最多的答案给出的具体答案(目前有128票)。
这意味着我们可以在整个应用程序中保持样式一致:
不幸的是,如果没有一些修改,最初的答案不能与DevExpress的ComboBoxEdit一起工作。
首先,ComboBoxEdit的XAML:
<dxe:ComboBoxEdit ItemsSource="{Binding Source={xamlExtensions:XamlExtensionEnumDropdown {x:myEnum:EnumFilter}}}"
SelectedItem="{Binding BrokerOrderBookingFilterSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMember="Description"
MinWidth="144" Margin="5"
HorizontalAlignment="Left"
IsTextEditable="False"
ValidateOnTextInput="False"
AutoComplete="False"
IncrementalFiltering="True"
FilterCondition="Like"
ImmediatePopup="True"/>
不用说,你需要将xamlExtensions指向包含XAML扩展类的命名空间(下面定义):
xmlns:xamlExtensions="clr-namespace:XamlExtensions"
我们必须将myEnum指向包含枚举的命名空间:
xmlns:myEnum="clr-namespace:MyNamespace"
然后,枚举:
namespace MyNamespace
{
public enum EnumFilter
{
[Description("Free as a bird")]
Free = 0,
[Description("I'm Somewhat Busy")]
SomewhatBusy = 1,
[Description("I'm Really Busy")]
ReallyBusy = 2
}
}
XAML中的问题是我们不能使用SelectedItemValue,因为这会抛出一个错误,因为setter是不可访问的(DevExpress,这是您的一部分疏忽)。所以我们必须修改我们的ViewModel来直接从对象中获取值:
private EnumFilter _filterSelected = EnumFilter.All;
public object FilterSelected
{
get
{
return (EnumFilter)_filterSelected;
}
set
{
var x = (XamlExtensionEnumDropdown.EnumerationMember)value;
if (x != null)
{
_filterSelected = (EnumFilter)x.Value;
}
OnPropertyChanged("FilterSelected");
}
}
为了完整起见,这里是原始答案(稍微重命名)的XAML扩展:
namespace XamlExtensions
{
/// <summary>
/// Intent: XAML markup extension to add support for enums into any dropdown box, see http://bit.ly/1g70oJy. We can name the items in the
/// dropdown box by using the [Description] attribute on the enum values.
/// </summary>
public class XamlExtensionEnumDropdown : MarkupExtension
{
private Type _enumType;
public XamlExtensionEnumDropdown(Type enumType)
{
if (enumType == null)
{
throw new ArgumentNullException("enumType");
}
EnumType = enumType;
}
public Type EnumType
{
get { return _enumType; }
private set
{
if (_enumType == value)
{
return;
}
var enumType = Nullable.GetUnderlyingType(value) ?? value;
if (enumType.IsEnum == false)
{
throw new ArgumentException("Type must be an Enum.");
}
_enumType = value;
}
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var enumValues = Enum.GetValues(EnumType);
return (
from object enumValue in enumValues
select new EnumerationMember
{
Value = enumValue,
Description = GetDescription(enumValue)
}).ToArray();
}
private string GetDescription(object enumValue)
{
var descriptionAttribute = EnumType
.GetField(enumValue.ToString())
.GetCustomAttributes(typeof (DescriptionAttribute), false)
.FirstOrDefault() as DescriptionAttribute;
return descriptionAttribute != null
? descriptionAttribute.Description
: enumValue.ToString();
}
#region Nested type: EnumerationMember
public class EnumerationMember
{
public string Description { get; set; }
public object Value { get; set; }
}
#endregion
}
}
免责声明:我与DevExpress没有任何关系。Telerik也是一个很棒的图书馆。
我最喜欢的方法是使用ValueConverter,这样ItemsSource和SelectedValue都绑定到相同的属性。这不需要额外的属性来保持ViewModel的美观和整洁。
<ComboBox ItemsSource="{Binding Path=ExampleProperty, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path=ExampleProperty}" />
以及转换器的定义:
public static class EnumHelper
{
public static string Description(this Enum e)
{
return (e.GetType()
.GetField(e.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.FirstOrDefault() as DescriptionAttribute)?.Description ?? e.ToString();
}
}
[ValueConversion(typeof(Enum), typeof(IEnumerable<ValueDescription>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Enum.GetValues(value.GetType())
.Cast<Enum>()
.Select(e => new ValueDescription() { Value = e, Description = e.Description()})
.ToList();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
此转换器可用于任何枚举。ValueDescription只是一个具有Value属性和Description属性的简单类。您可以很容易地使用带有Item1和Item2的Tuple,或带有Key和Value而不是Value和Description的KeyValuePair,或任何其他您选择的类,只要它可以保存枚举值和该枚举值的字符串描述。
Code
public enum RULE
{
[Description( "Любые, без ограничений" )]
any,
[Description( "Любые если будет три в ряд" )]
anyThree,
[Description( "Соседние, без ограничений" )]
nearAny,
[Description( "Соседние если будет три в ряд" )]
nearThree
}
class ExtendRULE
{
public static object Values
{
get
{
List<object> list = new List<object>();
foreach( RULE rule in Enum.GetValues( typeof( RULE ) ) )
{
string desc = rule.GetType().GetMember( rule.ToString() )[0].GetCustomAttribute<DescriptionAttribute>().Description;
list.Add( new { value = rule, desc = desc } );
}
return list;
}
}
}
XAML
<StackPanel>
<ListBox ItemsSource= "{Binding Source={x:Static model:ExtendRULE.Values}}" DisplayMemberPath="desc" SelectedValuePath="value" SelectedValue="{Binding SelectedRule}"/>
<ComboBox ItemsSource="{Binding Source={x:Static model:ExtendRULE.Values}}" DisplayMemberPath="desc" SelectedValuePath="value" SelectedValue="{Binding SelectedRule}"/>
</StackPanel>
看到所有的东西,看到某些过于复杂的解决方案如何成为最琐碎问题的“标准(反)模式”是一件痛苦的事情:应该避免实现MarkupExtension的开销和复杂性,特别是使用属性修饰枚举值。简单地实现一个数据模型。
通常,向用户显示枚举值名称是一个坏主意。枚举不应该显示在UI中。它们是在编程上下文中使用的常量。值名称不用于显示。他们是为了称呼工程师,因此这些名字通常使用特殊的语义和词汇,就像科学词汇一样,不被公众所理解。不要犹豫,为显示的值创建一个专用的源。
当涉及到本地化时,问题变得更加明显。
这就是为什么所有贴出来的答案都是过度设计的。它们使一个非常简单的问题看起来像一个关键问题。
最平凡的解决方案就是最好的解决方案,这是事实。最初问题的主题绝对不是个例外。
我强烈建议不要使用任何提供的答案。尽管它们可能会起作用,但它们会给一个微不足道的问题增加不必要的复杂性。
请注意,您总是可以通过调用静态enum将枚举转换为其值或值名的列表。GetValues或Enum。GetNames,它们都返回一个IEnumerable,你可以直接分配给组合框。ItemsSource属性,例如通过数据绑定。
IEnumerable<ExampleEnum> values = Enum.GetValues<ExampleEnum>();
IEnumerable<string> names = Enum.GetNames<ExampleEnum>();
Usually, when defining an enumeration, you don't have UI in mind.
Enumeration value names are not chosen based on UI design rules.
Usually, UI labels and text in general are created by people with no developer or programmer background. They usually provide all the required translations to localize the application.
There are many good reasons not to mix UI with the application.
You would never design a class and name its properties with UI (e.g., DataGrid columns) in mind. You may want your column header to contain whitespaces etc.
Same reason why exception messages are directed at developers and not users. You definitely don't want to decorate every property, every exception, enum or whatever data type or member with attributes in order to provide a display name that makes sense to the user in a particular UI context.
You don't want to have UI design bleed into your code base and polute your classes.
Application and its user interface - this are two different problems.
Adding this abstract or virtual extra layer of separation allows e.g., to add enum values that should not be displayed. Or more general, modify code without having to break or modify the UI.
您应该使用一个简单的IValueConverter或一个专门的类来提供这些显示值作为绑定源,而不是使用属性和实现额外的逻辑负载来提取它们的值(使用反射)。
坚持最常见的模式,并为ComboBox项目实现一个数据模型,其中类具有枚举类型的属性作为成员,这有助于您识别ComboBox。SelectedItem(如果你需要枚举值):
ExampleEnum.cs
// Define enumeration without minding any UI elements and context
public enum ExampleEnum
{
FooBar = 0,
BarFoo
}
ExampleClass.cs
// Define readable enum display values in the UI context.
// Display names can come from a localizable resource.
public class BindingSource : INotifyPropertyChanged
{
public BindingSource()
{
ItemModels = new List<ItemModel>
{
new ItemModel { Label = "Foo Bar Display", Value = ExampleEnum.FooBar },
new ItemModel { Label = "Bar Foo Display", Value = ExampleEnum.BarFoo }
}
}
public List<ItemModel> ItemModels { get; }
private ItemModel selectedItemModel;
public ItemModel SelectedItemModel { get => selectedItemModel; => set and notify; }
}
ItemModel.cs
public class ItemModel
{
public string Label { get; set; }
public ExampleEnum Value { get; set; }
}
MainWindow.xaml
<Window>
<Window.DataContext>
<BindingSource />
</Window.DataContext>
<ComboBox ItemsSource="{Binding ItemModels}"
DisplayMemberName="DisplayValue"
SelectedItem="{Binding SelectedItemModel}" />
</Window>