2008-09-12 122 views
220

作为一个例子采取下面的代码:数据绑定一个枚举属性的组合框在WPF

public enum ExampleEnum { FooBar, BarFoo } 

public class ExampleClass : INotifyPropertyChanged 
{ 
    private ExampleEnum example; 

    public ExampleEnum ExampleProperty 
    { get { return example; } { /* set and notify */; } } 
} 

我想向物业ExampleProperty数据绑定到一个组合框,以便它显示选项“FooBar的”和“ BarFoo“并以TwoWay模式工作。最理想的我希望我的组合框定义是这个样子:

<ComboBox ItemsSource="What goes here?" SelectedItem="{Binding Path=ExampleProperty}" /> 

目前我对ComboBox.SelectionChanged处理器和ExampleClass.PropertyChanged安装在我的窗口,我做的手工绑定事件。

有没有更好的或某种规范的方式?你通常会使用转换器,你将如何使用正确的值填充组合框?我甚至不想立即开始使用i18n。

编辑

所以一个问题得到回答:如何填充正确的价值观的组合框。

通过从静态Enum.GetValues方法的ObjectDataProvider的检索枚举值作为字符串列表:

<Window.Resources> 
    <ObjectDataProvider MethodName="GetValues" 
     ObjectType="{x:Type sys:Enum}" 
     x:Key="ExampleEnumValues"> 
     <ObjectDataProvider.MethodParameters> 
      <x:Type TypeName="ExampleEnum" /> 
     </ObjectDataProvider.MethodParameters> 
    </ObjectDataProvider> 
</Window.Resources> 

这我可以作为一个的ItemsSource用我的组合框:

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"/> 
+3

我探讨了这一点,并有一个解决方案,您可以使用(完成本地化)在WPF位于[这里](http://www.ageektrapped.com/blog/the-missing-net-7-displaying-enums-在-WPF /)。 – ageektrapped 2008-09-19 00:52:56

回答

39

我不不知道是否可以在XAML中使用,但请尝试以下操作:

为您的ComboBox命名,以便您可以在代码隐藏中访问它:“typesComboBox1”

现在尝试以下

typesComboBox1.ItemsSource = Enum.GetValues(typeof(ExampleEnum)); 
0

尝试使用

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}" 
    SelectedValue="{Binding Path=ExampleProperty}" /> 
+0

这不起作用。组合框只会显示一个空文本,改变它不会做任何事情。 我猜在这里投入转换器将是最好的解决方案。 – Maximilian 2008-09-12 12:48:11

+0

为什么downvote? Geez .. – 2010-10-30 00:13:51

5

可以考虑类似的东西:

  1. 定义文本块样式,或你想要的任何其他控制用来显示你的枚举:

    <Style x:Key="enumStyle" TargetType="{x:Type TextBlock}"> 
         <Setter Property="Text" Value="&lt;NULL&gt;"/> 
         <Style.Triggers> 
          <Trigger Property="Tag"> 
           <Trigger.Value> 
            <proj:YourEnum>Value1<proj:YourEnum> 
           </Trigger.Value> 
           <Setter Property="Text" Value="{DynamicResource yourFriendlyValue1}"/> 
          </Trigger> 
          <!-- add more triggers here to reflect your enum --> 
         </Style.Triggers> 
        </Style> 
    
  2. 定义样式ComboBoxItem

    <Style TargetType="{x:Type ComboBoxItem}"> 
         <Setter Property="ContentTemplate"> 
          <Setter.Value> 
           <DataTemplate> 
            <TextBlock Tag="{Binding}" Style="{StaticResource enumStyle}"/> 
           </DataTemplate> 
          </Setter.Value> 
         </Setter> 
        </Style> 
    
  3. 添加一个组合框,并加载它与你的枚举值:

     <ComboBox SelectedValue="{Binding Path=your property goes here}" SelectedValuePath="Content"> 
          <ComboBox.Items> 
           <ComboBoxItem> 
            <proj:YourEnum>Value1</proj:YourEnum> 
           </ComboBoxItem> 
          </ComboBox.Items> 
         </ComboBox> 
    

如果你的枚举很大,你当然可以在代码中做同样的事情,避免了大量的输入。 我喜欢这种方式,因为它使本地化变得简单 - 您只需定义一次所有模板,然后,您只需更新字符串资源文件。

