使用MarkupExtension将ComboBox绑定到枚举值的集合

时间:2016-04-23 20:34:16

标签: c# .net wpf xaml data-binding

我正在尝试使用此答案中的MarkupExtension将WPF ComboBox绑定到枚举值的集合https://stackoverflow.com/a/4398752/964478

它适用于{Binding Source={my:Enumeration {x:Type my:Status}}},但我想使用枚举值的集合而不是所有值。

    public IList<Status> Statuses
    {
        get
        {
            return new[]
            {
                Status.Available,
                Status.Away
            };
        }
    }

我为那个

添加了一个构造函数
  public class EnumerationExtension : MarkupExtension
  {
       private Type _enumType;

        private Enum[] _enumValues;

        public EnumerationExtension(IEnumerable<Enum> enumValues)
        {
            _enumValues = enumValues.ToArray();

            EnumType = _enumValues.First().GetType();
        }

        //public EnumerationExtension(Type enumType)
        //{
        //    if (enumType == null)
        //        throw new ArgumentNullException(nameof(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 = _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; }
        }
    }

但是当我尝试这样的事情时

ItemsSource="{Binding Source={my:Enumeration Statuses}}" 

我在NullReferenceException

中的InitializeComponent处获得了 at MS.Internal.Xaml.Runtime.ClrObjectRuntime.GetConverterInstance[TConverterBase](XamlValueConverter`1 converter) at MS.Internal.Xaml.Runtime.ClrObjectRuntime.CreateObjectWithTypeConverter(ServiceProviderContext serviceContext, XamlValueConverter`1 ts, Object value) at MS.Internal.Xaml.Runtime.ClrObjectRuntime.CreateFromValue(ServiceProviderContext serviceContext, XamlValueConverter`1 ts, Object value, XamlMember property) at MS.Internal.Xaml.Runtime.PartialTrustTolerantRuntime.CreateFromValue(ServiceProviderContext serviceContext, XamlValueConverter`1 ts, Object value, XamlMember property) at System.Xaml.XamlObjectWriter.Logic_CreateFromValue(ObjectWriterContext ctx, XamlValueConverter`1 typeConverter, Object value, XamlMember property, String targetName, IAddLineInfo lineInfo) at System.Xaml.XamlObjectWriter.Logic_ConvertPositionalParamsToArgs(ObjectWriterContext ctx) at System.Xaml.XamlObjectWriter.WriteEndMember() at System.Xaml.XamlWriter.WriteNode(XamlReader reader) at System.Windows.Markup.WpfXamlLoader.TransformNodes(XamlReader xamlReader, XamlObjectWriter xamlWriter, Boolean onlyLoadOneNode, Boolean skipJournaledProperties, Boolean shouldPassLineNumberInfo, IXamlLineInfo xamlLineInfo, IXamlLineInfoConsumer xamlLineInfoConsumer, XamlContextStack`1 stack, IStyleConnector styleConnector) at System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri) at System.Windows.Markup.WpfXamlLoader.LoadBaml(XamlReader xamlReader, Boolean skipJournaledProperties, Object rootObject, XamlAccessLevel accessLevel, Uri baseUri) at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream) at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator) at MvvmLight3.MainWindow.InitializeComponent() in \MainWindow.xaml:line 1 at MvvmLight3.MainWindow..ctor() in \MainWindow.xaml.cs:line 16
{{1}}

2 个答案:

答案 0 :(得分:1)

ItemsSource="{Binding Source={my:Enumeration Statuses}}" 不在此处:

my:Enumeration

Enumeration不是绑定。应该为ProvideValue的构造函数提供什么值?你无法在那里放置绑定:请注意,在加载XAML时会抛出异常。你的viewmodel还没有出现一段时间。我猜你可以在Binding中以编程方式创建一个绑定,但是......这会增加什么?您已经有一个标记扩展来创建绑定:它被称为MarkupExtension

在这种情况下,我实际上甚至不理解为什么你需要ItemsSource="{Binding Statuses}" :你不需要它来枚举任何东西,因为你处理它的是一个枚举。

我可能会遗漏一些东西,但这不会做你想要的吗?

MarkupExtension

enum枚举Enum.GetValues(typeof(MyEnumType)).Cast<MyEnumType>();的优点是你只需给它一个类型,它可以省去你自己创建列表的麻烦,或者为返回{{1}}的属性编写代码。但是你已经自己制作了这份清单。

答案 1 :(得分:1)

您可以创建一个获取枚举值并返回其描述的转换器,而不是使用MarkupExtension。然后在绑定时使用此转换器。

public class EnumToDescriptionConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null)
        {
            return String.Empty;
        }

        Type type = value.GetType();

        return
            type.IsEnum
                ? GetDescription(type, value)
                : String.Empty;
    }

    private string GetDescription(Type enumType, object enumValue)
    {
        var descriptionAttribute =
            enumType
                .GetField(enumValue.ToString())
                .GetCustomAttributes(typeof(DescriptionAttribute), false)
                .FirstOrDefault() as DescriptionAttribute;

        return
            descriptionAttribute != null
                ? descriptionAttribute.Description
                : enumValue.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

构建项目后,当您的命名空间作为xml命名空间添加时,您可以在XAML中使用它:

<Window [...] xmlns:local="clr-namespace:YourNameSpace">

将此转换器添加到窗口(或应用程序)资源:

<Window.Resources>
    <local:EnumToDescriptionConverter x:Key="EnumToDescriptionConverter" />
</Window.Resources>

然后,您可以在将Statuses绑定到ListBox时使用它。

<ListBox ItemsSource="{Binding Statuses}" SelectedItem="{Binding CurrentStatus}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource EnumToDescriptionConverter}}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

CurrentStatusStatuses是viewmodel的属性:

public class MainViewModel
{
    public Status CurrentStatus { get; set; }

    public IEnumerable<Status> Statuses
    {
        get
        {
            return new Status[]
            {
                Status.Available,
                Status.Away
            };
        }
    }
}