WPF绑定枚举到ListBox并显示Description属性

47

是否可以使用ObjectDataProvider方法将ListBox绑定到枚举,并以某种方式对其进行样式设置,以显示Description属性?如果可以的话,应该如何操作...


可能是 https://dev59.com/vHM_5IYBdhLWcg3wq1CF 的重复问题。 - Pieter van Ginkel
6个回答

108

可以做到。以下代码可实现。假设我们有一个枚举

public enum MyEnum
{
    [Description("MyEnum1 Description")]
    MyEnum1,
    [Description("MyEnum2 Description")]
    MyEnum2,
    [Description("MyEnum3 Description")]
    MyEnum3
}

然后我们可以使用ObjectDataProvider:

xmlns:MyEnumerations="clr-namespace:MyEnumerations"
<ObjectDataProvider MethodName="GetValues"
                ObjectType="{x:Type sys:Enum}"
                x:Key="MyEnumValues">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="MyEnumerations:MyEnum" />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

对于ListBox,我们将ItemsSource设置为MyEnumValues,并应用一个带有转换器的ItemTemplate。

<ListBox Name="c_myListBox" SelectedIndex="0" Margin="8"
        ItemsSource="{Binding Source={StaticResource MyEnumValues}}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource EnumDescriptionConverter}}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

在转换器中,我们获取描述并返回它。

public class EnumDescriptionConverter : IValueConverter
{
    private string GetEnumDescription(Enum enumObj)
    {
        FieldInfo fieldInfo = enumObj.GetType().GetField(enumObj.ToString());

        object[] attribArray = fieldInfo.GetCustomAttributes(false);

        if (attribArray.Length == 0)
        {
            return enumObj.ToString();
        }
        else
        {
            DescriptionAttribute attrib = attribArray[0] as DescriptionAttribute;
            return attrib.Description;
        }
    }

    object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Enum myEnum = (Enum)value;
        string description = GetEnumDescription(myEnum);
        return description;
    }

    object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return string.Empty;
    }
}

GetEnumDescription方法可能应该放在其他地方,但你可以理解这个想法 :)

查看作为扩展方法的GetEnumDescription


5
喜欢它,就抓住它。我使用了一点Linq来简化GetEnumDescription函数,你可以在这里获取它http://pastebin.com/XLm9hbhG。 - user1228
2
那么你需要为每种枚举类型都制作一个转换器吗? - Carlo
@Carlo:不,这个例子中转换器将值转换为 MyEnum,但如果您想要一个通用的转换器,同样可以使用 Enum - Fredrik Hedblad
8
请注意,您需要将转换器创建为资源的实例,例如:<helper:EnumDescriptionConverter x:Key="HEnumDescriptionConverter" /> - Timm
11
如果枚举属性不同,这段代码就会出错。建议修改代码为 attrib = attribArray.OfType<DescriptionAttribute>().FirstOrDefault(); 并检查是否为null,因为这样更加健壮。 - RichardOD
显示剩余3条评论

8

另一个解决方案是使用自定义MarkupExtension从枚举类型生成项目。这将使xaml更加紧凑和易读。

using System.ComponentModel;

namespace EnumDemo
{
    public enum Numbers
    {
        [Description("1")]
        One,

        [Description("2")]
        Two,

        Three,
    }
}

使用示例:

<Window x:Class="EnumDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:EnumDemo">

    <ListBox ItemsSource="{local:EnumToCollection EnumType={x:Type local:Numbers}}"/>

</Window>

标记扩展实现

using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Markup;

namespace EnumDemo
{
    public class EnumToCollectionExtension : MarkupExtension
    {
        public Type EnumType { get; set; }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (EnumType == null) throw new ArgumentNullException(nameof(EnumType));

            return Enum.GetValues(EnumType).Cast<Enum>().Select(EnumToDescriptionOrString);
        }

        private string EnumToDescriptionOrString(Enum value)
        {
            return value.GetType().GetField(value.ToString())
                       .GetCustomAttributes(typeof(DescriptionAttribute), false)
                       .Cast<DescriptionAttribute>()
                       .FirstOrDefault()?.Description ?? value.ToString();
        }
    }
}

这比最佳答案要简单得多。我只有一个问题。当我绑定到存储枚举的属性时,它会正确更新,但在加载窗口时不会自动显示值。你知道我该如何解决吗? - EatATaco
@EatATaco 你在使用组合框吗?如果是的话,可以看一下这里:https://dev59.com/wWIj5IYBdhLWcg3wPy0a - chviLadislav

3

2
您可以在项目中定义一个资源文件(*.resx 文件)。在这个文件中,您必须定义“键值对”,就像这样:
"YellowCars" : "Yellow Cars",
"RedCars" : "Red Cars",

密钥等于您的枚举条目,类似于以下内容:

and so on...

public enum CarColors
{
    YellowCars,
    RedCars
}

当你使用WPF时,你可以在XAML代码中实现类似以下内容:

<ComboBox ItemsSource="{Binding Source={StaticResource CarColors}}" SelectedValue="{Binding CarColor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource CarColorConverter}}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

那么你需要编写一个转换器,类似于这样:
using System;
using System.Globalization;
using System.Resources;
using System.Windows.Data;

public class CarColorConverter : IValueConverter
{
    private static ResourceManager CarColors = new ResourceManager(typeof(Properties.CarColors));

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var key = ((Enum)value).ToString();
        var result = CarColors.GetString(key);
        if (result == null) {
            result = key;
        }

