WPF 将 ComboBox 绑定到枚举(稍加修改)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/916278/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 20:29:00  来源:igfitidea点击:

WPF binding ComboBox to enum (with a twist)

wpfdata-bindingenumscombobox

提问by Carlo

Well the problem is that I have this enum, BUT I don't want the combobox to show the values of the enum. This is the enum:

那么问题是我有这个枚举,但我不希望组合框显示枚举的值。这是枚举:

public enum Mode
    {
        [Description("Display active only")]
        Active,
        [Description("Display selected only")]
        Selected,
        [Description("Display active and selected")]
        ActiveAndSelected
    }

So in the ComboBox instead of displaying Active, Selected or ActiveAndSelected, I want to display the DescriptionProperty for each value of the enum. I do have an extension method called GetDescription() for the enum:

因此,在 ComboBox 中,我想显示枚举的每个值的 DescriptionProperty,而不是显示 Active、Selected 或 ActiveAndSelected。我确实有一个名为 GetDescription() 的扩展方法用于枚举:

public static string GetDescription(this 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;
            }
        }

So is there a way I can bind the enum to the ComboBox AND show it's content with the GetDescription extension method?

那么有没有一种方法可以将枚举绑定到 ComboBox 并使用 GetDescription 扩展方法显示它的内容?

Thanks!

谢谢!

采纳答案by Robert Harvey

I like the way you think. But GetCustomAttributesuses reflection. What is that going to do to your performance?

我喜欢你思考的方式。但GetCustomAttributes使用reflection. 这对你的表现有什么影响?

Check out this post: WPF - Displaying enums in ComboBox control http://www.infosysblogs.com/microsoft/2008/09/wpf_displaying_enums_in_combob.html

查看这篇文章:WPF - 在 ComboBox 控件中显示枚举 http://www.infosysblogs.com/microsoft/2008/09/wpf_displaying_enums_in_combob.html

回答by Joe White

I would suggest a DataTemplate and a ValueConverter. That will let you customize the way it's displayed, but you would still be able to read the combobox's SelectedItem property and get the actual enum value.

我建议使用 DataTemplate 和 ValueConverter。这将让您自定义它的显示方式,但您仍然可以读取组合框的 SelectedItem 属性并获取实际的枚举值。

ValueConverters require a lot of boilerplate code, but there's nothing too complicated here. First you create the ValueConverter class:

ValueConverters 需要大量样板代码,但这里没有什么太复杂的。首先创建 ValueConverter 类:

public class ModeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter,
        CultureInfo culture)
    {
        return ((Mode) value).GetDescription();
    }
    public object ConvertBack(object value, Type targetType, object parameter,
        CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

Since you're only converting enum values to strings (for display), you don't need ConvertBack -- that's just for two-way binding scenarios.

由于您只是将枚举值转换为字符串(用于显示),因此您不需要 ConvertBack —— 这仅适用于双向绑定场景。

Then you put an instance of the ValueConverter into your resources, with something like this:

然后将 ValueConverter 的一个实例放入资源中,如下所示:

<Window ... xmlns:WpfApplication1="clr-namespace:WpfApplication1">
    <Window.Resources>
        <WpfApplication1:ModeConverter x:Key="modeConverter"/>
    </Window.Resources>
    ....
</Window>

Then you're ready to give the ComboBox a DisplayTemplate that formats its items using the ModeConverter:

然后,您就可以为 ComboBox 提供一个 DisplayTemplate,该模板使用 ModeConverter 设置其项目的格式:

<ComboBox Name="comboBox" ...>
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource modeConverter}}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

To test this, I threw in a Label too, that would show me the actual SelectedItem value, and it did indeed show that SelectedItem is the enum instead of the display text, which is what I would want:

为了测试这一点,我也加入了一个标签,它会显示实际的 SelectedItem 值,它确实表明 SelectedItem 是枚举而不是显示文本,这正是我想要的:

<Label Content="{Binding ElementName=comboBox, Path=SelectedItem}"/>

回答by Mike Rowley

This is how I am doing it with MVVM. On my model I would have defined my enum:

这就是我使用 MVVM 的方式。在我的模型上,我会定义我的枚举:

    public enum VelocityUnitOfMeasure
    {
        [Description("Miles per Hour")]
        MilesPerHour,
        [Description("Kilometers per Hour")]
        KilometersPerHour
    }

On my ViewModel I expose a property that provides possible selections as string as well as a property to get/set the model's value. This is useful if we don't want to use every enum value in the type:

在我的 ViewModel 上,我公开了一个属性,该属性提供了可能的选择作为字符串以及一个用于获取/设置模型值的属性。如果我们不想使用类型中的每个枚举值,这很有用:

    //UI Helper
    public IEnumerable<string> VelocityUnitOfMeasureSelections
    {
        get
        {
            var units = new []
                            {
                               VelocityUnitOfMeasure.MilesPerHour.Description(),
                               VelocityUnitOfMeasure.KilometersPerHour.Description()
                            };
            return units;
        }
    }

    //VM property
    public VelocityUnitOfMeasure UnitOfMeasure
    {
        get { return model.UnitOfMeasure; }
        set { model.UnitOfMeasure = value; }
    }

Furthermore, I use a generic EnumDescriptionCoverter:

此外,我使用通用 EnumDescriptionCoverter:

public class EnumDescriptionConverter : IValueConverter
{
    //From Binding Source
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is Enum)) throw new ArgumentException("Value is not an Enum");
        return (value as Enum).Description();
    }

    //From Binding Target
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is string)) throw new ArgumentException("Value is not a string");
        foreach(var item in Enum.GetValues(targetType))
        {
            var asString = (item as Enum).Description();
            if (asString == (string) value)
            {
                return item;
            }
        }
        throw new ArgumentException("Unable to match string to Enum description");
    }
}

And finally, with the view I can do the following:

最后,有了视图,我可以执行以下操作:

<Window.Resources>
    <ValueConverters:EnumDescriptionConverter x:Key="enumDescriptionConverter" />
</Window.Resources>
...
<ComboBox SelectedItem="{Binding UnitOfMeasure, Converter={StaticResource enumDescriptionConverter}}"
          ItemsSource="{Binding VelocityUnitOfMeasureSelections, Mode=OneWay}" />

回答by Kent Boogaart

Questions of using reflection and attributes aside, there are a few ways you could do this, but I think the best way is to just create a little view model class that wraps the enumeration value:

除了使用反射和属性的问题,有几种方法可以做到这一点,但我认为最好的方法是创建一个包含枚举值的小视图模型类:

public class ModeViewModel : ViewModel
{
    private readonly Mode _mode;

    public ModeViewModel(Mode mode)
    {
        ...
    }

    public Mode Mode
    {
        get { ... }
    }

    public string Description
    {
        get { return _mode.GetDescription(); }
    }
}

Alternatively, you could look into using ObjectDataProvider.

或者,您可以考虑使用ObjectDataProvider.

回答by Thomas Levesque

I suggest you use a markup extension I had already posted here, with just a little modification :

我建议您使用我已经在这里发布的标记扩展,只需稍作修改:

[MarkupExtensionReturnType(typeof(IEnumerable))]
public class EnumValuesExtension : MarkupExtension
{
    public EnumValuesExtension()
    {
    }

    public EnumValuesExtension(Type enumType)
    {
        this.EnumType = enumType;
    }

    [ConstructorArgument("enumType")]
    public Type EnumType { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (this.EnumType == null)
            throw new ArgumentException("The enum type is not set");
        return Enum.GetValues(this.EnumType).Select(o => GetDescription(o));
    }
}

You can then use it like that :

然后你可以这样使用它:

<ComboBox ItemsSource="{local:EnumValues local:Mode}"/>

EDIT: the method I suggested will bind to a list of string, which is not desirable since we want the SelectedItem to be of type Mode. It would be better to remove the .Select(...) part, and use a binding with a custom converter in the ItemTemplate.

编辑:我建议的方法将绑定到字符串列表,这是不可取的,因为我们希望 SelectedItem 为 Mode 类型。最好删除 .Select(...) 部分,并在 ItemTemplate 中使用与自定义转换器的绑定。

回答by Cédric Bellec

I've done it like this :

我已经这样做了:

<ComboBox x:Name="CurrencyCodeComboBox" Grid.Column="4" DisplayMemberPath="." HorizontalAlignment="Left" Height="22"   Margin="11,6.2,0,10.2" VerticalAlignment="Center" Width="81" Grid.Row="1" SelectedValue="{Binding currencyCode}" >
            <ComboBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel/>
                </ItemsPanelTemplate>
            </ComboBox.ItemsPanel>
        </ComboBox>

in code I set itemSource :

在代码中我设置了 itemSource :

CurrencyCodeComboBox.ItemsSource = [Enum].GetValues(GetType(currencyCode))