【问题标题】:Binding ComboBox to collection of enum values using MarkupExtension使用 MarkupExtension 将 ComboBox 绑定到枚举值的集合
【发布时间】:2016-04-23 20:34:16
【问题描述】:

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

它适用于{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}}" 

我在InitializeComponent 某处得到NullReferenceException

   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

【问题讨论】:

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


    【解决方案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 是视图模型的属性:

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

    【讨论】:

    • 是的,我知道这种方法,我就是这样做的。我尝试使用MarkupExtension ,因为它看起来比 Converter + ItemTemplate 更干净。但显然它不适合这项任务。
    【解决方案2】:

    Statuses 在这里什么都不是:

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

    my:Enumeration 不是绑定。什么应该为Enumeration 的构造函数提供值?无论如何,您不能在那里放置绑定:请注意,在加载 XAML 时会引发异常。您的视图模型将在一段时间内不存在。我猜你可以在ProvideValue 中以编程方式创建一个绑定,但是......这会增加什么?您已经有一个创建绑定的标记扩展:它称为Binding

    我什至不明白为什么在这种情况下你需要MarkupExtension:你不需要它来枚举任何东西,因为你正在处理它已经是一个枚举。

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

    ItemsSource="{Binding Statuses}" 
    

    枚举枚举MarkupExtension 的优点是您只需给它一个类型,这样就省去了自己制作列表或为返回Enum.GetValues(typeof(MyEnumType)).Cast&lt;MyEnumType&gt;(); 的属性编写代码的麻烦。但你已经在自己列出清单了。

    【讨论】:

    • 是的,看来我误解了MarkupExtension 的工作原理。:) 我尝试使用它,因为它看起来比Converter + ItemTemplate 更干净
    • @Alex11223 不知何故,我错过了描述属性转换器的要求。但 Gabor 有。
    猜你喜欢
    • 1970-01-01
    • 2011-07-03
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 2014-01-26
    • 2016-09-15
    • 2015-07-03
    相关资源
    最近更新 更多