        return result;
    }

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

我的回答晚了7年;-)但也许对其他人有用!

该内容与it技术无关。

0

没问题,可以实现。

ListBox 可以帮助我们实现这个功能,无需转换器。

具体步骤如下:
创建一个 ListBox,并将其 ItemsSource 设置为枚举类型,将 ListBox 的 SelectedItem 绑定到所选属性。

然后每个 ListBoxItem 将被创建。

  • 步骤 1:定义您的枚举。
public enum EnumValueNames
{ 
   EnumValueName1, 
   EnumValueName2, 
   EnumValueName3
}

然后在您的DataContext(或MVVM的ViewModel)中添加以下属性,记录选中的已勾选项。

public EnumValueNames SelectedEnumValueName { get; set; }
  • 第二步:将此枚举类型添加到您的窗口、用户控件或网格等静态资源中。
    <Window.Resources>
        <ObjectDataProvider MethodName="GetValues"
                            ObjectType="{x:Type system:Enum}"
                            x:Key="EnumValueNames">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:EnumValueNames" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
  • 步骤三: 使用列表框填充每个项目
<ListBox ItemsSource="{Binding Source={StaticResource EnumValueNames}}"
              SelectedItem="{Binding SelectedEnumValueName, Mode=TwoWay}" />

References: https://www.codeproject.com/Articles/130137/Binding-TextBlock-ListBox-RadioButtons-to-Enums


0

这个例子是应用于ComboBox,但同样适用于任何枚举绑定。

起源:

这个答案基于Brian Lagunas' EnumBindingSourceExtension + EnumDescriptionTypeConverter的原始工作。 我对它进行了修改以更好地满足我的需求。

我做了什么改变:

  1. 扩展Enum类,增加一个布尔型函数,检查EnumValue是否具有[Description]属性

    public static bool HasDescriptionAttribute(this Enum value)
    {
        var attribute = value.GetType().GetField(value.ToString())
                            .GetCustomAttributes(typeof(DescriptionAttribute), false)
                            .FirstOrDefault();                                
    
        return (attribute != null);
    }
    
  2. 修改了"EnumDescriptionTypeConverter"中Brian的"ConvertTo()"函数,如果没有应用[Description]属性,则返回"null"

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        ...
        // 原代码:return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : value.ToString();
        return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : null;
    }
    
  3. 通过编辑"EnumBindingSourceExtension"中的"ProvideValue()"函数的返回值来修改Brian的函数,并在其中调用自己的函数

    ProvideValue(IServiceProvider serviceProvider)
    {
        ...
        // 原代码:return enumValues
        return SortEnumValuesByIndex(enumValues);
    
        ...
        // 原代码:return tempArray
        return SortEnumValuesByIndex(tempArray);
    }
    

    添加我的函数以按索引排序Enum(原始代码在跨项目时存在问题),并剥离没有[Description]属性的任何值:

    private object SortEnumValuesByIndex(Array enumValues)
    {
        var values = enumValues.Cast<Enum>().ToList();
        var indexed = new Dictionary<int, Enum>();
        foreach (var value in values)
        {
            int index = (int)Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()));
            indexed.Add(index, value);
        }
    
        return indexed.OrderBy(x => x.Key).Select(x => x.Value).Where(x => x.HasDescriptionAttribute()).Cast<Enum>();
    }     
    

此示例已应用于ComboBoxes:

注意:上传图像到服务器失败,因此我添加了有关图像的URL

<ComboBox x:Name="ConversionPreset_ComboBox" Grid.Row="4" Grid.Column="1" Margin="5,5,5,5" ItemsSource="{objects:EnumBindingSource EnumType={x:Type enums:ConversionPreset}}" SelectedIndex="2" SelectionChanged="ConversionPreset_ComboBox_SelectionChanged" />
<ComboBox x:Name="OutputType_ComboBox" Grid.Row="4" Grid.Column="2" Margin="5,5,5,5" ItemsSource="{objects:EnumBindingSource EnumType={x:Type enums:Output}}" SelectedIndex="1" SelectionChanged="OutputType_ComboBox_SelectionChanged" />

使用代碼後端:

    private Enumeration.Output Output { get; set; }

    private void OutputType_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        => Output = (Enumeration.Output)OutputType_ComboBox.SelectedItem;

    private Enumeration.ConversionPreset ConversionPreset { get; set; }

    private void ConversionPreset_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        => ConversionPreset = (Enumeration.ConversionPreset)ConversionPreset_ComboBox.SelectedItem;

"ConversionPreset" 枚举和 ComboBox 渲染的图片

[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum ConversionPreset
{
    [Description("Very Slow (Smaller File Size)")]
    VerySlow = -2,

    [Description("Slow (Smaller File Size)")]
    Slow = -1,

    [Description("Medium (Balanced File Size)")]
    Medium = 0,

    [Description("Fast (Bigger File Size)")]
    Fast = 1,

    [Description("Very Fast (Bigger File Size)")]
    VeryFast = 2,

    [Description("Ultra Fast (Biggest File Size)")]
    UltraFast = 3
}

"Output" 枚举和 ComboBox 渲染的图片

[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum Output
{
    // This will be hidden in the Output
    None = -1,

    [Description("Video")]
    Video = 0,

    [Description("Audio")]
    Audio = 1
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接