2016-03-09 147 views
2

我想要在我的ViewModel上有一個枚舉,讓我們來說代表一個人的性別。代表該ViewModel的視圖應該能夠呈現提供該值的方式;無論是一組單選按鈕還是一個組合框(如果有很多)。在這裏有很多例子,你在XAML中硬編碼單選按鈕,每個按鈕代表它代表了它的值。而更好的人也將使用顯示屬性的名稱來提供單選按鈕的文本。動態綁定到一個枚舉

我期待着更進一步。我希望根據Enum的值和DisplayAttribute的名稱和描述等動態生成 RadioButtons。理想情況下,我希望它選擇創建一個ComboBox(而不是RadioButtons),如果它超過6個項目(可能實現爲某種控件);但讓我們看看我們是否可以在我們嘗試跑步之前走路。 :)

我的谷歌搜索讓我很接近......這裏就是我的了:

public enum Gender 
{ 
    [Display(Name="Gentleman", Description = "Slugs and snails and puppy-dogs' tails")] 
    Male, 

    [Display(Name = "Lady", Description = "Sugar and spice and all things nice")] 
    Female 
} 

窗口:

<Window x:Class="WpfApplication2.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:WpfApplication2" 
    mc:Ignorable="d" 
    Title="MainWindow" Height="350" Width="525"> 
<Window.Resources> 
    <local:EnumMultiConverter x:Key="EnumMultiConverter"/> 

    <ObjectDataProvider 
     MethodName="GetValues" 
     ObjectType="{x:Type local:EnumDescriptionProvider}" 
     x:Key="AdvancedGenderTypeEnum"> 

     <ObjectDataProvider.MethodParameters> 
      <x:Type TypeName="local:Gender"/> 
     </ObjectDataProvider.MethodParameters> 
    </ObjectDataProvider> 
</Window.Resources> 
<StackPanel> 
    <ItemsControl ItemsSource="{Binding Source={StaticResource AdvancedGenderTypeEnum}}"> 
     <ItemsControl.ItemTemplate> 
      <DataTemplate> 
       <RadioButton GroupName="{Binding GroupName}" Content="{Binding Name}" ToolTip="{Binding Description}"> 
        <RadioButton.IsChecked> 
         <MultiBinding Converter="{StaticResource EnumMultiConverter}" Mode="TwoWay"> 
          <Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}" Path="DataContext.Gender" Mode="TwoWay" /> 
          <Binding Path="Value" Mode="OneWay"/> 
         </MultiBinding> 
        </RadioButton.IsChecked> 
       </RadioButton> 
      </DataTemplate> 
     </ItemsControl.ItemTemplate> 
    </ItemsControl> 
</StackPanel> 
</Window> 

EnumDescriptionProvider:

public static class EnumDescriptionProvider 
{ 
    public static IList<EnumerationItem> GetValues(Type enumType) 
    { 
     string typeName = enumType.Name; 
     var typeList = new List<EnumerationItem>(); 

     foreach (var value in Enum.GetValues(enumType)) 
     { 
      FieldInfo fieldInfo = enumType.GetField(value.ToString()); 
      var displayAttribute = (DisplayAttribute)Attribute.GetCustomAttribute(fieldInfo, typeof(DisplayAttribute)); 

      if (displayAttribute == null) 
      { 
       typeList.Add(new EnumerationItem 
       { 
        GroupName = typeName, 
        Value = value, 
        Name = value.ToString(), 
        Description = value.ToString() 
       }); 
      } 
      else 
      { 
       typeList.Add(new EnumerationItem 
       { 
        GroupName = typeName, 
        Value = value, 
        Name = displayAttribute.Name, 
        Description = displayAttribute.Description 
       }); 
      } 
     } 

     return typeList; 
    } 
} 

EnumerationItem:

public class EnumerationItem 
{ 
    public object GroupName { get; set; } 
    public object Value { get; set; } 
    public string Name { get; set; } 
    public string Description { get; set; } 
} 

而多轉換器(因爲的IValueConverter不能把綁定的ConverterParameter):