+0

SelectedValuePath =“Content”帮助我在这里。我有我的ComboBoxItems作为字符串值,并继续获取无法将ComboBoxItem转换为我的枚举类型。谢谢 – adriaanp 2009-05-13 03:43:44

183

您可以创建自定义标记扩展。用法

例子:

enum Status 
{ 
    [Description("Available.")] 
    Available, 
    [Description("Not here right now.")] 
    Away, 
    [Description("I don't have time right now.")] 
    Busy 
} 
<ComboBox 
    ItemsSource="{Binding Source={my:Enumeration {x:Type my:Status}}}" 
    DisplayMemberPath="Description" 
    SelectedValue="{Binding CurrentStatus}" 
    SelectedValuePath="Value" /> 

和实现...

public class EnumerationExtension : MarkupExtension 
    { 
    private Type _enumType; 


    public EnumerationExtension(Type enumType) 
    { 
     if (enumType == null) 
     throw new ArgumentNullException("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 = 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; } 
    } 
    } 
+4

@Gregor S.我的:枚举是什么? – joshua 2012-07-10 06:53:24

+8

@Crown'my'是您在xaml文件顶部声明的命名空间前缀:例如xmlns:my =“clr-namespace:namespace_to_enumeration_extension_class。Enumeration是EnumerationExtension的缩写,在xaml中,您不必编写整个扩展类 – 2012-07-10 07:20:42

+0

使用上面的代码是否仍然可以将所选项目的值保存到用户设置并将其读回? – papaiatis 2012-07-23 07:42:54

149

在视图模型,你可以有:

public MyEnumType SelectedMyEnumType 
    { 
     get { return _selectedMyEnumType; } 
     set { 
       _selectedMyEnumType = value; 
       OnPropertyChanged("SelectedMyEnumType"); 
      } 
    } 

    public IEnumerable<MyEnumType> MyEnumTypeValues 
    { 
     get 
     { 
      return Enum.GetValues(typeof(MyEnumType)) 
       .Cast<MyEnumType>(); 
     } 
    } 

在XAML中的ItemSource结合到MyEnumTypeValues和SelectedI tem绑定到SelectedMyEnumType。

<ComboBox SelectedItem="{Binding SelectedMyEnumType}" ItemsSource="{Binding MyEnumTypeValues}"></ComboBox> 
22

基于由ageektrapped提供的接受,但现在删除的答案,我创建了一个瘦身版本,而一些更先进的功能。所有代码都包含在这里,允许您复制粘贴它,而不会被link-rot阻止。

我使用System.ComponentModel.DescriptionAttribute这真的是用于设计时间说明。如果你不喜欢使用这个属性,你可以创建自己的属性,但是我认为使用这个属性真的可以完成工作。如果您不使用该属性,则该名称将默认为代码中的枚举值的名称。

public enum ExampleEnum { 

    [Description("Foo Bar")] 
    FooBar, 

    [Description("Bar Foo")] 
    BarFoo 

} 

这里是作为项目源类:

public class EnumItemsSource : Collection<String>, IValueConverter { 

    Type type; 

    IDictionary<Object, Object> valueToNameMap; 

    IDictionary<Object, Object> nameToValueMap; 

    public Type Type { 
    get { return this.type; } 
    set { 
     if (!value.IsEnum) 
     throw new ArgumentException("Type is not an enum.", "value"); 
     this.type = value; 
     Initialize(); 
    } 
    } 

    public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) { 
    return this.valueToNameMap[value]; 
    } 

    public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) { 
    return this.nameToValueMap[value]; 
    } 

    void Initialize() { 
    this.valueToNameMap = this.type 
     .GetFields(BindingFlags.Static | BindingFlags.Public) 
     .ToDictionary(fi => fi.GetValue(null), GetDescription); 
    this.nameToValueMap = this.valueToNameMap 
     .ToDictionary(kvp => kvp.Value, kvp => kvp.Key); 
    Clear(); 
    foreach (String name in this.nameToValueMap.Keys) 
     Add(name); 
    } 

    static Object GetDescription(FieldInfo fieldInfo) { 
    var descriptionAttribute = 
     (DescriptionAttribute) Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute)); 
    return descriptionAttribute != null ? descriptionAttribute.Description : fieldInfo.Name; 
    } 

} 

