我试图找到一个简单的例子,枚举显示为是。我所看到的所有示例都试图添加好看的显示字符串,但我不想要那种复杂性。
基本上,我有一个类,通过首先将DataContext设置为这个类,然后在xaml文件中指定绑定,保存我绑定的所有属性:
<ComboBox ItemsSource="{Binding Path=EffectStyle}"/>
但这不会在组合框中显示枚举值作为项。
我试图找到一个简单的例子,枚举显示为是。我所看到的所有示例都试图添加好看的显示字符串,但我不想要那种复杂性。
基本上,我有一个类,通过首先将DataContext设置为这个类,然后在xaml文件中指定绑定,保存我绑定的所有属性:
<ComboBox ItemsSource="{Binding Path=EffectStyle}"/>
但这不会在组合框中显示枚举值作为项。
当前回答
我喜欢在ViewModel中绑定定义的所有对象,所以我尽量避免在xaml中使用<ObjectDataProvider>。
我的解决方案不使用视图中定义的数据,也不使用隐藏代码。只有一个DataBinding,一个可重用的ValueConverter,一个用于获取任意Enum类型的描述集合的方法,以及ViewModel中要绑定的单个属性。
当我想将一个Enum绑定到一个组合框时,我想要显示的文本永远不匹配Enum的值,所以我使用[Description()]属性(from System.ComponentModel)给它我实际上想在组合框中看到的文本。如果我有一周中的一天,它看起来会像这样:
public enum DayOfWeek
{
// add an optional blank value for default/no selection
[Description("")]
NOT_SET = 0,
[Description("Sunday")]
SUNDAY,
[Description("Monday")]
MONDAY,
...
}
首先,我创建了一个helper类,其中包含几个处理枚举的方法。一个方法获取一个特定值的描述,另一个方法获取一个类型的所有值及其描述。
public static class EnumHelper
{
public static string Description(this Enum value)
{
var attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Any())
return (attributes.First() as DescriptionAttribute).Description;
// If no description is found, the least we can do is replace underscores with spaces
// You can add your own custom default formatting logic here
TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " ")));
}
public static IEnumerable<ValueDescription> GetAllValuesAndDescriptions(Type t)
{
if (!t.IsEnum)
throw new ArgumentException($"{nameof(t)} must be an enum type");
return Enum.GetValues(t).Cast<Enum>().Select((e) => new ValueDescription() { Value = e, Description = e.Description() }).ToList();
}
}
接下来,我们创建一个ValueConverter。从MarkupExtension继承使它更容易在XAML中使用,因此我们不必将其声明为资源。
[ValueConversion(typeof(Enum), typeof(IEnumerable<ValueDescription>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return EnumHelper.GetAllValuesAndDescriptions(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
我的ViewModel只需要1个属性,我的视图可以绑定到组合框的SelectedValue和ItemsSource:
private DayOfWeek dayOfWeek;
public DayOfWeek SelectedDay
{
get { return dayOfWeek; }
set
{
if (dayOfWeek != value)
{
dayOfWeek = value;
OnPropertyChanged(nameof(SelectedDay));
}
}
}
最后绑定ComboBox视图(在ItemsSource绑定中使用ValueConverter)…
<ComboBox ItemsSource="{Binding Path=SelectedDay, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path=SelectedDay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
要实现这个解决方案,你只需要复制我的EnumHelper类和EnumToCollectionConverter类。它们可以处理任何枚举。同样,我没有在这里包括它,但ValueDescription类只是一个简单的类,具有两个公共对象属性,一个叫Value,一个叫Description。您可以自己创建,或者您可以更改代码使用Tuple<object, object>或KeyValuePair<object, object>
对于那些想要查看ValueDescription类的人:
public class ValueDescription
{
public object Value {get; set};
public object Description {get; set};
}
其他回答
上面所有的帖子都忽略了一个简单的技巧。可以从SelectedValue的绑定中找到如何自动地填充ItemsSource,以便您的XAML标记是正确的。
<Controls:EnumComboBox SelectedValue="{Binding Fool}"/>
例如,在ViewModel中
public enum FoolEnum
{
AAA, BBB, CCC, DDD
};
FoolEnum _Fool;
public FoolEnum Fool
{
get { return _Fool; }
set { ValidateRaiseAndSetIfChanged(ref _Fool, value); }
}
ValidateRaiseAndSetIfChanged是我的INPC钩子。你的可能不同。
EnumComboBox的实现如下所示,但首先我需要一个小助手来获取我的枚举字符串和值
public static List<Tuple<object, string, int>> EnumToList(Type t)
{
return Enum
.GetValues(t)
.Cast<object>()
.Select(x=>Tuple.Create(x, x.ToString(), (int)x))
.ToList();
}
和主类(注意,我使用ReactiveUI通过WhenAny钩子属性更改)
using ReactiveUI;
using ReactiveUI.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Documents;
namespace My.Controls
{
public class EnumComboBox : System.Windows.Controls.ComboBox
{
static EnumComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(EnumComboBox), new FrameworkPropertyMetadata(typeof(EnumComboBox)));
}
protected override void OnInitialized( EventArgs e )
{
base.OnInitialized(e);
this.WhenAnyValue(p => p.SelectedValue)
.Where(p => p != null)
.Select(o => o.GetType())
.Where(t => t.IsEnum)
.DistinctUntilChanged()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(FillItems);
}
private void FillItems(Type enumType)
{
List<KeyValuePair<object, string>> values = new List<KeyValuePair<object,string>>();
foreach (var idx in EnumUtils.EnumToList(enumType))
{
values.Add(new KeyValuePair<object, string>(idx.Item1, idx.Item2));
}
this.ItemsSource = values.Select(o=>o.Key.ToString()).ToList();
UpdateLayout();
this.ItemsSource = values;
this.DisplayMemberPath = "Value";
this.SelectedValuePath = "Key";
}
}
}
您还需要在Generic中正确设置样式。XAML或者你的盒子不会渲染任何东西,你会抓狂的。
<Style TargetType="{x:Type local:EnumComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
</Style>
就是这样。这显然可以扩展到支持i18n,但会使帖子更长。
<Window.Resources>
<ObjectDataProvider x:Key="DiaryTypeEnum"
MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="z:Enums+DiaryType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
...
<ComboBox ItemsSource="{Binding Source={StaticResource DiaryTypeEnum}}" SelectedItem="{x:Static z:Enums+DiaryType.Defect}" />
其中z它的xmlns:z="clr-namespace:ProjName。助手”
我的Enum变成静态类
public static class Enums
{
public enum DiaryType
{
State,
Defect,
Service,
Other
}
public enum OtherEnumOrMethods
{
//TODO
}
}
使用ReactiveUI,我创建了以下替代解决方案。这不是一个优雅的一体化解决方案,但我认为至少它是可读的。
在我的例子中,将枚举列表绑定到控件是一种罕见的情况,所以我不需要跨代码库扩展解决方案。但是,可以通过更改EffectStyleLookup使代码更加通用。项目转化为对象。我用我的代码测试了,没有其他修改是必要的。这意味着一个helper类可以应用于任何枚举列表。虽然这会降低它的可读性- ReactiveList<EnumLookupHelper>并没有一个很好的环。
使用以下helper类:
public class EffectStyleLookup
{
public EffectStyle Item { get; set; }
public string Display { get; set; }
}
在ViewModel中,转换枚举列表并将其作为属性公开:
public ViewModel : ReactiveObject
{
private ReactiveList<EffectStyleLookup> _effectStyles;
public ReactiveList<EffectStyleLookup> EffectStyles
{
get { return _effectStyles; }
set { this.RaiseAndSetIfChanged(ref _effectStyles, value); }
}
// See below for more on this
private EffectStyle _selectedEffectStyle;
public EffectStyle SelectedEffectStyle
{
get { return _selectedEffectStyle; }
set { this.RaiseAndSetIfChanged(ref _selectedEffectStyle, value); }
}
public ViewModel()
{
// Convert a list of enums into a ReactiveList
var list = (IList<EffectStyle>)Enum.GetValues(typeof(EffectStyle))
.Select( x => new EffectStyleLookup() {
Item = x,
Display = x.ToString()
});
EffectStyles = new ReactiveList<EffectStyle>( list );
}
}
在组合框中,使用SelectedValuePath属性,绑定到原始enum值:
<ComboBox Name="EffectStyle" DisplayMemberPath="Display" SelectedValuePath="Item" />
在视图中,这允许我们将原始enum绑定到ViewModel中的SelectedEffectStyle,但在组合框中显示ToString()值:
this.WhenActivated( d =>
{
d( this.OneWayBind(ViewModel, vm => vm.EffectStyles, v => v.EffectStyle.ItemsSource) );
d( this.Bind(ViewModel, vm => vm.SelectedEffectStyle, v => v.EffectStyle.SelectedValue) );
});
以下是我的简短回答。
public enum Direction { Left, Right, Up, Down };
public class Program
{
public Direction ScrollingDirection { get; set; }
public List<string> Directions { get; } = new List<string>();
public Program()
{
loadListDirection();
}
private void loadListDirection()
{
Directions.AddRange(Enum.GetNames(typeof(Direction)));
}
}
和Xaml:
<ComboBox SelectedIndex="0" ItemsSource="{Binding Path=Directions, Mode=OneWay}" SelectedItem="{Binding Path=ScrollingDirection, Mode=TwoWay}"/>
好运!
Nick的解决方案可以更简化,没有什么花哨的,你只需要一个转换器:
[ValueConversion(typeof(Enum), typeof(IEnumerable<Enum>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var r = Enum.GetValues(value.GetType());
return r;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
然后在你想要组合框出现的地方使用这个:
<ComboBox ItemsSource="{Binding PagePosition, Converter={converter:EnumToCollectionConverter}, Mode=OneTime}" SelectedItem="{Binding PagePosition}" />