WPF 将列表框绑定到枚举,显示描述属性

发布于 2024-09-28 14:06:53 字数 95 浏览 3 评论 0 原文

是否可以使用 ObjectDataProvider 方法将 ListBox 绑定到枚举,并以某种方式设置其样式以显示“描述”属性?如果是这样,人们将如何去做这件事......?

Is it possible to use the ObjectDataProvider method to bind a ListBox to an enum, and style it somehow to display the Description attriibute? If so how would one go about doing this...?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

温柔女人霸气范 2024-10-05 14:06:53

是的,这是可能的。这样就可以了。假设我们有枚举

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 作为扩展方法

Yes, it is possible. This will do it. Say we have the enum

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

Then we can use the ObjectDataProvider as

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>

And for the ListBox we set the ItemsSource to MyEnumValues and apply an ItemTemplate with a Converter.

<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>

And in the converter we get the description and return it

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;
    }
}

The GetEnumDescription method should probably go somewhere else but you get the idea :)

Check GetEnumDescription as extension method.

失而复得 2024-10-05 14:06:53

另一个解决方案是自定义 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>

MarkupExtension 实现

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();
        }
    }
}

Another solution would be a custom MarkupExtension that generates the items from enum type. This makes the xaml more compact and readable.

using System.ComponentModel;

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

        [Description("2")]
        Two,

        Three,
    }
}

Example of usage:

<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>

MarkupExtension implementation

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();
        }
    }
}
寄人书 2024-10-05 14:06:53

如果您绑定到枚举,您可能可以通过 IValueConverter 将其转换为描述。

有关如何操作的说明,请参阅将组合框绑定到枚举...在 Silverlight 中!来实现这一目标。

请参阅 http://msdn.microsoft.com/en -us/library/system.windows.data.ivalueconverter.aspx 了解更多信息。

If you bind to the Enum, you could probably convert this to the description through an IValueConverter.

See Binding ComboBoxes to enums... in Silverlight! for a description on how to accomplish this.

See http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx for more information.

眼前雾蒙蒙 2024-10-05 14:06:53

您可以在项目中定义资源文件(*.resx 文件)。在此文件中,您必须定义“键值对”,如下所示:

"YellowCars" : "Yellow Cars",
"RedCars" : "Red Cars",

等等...

键等于您的枚举条目,如下所示:

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 年;-) 但也许它可以被其他人使用!

You can define a ressource file in your project (*.resx file). In this file you must define "key-value-pairs", something like this:

"YellowCars" : "Yellow Cars",
"RedCars" : "Red Cars",

and so on...

The keys are equals to your enum-entries, something like this:

public enum CarColors
{
    YellowCars,
    RedCars
}

and so on...

When you use WPF you can implement in your XAML-Code, something like this:

<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>

Then you must write your Converter, something like this:

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();
    }
}

My answer comes 7 years to late ;-) But maybe it can be used by someone else!

深海夜未眠 2024-10-05 14:06:53

是的,有可能。

ListBox 可以帮助我们做到这一点,无需转换器。

该方法的步骤如下:
创建一个 ListBox 并将列表框的 ItemsSource 设置为枚举,并将 ListBox 的 SelectedItem 绑定到选定的属性。

然后将创建每个ListBoxItem。

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

然后将以下属性添加到您的DataContext(或MVVM的ViewModel)中,该属性记录了选中的所选项目。

public EnumValueNames SelectedEnumValueName { get; set; }
  • 第 2 步:将枚举添加到窗口、用户控件或网格等的静态资源。
    <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>
  • 第 3 步:使用列表框填充每个项目
<ListBox ItemsSource="{Binding Source={StaticResource EnumValueNames}}"
              SelectedItem="{Binding SelectedEnumValueName, Mode=TwoWay}" />

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

Yeah, possible.

ListBox can help us do that, without converters.

The steps of this method are below:
create a ListBox and set the ItemsSource for the listbox as the enum and binding the SelectedItem of the ListBox to the selected property.

Then each ListBoxItem will be created.

  • Step 1: define your Enum.
public enum EnumValueNames
{ 
   EnumValueName1, 
   EnumValueName2, 
   EnumValueName3
}

Then add below property to your DataContext (or ViewModel of MVVM), which records the selected item which is checked.