你可以用它在XAML这样的:

<Windows.Resources> 
    <local:EnumItemsSource 
    x:Key="ExampleEnumItemsSource" 
    Type="{x:Type local:ExampleEnum}"/> 
</Windows.Resources> 
<ComboBox 
    ItemsSource="{StaticResource ExampleEnumItemsSource}" 
    SelectedValue="{Binding ExampleProperty, Converter={StaticResource ExampleEnumItemsSource}}"/> 
61

我宁愿不使用枚举的名称UI。我更喜欢为用户使用不同的值(DisplayMemberPath),而不同的值(在这种情况下枚举)(SelectedValuePath)。这两个值可以打包到KeyValuePair并存储在字典中。

XAML

<ComboBox Name="fooBarComboBox" 
      ItemsSource="{Binding Path=ExampleEnumsWithCaptions}" 
      DisplayMemberPath="Value" 
      SelectedValuePath="Key" 
      SelectedValue="{Binding Path=ExampleProperty, Mode=TwoWay}" > 

C#

public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions 
{ 
    get 
    { 
     return new Dictionary<ExampleEnum, string>() // Fix. Each time new dict.? 
     { 
      {ExampleEnum.FooBar, "Foo Bar"}, 
      {ExampleEnum.BarFoo, "Reversed Foo Bar"}, 
      //{ExampleEnum.None, "Hidden in UI"}, 
     }; 
    } 
} 


private ExampleEnum example; 
public ExampleEnum ExampleProperty 
{ 
    get { return example; } 
    set { /* set and notify */; } 
} 

编辑:与MVVM模式兼容。

2

这是一个使用辅助方法的通用解决方案。 这也可以处理任何基础类型的枚举(byte,sbyte,uint,long等)。)

helper方法:

static IEnumerable<object> GetEnum<T>() { 
    var type = typeof(T); 
    var names = Enum.GetNames(type); 
    var values = Enum.GetValues(type); 
    var pairs = 
     Enumerable.Range(0, names.Length) 
     .Select(i => new { 
       Name = names.GetValue(i) 
      , Value = values.GetValue(i) }) 
     .OrderBy(pair => pair.Name); 
    return pairs; 
}//method 

视图模型:

public IEnumerable<object> EnumSearchTypes { 
    get { 
     return GetEnum<SearchTypes>(); 
    } 
}//property 

组合框:

<ComboBox 
    SelectedValue  ="{Binding SearchType}" 
    ItemsSource   ="{Binding EnumSearchTypes}" 
    DisplayMemberPath ="Name" 
    SelectedValuePath ="Value" 
/> 
18

使用的ObjectDataProvider:

<ObjectDataProvider x:Key="enumValues" 
    MethodName="GetValues" ObjectType="{x:Type System:Enum}"> 
     <ObjectDataProvider.MethodParameters> 
      <x:Type TypeName="local:ExampleEnum"/> 
     </ObjectDataProvider.MethodParameters> 
</ObjectDataProvider> 

,然后绑定到静态资源:

ItemsSource="{Binding Source={StaticResource enumValues}}" 

找到这个solution at this blog

1

这是Gregor S.基础上得票最多的回答一个DevExpress明确的答案(目前有128票)。

这意味着我们可以保持造型在整个应用程序是一致的:

enter image description here

不幸的是,原来的答案不会与一个DevExpress的不ComboBoxEdit一些修改工作。

首先,XAML为ComboBoxEdit

<dxe:ComboBoxEdit ItemsSource="{Binding Source={xamlExtensions:XamlExtensionEnumDropdown {x:myEnum:EnumFilter}}}" 
    SelectedItem="{Binding BrokerOrderBookingFilterSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
    DisplayMember="Description" 
    MinWidth="144" Margin="5" 
    HorizontalAlignment="Left" 
    IsTextEditable="False" 
    ValidateOnTextInput="False" 
    AutoComplete="False" 
    IncrementalFiltering="True" 
    FilterCondition="Like" 
    ImmediatePopup="True"/> 

Needsless说,你需要在包含XAML扩展类(其定义如下)的命名空间指向xamlExtensions

xmlns:xamlExtensions="clr-namespace:XamlExtensions" 