public class EnumMultiConverter : IMultiValueConverter 
{ 
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
    { 
     return values[0].Equals(values[1]); 
    } 

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 
    { 
     throw new NotSupportedException(); 
    } 
} 

所以,我所擁有的唯一的問題是,我不能做ConvertBack。但也許有人有一個很好的解決方案。正如我所說,理想情況下,我只想要一些可以綁定到ViewModel上的枚舉的魔法控件,併爲它爲該枚舉的每個值動態創建RadioButton。但我會採取我能得到的任何建議。

+0

爲什麼當字典能夠很好地完成這個任務時,你想要使用Enum,當我們有編譯時間常量時使用枚舉。 在你的情況'''詞典<字符串,DisplayAttribute>'''會做一樣好 –

+0

罷工上面我誤解了問題 –

+0

你爲什麼要回來轉換?您已經擁有索引 - 只需將選定的索引綁定到視圖模型,並且可以將索引(一個int)轉換爲枚舉。 – code4life

回答

0

我終於找到這個帖子:How to bind RadioButtons to an enum? 如果按artiom看起來很長的路到了答案,他提出了一個解決方案,而被嚴厲批評給了一個鏈接之前給出的鏈接(也就是現在損壞)這可能會破壞:)我聯繫了他,他立即給我發送信息。例如,它可以讓我只有這個在XAML:

<local:EnumRadioButton 
     SelectedItem="{Binding Path=Gender, Mode=TwoWay}" 
     EnumType="{x:Type local:Gender}" 
     RadioButtonStyle="{StaticResource MyStyle}"/> 

因此而不是多轉換,我在原來的文章中提到,您需要:

public class EnumToBooleanConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     return value?.Equals(parameter); 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     return value.Equals(true) ? parameter : Binding.DoNothing; 
    } 
} 

,這裏是魔法位:

public class EnumRadioButton : ItemsControl 
{ 
    public static readonly DependencyProperty EnumTypeProperty = 
     DependencyProperty.Register(nameof(EnumType), typeof(Type), typeof(EnumRadioButton), new PropertyMetadata(null, EnumTypeChanged)); 

    public static readonly DependencyProperty SelectedItemProperty = 
     DependencyProperty.Register(nameof(SelectedItem), typeof(object), typeof(EnumRadioButton)); 

    public static readonly DependencyProperty RadioButtonStyleProperty = 
     DependencyProperty.Register(nameof(RadioButtonStyle), typeof(Style), typeof(EnumRadioButton)); 


    public Type EnumType 
    { 
     get { return (Type)GetValue(EnumTypeProperty); } 
     set { SetValue(EnumTypeProperty, value); } 
    } 

    public object SelectedItem 
    { 
     get { return GetValue(SelectedItemProperty); } 
     set { SetValue(SelectedItemProperty, value); } 
    } 

    public Style RadioButtonStyle 
    { 
     get { return (Style)GetValue(RadioButtonStyleProperty); } 
     set { SetValue(RadioButtonStyleProperty, value); } 
    } 

    private static void EnumTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     EnumRadioButton enumRadioButton = (EnumRadioButton)d; 
     enumRadioButton.UpdateItems(e.NewValue as Type); 
    } 

    private void UpdateItems(Type newValue) 
    { 
     Items.Clear(); 
     if (!newValue.IsEnum) 
     { 
      throw new ArgumentOutOfRangeException(nameof(newValue), $"Only enum types are supported in {GetType().Name} control"); 
     } 

     var enumerationItems = EnumerationItemProvider.GetValues(newValue); 
     foreach (var enumerationItem in enumerationItems) 
     { 
      var radioButton = new RadioButton { Content = enumerationItem.Name, ToolTip = enumerationItem.Description }; 
      SetCheckedBinding(enumerationItem, radioButton); 
      SetStyleBinding(radioButton); 
      Items.Add(radioButton); 
     } 
    } 

    private void SetStyleBinding(RadioButton radioButton) 
    { 
     var binding = new Binding 
     { 
      Source = this, 
      Mode = BindingMode.OneWay, 
      Path = new PropertyPath(nameof(RadioButtonStyle)) 
     }; 
     radioButton.SetBinding(StyleProperty, binding); 
    } 

    private void SetCheckedBinding(EnumerationItem enumerationItem, RadioButton radioButton) 
    { 
     var binding = new Binding 
     { 
      Source = this, 
      Mode = BindingMode.TwoWay, 
      Path = new PropertyPath(nameof(SelectedItem)), 
      Converter = new EnumToBooleanConverter(), // would be more efficient as a singleton 
      ConverterParameter = enumerationItem.Value 
     }; 
     radioButton.SetBinding(ToggleButton.IsCheckedProperty, binding); 
    } 
} 
1