public EnumValueNames SelectedEnumValueName { get; set; }
  • Step 2: add the enum to static resources for your Window, UserControl or Grid etc.
    <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>
  • Step 3: Use the List Box to populate each item
<ListBox ItemsSource="{Binding Source={StaticResource EnumValueNames}}"
              SelectedItem="{Binding SelectedEnumValueName, Mode=TwoWay}" />

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

风铃鹿 2024-10-05 14:06:53

这里的示例应用于 ComboBox,但对于任何枚举绑定都同样有效。

来源:

此 anwser 基于 Brian Lagunas 的 EnumBindingSourceExtension + EnumDescriptionTypeConverter
我对其进行了修改以更好地满足我的需要。

我更改的内容:

  1. 使用布尔函数扩展了 Enum 类,该函数检查 EnumValue 是否具有 [Description] 属性

    public static bool HasDescriptionAttribute(此枚举值)
    {
        var 属性 = value.GetType().GetField(value.ToString())
                            .GetCustomAttributes(typeof(DescriptionAttribute), false)
                            .FirstOrDefault();                                
    
        返回(属性!= null);
    }
    
  2. 修改了 Brian 在“EnumDescriptionTypeConverter”中的“ConvertTo()”函数,以在 [Description] 属性不存在时返回“null”已应用

    公共覆盖对象 ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfoculture, object value, Type destinationType)
    {
        ...
        // 原文: return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ?属性[0].描述:value.ToString();
        return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ?属性[0].描述:空;
    }
    
  3. 通过调用 my 来编辑其返回值,修改了“EnumBindingSourceExtension”中的 Brian 的“ProvideValue()”函数自己的功能

    ProvideValue(IServiceProvider 服务提供商)
    {
        ...
        // 原始:返回 enumValues
        返回 SortEnumValuesByIndex(enumValues);
    
        ...
        // 原文:返回 tempArray
        返回 SortEnumValuesByIndex(tempArray);
    }
    

    并添加我的函数以按索引对枚举进行排序(原始代码在跨项目时出现问题..),并删除任何没有 [Description] 属性的值:

    私有对象 SortEnumValuesByIndex(Array enumValues)
    {
        var 值 = enumValues.Cast().ToList();
        var indexed = new Dictionary();
        foreach(值中的变量值)
        {
            int index = (int)Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()));
            索引。添加(索引,值);
        }
    
        return indexed.OrderBy(x => x.Key).Select(x => x.Value).Where(x => x.HasDescriptionAttribute()).Cast();
    }     
    

此示例已应用于 ComboBox:

注意:将图像上传到服务器失败,因此我向相关图像添加了 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
}

输出”枚举和 渲染的 ComboBox 图片

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

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

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

The example here is applied to a ComboBox, but will work all the same for any Enum Binding.

Origin:

This anwser is based on the original work of Brian Lagunas' EnumBindingSourceExtension + EnumDescriptionTypeConverter.
I have made modifications for it to better suit my needs.

What I changed:

  1. Extended the Enum class with a boolean function that checks if the EnumValue has the [Description] attribute or not

    public static bool HasDescriptionAttribute(this Enum value)
    {
        var attribute = value.GetType().GetField(value.ToString())
                            .GetCustomAttributes(typeof(DescriptionAttribute), false)
                            .FirstOrDefault();                                
    
        return (attribute != null);
    }
    
  2. Modified Brian's "ConvertTo()" function in "EnumDescriptionTypeConverter" to return "null" in case the [Description] attribute was not applied

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        ...
        // Original: 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. Modified Brian's "ProvideValue()" function in "EnumBindingSourceExtension" by editing it's return value by calling my own function

    ProvideValue(IServiceProvider serviceProvider)
    {
        ...
        // Original: return enumValues
        return SortEnumValuesByIndex(enumValues);
    
        ...
        // Original: return tempArray
        return SortEnumValuesByIndex(tempArray);
    }
    

    And adding my function to Sort the enum by Index (Original code had problems when going across Projects ..), and Strip out any Values that don't have the [Description] attribute:

    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>();
    }     
    

This example has been applied to ComboBoxes:

Note: Failures in Uploading images to the Server, so i added a URL to the images in question

<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" />

With code behind:

    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;

The "ConversionPreset" Enum and a Picture of the ComboBox Rendered

[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
}

The "Output" Enum and a Picture of the ComboBox Rendered

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

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

    [Description("Audio")]
    Audio = 1
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文