而我们必须在包含枚举的名称空间指向myEnum

xmlns:myEnum="clr-namespace:MyNamespace" 

然后,枚举:

namespace MyNamespace 
{ 
    public enum EnumFilter 
    { 
     [Description("Free as a bird")] 
     Free = 0, 

     [Description("I'm Somewhat Busy")] 
     SomewhatBusy = 1, 

     [Description("I'm Really Busy")] 
     ReallyBusy = 2 
    } 
} 

与XAML的问题是,作为二传手是unaccessable监督您的部分(位,我们不能用SelectedItemValue,因为这将引发一个错误,DevExpress )。所以我们要修改我们的ViewModel直接从对象获得的值:

private EnumFilter _filterSelected = EnumFilter.All; 
public object FilterSelected 
{ 
    get 
    { 
     return (EnumFilter)_filterSelected; 
    } 
    set 
    { 
     var x = (XamlExtensionEnumDropdown.EnumerationMember)value; 
     if (x != null) 
     { 
      _filterSelected = (EnumFilter)x.Value; 
     } 
     OnPropertyChanged("FilterSelected"); 
    } 
} 

为了完整起见,这里是从原来的答案(略重命名)的XAML延伸:

namespace XamlExtensions 
{ 
    /// <summary> 
    ///  Intent: XAML markup extension to add support for enums into any dropdown box, see http://bit.ly/1g70oJy. We can name the items in the 
    ///  dropdown box by using the [Description] attribute on the enum values. 
    /// </summary> 
    public class XamlExtensionEnumDropdown : MarkupExtension 
    { 
     private Type _enumType; 


     public XamlExtensionEnumDropdown(Type enumType) 
     { 
      if (enumType == null) 
      { 
       throw new ArgumentNullException("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 = 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(); 
     } 

     #region Nested type: EnumerationMember 
     public class EnumerationMember 
     { 
      public string Description { get; set; } 
      public object Value { get; set; } 
     } 
     #endregion 
    } 
} 

免责声明:我与DevExpress没有任何关系。 Telerik也是一个很棒的图书馆。

0

我创建了一个开源的CodePlex项目。您可以从here下载NuGet软件包。

<enumComboBox:EnumComboBox EnumType="{x:Type demoApplication:Status}" SelectedValue="{Binding Status}" /> 
1

如果您使用的是MVVM,基于@rudigrobler答案您可以执行以下操作:

以下属性添加到视图模型

public Array ExampleEnumValues => Enum.GetValues(typeof(ExampleEnum)); 

然后在XAML请执行以下操作:

<ComboBox ItemsSource="{Binding ExampleEnumValues}" ... /> 
2

我最喜欢的方式t o这是否与ValueConverter一致,以便ItemsSource和SelectedValue都绑定到相同的属性。这需要没有附加属性保持您的ViewModel很好,干净。

<ComboBox ItemsSource="{Binding Path=ExampleProperty, Converter={x:EnumToCollectionConverter}, Mode=OneTime}" 
      SelectedValuePath="Value" 
      DisplayMemberPath="Description" 
      SelectedValue="{Binding Path=ExampleProperty}" /> 

和转换器的定义:

public static class EnumHelper 
{ 
    public static string Description(this Enum e) 
    { 
    return (e.GetType() 
      .GetField(e.ToString()) 
      .GetCustomAttributes(typeof(DescriptionAttribute), false) 
      .FirstOrDefault() as DescriptionAttribute)?.Description ?? e.ToString(); 
    } 
} 

[ValueConversion(typeof(Enum), typeof(IEnumerable<ValueDescription>))] 
public class EnumToCollectionConverter : MarkupExtension, IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
    return Enum.GetValues(value.GetType()) 
       .Cast<Enum>() 
       .Select(e => new ValueDescription() { Value = e, Description = e.Description()}) 
       .ToList(); 
    } 
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
    return null; 
    } 
    public override object ProvideValue(IServiceProvider serviceProvider) 
    { 
    return this; 
    } 
} 

此转换器将与任何枚举工作。 ValueDescription只是一个简单的类,具有Value属性和Description属性。您可以使用TupleItem1Item2KeyValuePairKeyValue而不是您选择的值和描述或您选择的任何其他类,只要它具有可以保存该枚举值的枚举值和字符串描述。