【问题标题】:WPF binding ComboBox to enum (with a twist)WPF 将 ComboBox 绑定到枚举(有点扭曲)
【发布时间】:2010-10-29 07:30:26
【问题描述】:

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

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

所以在 ComboBox 中,我不想显示 Active、Selected 或 ActiveAndSelected,而是显示枚举的每个值的 DescriptionProperty。我确实为枚举提供了一个名为 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;
            }
        }

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

谢谢!

【问题讨论】:

    标签: wpf data-binding enums combobox


    【解决方案1】:

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

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

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

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

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

    然后你就可以为 ComboBox 提供一个 DisplayTemplate 了,它使用 ModeConverter 来格式化它的项目:

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

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

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

    【讨论】:

    • 老兄,经过几个小时的互联网挖掘,你的回答终于解决了我的问题。谢谢!
    【解决方案2】:

    我喜欢你的想法。但是GetCustomAttributes 使用reflection。这会对你的表现产生什么影响?

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

    【讨论】:

    • 老兄,反射并没有那么慢,尤其是与显示 GUI 所需的时间相比。我不认为这是个问题。
    • 好吧,不要相信我的话。上面引用的帖子说这是一个问题。
    • 但不引用任何配置文件结果。作者对此很担心,但这并不意味着它实际上是一个问题。
    • 这是我唯一可以开始工作的。在初始化 Dictionary 时,我可以使用 GetDescription 扩展方法将其缩短一点。谢谢!
    【解决方案3】:

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

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

    在我的 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; }
        }
    

    此外,我使用通用的 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");
        }
    }
    

    最后,通过视图,我可以执行以下操作:

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

    【讨论】:

    • 是 Enum.Description() 和扩展方法吗?我在 System.Enum 类型上找不到该方法 ..
    • .Description() 是获取描述属性的扩展方法。事后看来,使用 DisplayName 属性可能更合适。
    • 我忽略了问题正文中的扩展方法,这可能是您所指的,并且未使用 DisplayName 因为它不适用于枚举字段目标(除非您扩展属性使用)跨度>
    • 我喜欢你的方法,但我想将你的转换回方法升级为:public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return Enum.GetValues(targetType) .OfType&lt;Enum&gt;() .Single(element =&gt; element.Description() == (string)value); }
    【解决方案4】:

    我建议你使用我已经发布的标记扩展here,只需稍作修改:

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

    你可以这样使用它:

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

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

    【讨论】:

    • 这不会使组合框的 SelectedItem 成为“仅显示活动”而不是 Mode.Active 吗?对我来说似乎是一种不良副作用。
    • 那么您的意思是,通过这种方法,我将无法将所选项目设置为具有枚举的对象当前已选择的内容?
    • @Joe :是的,你是对的……这确实是个问题。我会更新我的答案
    【解决方案5】:

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

    public class ModeViewModel : ViewModel
    {
        private readonly Mode _mode;
    
        public ModeViewModel(Mode mode)
        {
            ...
        }
    
        public Mode Mode
        {
            get { ... }
        }
    
        public string Description
        {
            get { return _mode.GetDescription(); }
        }
    }
    

    或者,您可以考虑使用ObjectDataProvider

    【讨论】:

      【解决方案6】:

      我是这样做的:

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

      在代码中我设置了 itemSource :

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

      【讨论】:

        猜你喜欢
        • 2011-07-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-25
        • 2011-02-06
        • 2014-01-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多