以下面的代码为例:
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}}"/>
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>
这是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,或任何其他您选择的类,只要它可以保存枚举值和该枚举值的字符串描述。