我會建議您使用自定義行爲,這將允許您將所有Enum ViewModel邏輯放入一個可重用的代碼片段中。你不必纏鬥複雜ValueConverters

有演示解決這個問題非常一個偉大的文章和GitHub的樣本這樣,見下文

WPF – Enum ItemsSource With Custom Behavior - Article

GitHub repository for sample code

鏈接我希望這給你你正在尋找什麼

+0

感謝您的解決方案;但是,它只是爲每個值創建一個帶有項目的ListBox。我正在尋找的東西,爲每個值給我一個可用的* RadioButton *。例如,如果我刪除從我上面貼了「ItemsControl.ItemTemplate」標籤和它的內容,我也有類似你的解決方案提供了什麼(如果我用同樣的上添加一個ToString方法「騙」我EnumerationItem)。 – Richardissimo

+0

你說你真的想要一個組合框。只要改變列表框的組合框和它的工作原理完全一樣 –

+0

道歉的誤解,讓我澄清...這是一個長期的用戶界面設計原則是,如果只有幾個項目,你會使用單選按鈕(我認爲微軟有6作爲限制);但如果不止於此,則應使用ComboBox。我想要的東西會給我RadioButtons或ComboBox取決於有多少物品。 – Richardissimo

0

你幾乎在那裏,關鍵是認識到一個RadioButton的Command事件總是當用戶點擊它時被觸發,即使IsChecked屬性被綁定。您需要做的就是使IsChecked多值綁定OneWay並添加一個命令處理程序,當用戶選中單選按鈕時,它將被調用,即是這樣的:

<DataTemplate> 
    <RadioButton Content="{Binding Name}" ToolTip="{Binding Description}" 
       Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}, Path=DataContext.CheckedCommand}" 
       CommandParameter="{Binding}"> 
     <RadioButton.IsChecked> 
      <MultiBinding Converter="{StaticResource EnumMultiConverter}" Mode="OneWay"> 
       <Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}" Path="DataContext.Gender" /> 
       <Binding Path="Value" /> 
      </MultiBinding> 
     </RadioButton.IsChecked> 
    </RadioButton> 
</DataTemplate> 

然後回到您的視圖模型,你爲它設置性別的值手動而不是依賴於單選按鈕來傳播值回自己的命令處理程序:

public ICommand CheckedCommand { get { return new RelayCommand<Gender>(value => this.Gender = value); } } 

注意,你甚至不需要一個組名,這一切都根據其特性自動處理和指揮你的視圖模型綁定到(這是用於測試目的更好反正)。

+0

謝謝你。好的解決方案關於此方法的GroupName的好提示。 (BTW CommandParameter需要是「{Binding Value}」,因爲Binding是EnumerationItem)。迄今爲止的最佳答案。我只是覺得我的View應該能夠將雙向綁定到視圖模型的枚舉上。 (即,我不需要改變視圖模型來支持視圖的實現方式,並且必須爲每個Enum添加一個命令)。這就是爲什麼我想我可能需要寫我自己的控制(我不知道該怎麼做)。 – Richardissimo

+0

我希望你可以用一個附加的行爲來做同樣的事情,然後你可以用全局風格自動應用到所有單選按鈕。當我下週回到工作崗位時,甚至可能會自己離開。 –