2009-09-24 44 views
1

我需要有能力選擇多個值,就像從WPF視圖(全部是PropertyGrid中的)列舉枚舉的本質一樣。WPF DataBinding標誌枚舉(在PropertyGrid中)

在問題的性質是動態並且可以使用沒有預先定義的DataTemplates爲類型的屬性將在運行時被發現。 (可以檢測枚舉是否爲Flag的DataTemplate可能會有幫助,但根據我的理解,我需要提前知道Flag枚舉類型才能實現此目的,情況並非如此)。

我已經嘗試了WPF的許多專有和開放源碼屬性網格,並且都沒有支持'Flags'屬性枚舉類型開箱即用。

這個問題的解決方案將是任何可以允許我爲所有商業或開源WPF PropertyGrid的Flags Enum +選擇多個值的數據綁定。

代碼:

實施例屬性類型:

public class PropertyTypeOne 
{ 
    public PropertyTypeOne() 
    { 
     IntProp = 1; 
     InProp2 = 2; 
     BoolProp = true; 
     Boolprop2 = false; 
     StringProp = "string1"; 
     DoubleProp = 2.3; 
     EnumProp = FlagEnumDataTYpe.MarketDepth; 
    } 

    public int IntProp { get; set; } 

    public int InProp2 { get; set; } 

    public bool BoolProp { get; set; } 

    public bool BoolProp2 { get; set; } 

    public string StringProp { get; set; } 

    public double DoubleProp { get; set; } 

    //This is the property in question 
    public FlagEnumDataType EnumProp { get; set; } 
} 

例標誌枚舉類型:

[Flags] 
public enum FlagEnumDataType : byte 
{ 
    None = 0, 
    Trade = 1, 
    Quote = 2, 
    MarketDepth = 4, 
    All = 255 
} 

注:

如果溶液使使用開源WPF PropertyGrid(http://www.codeplex.com/wpg)我會將更改/添加回控件中。

謝謝。

回答

2

我還沒有找到一個真正優雅的方式來做到這一點,但從與Mindscape的開發人員交談,這裏有一些原始的,但功能多數民衆贊成在Mindscape PropertyGrid的作品。

首先,我們爲flag-enum編輯器本身創建一個模板。這是使用EnumValuesConverter從WPF屬性網格填充庫一個ItemsControl:

<ms:EnumValuesConverter x:Key="evc" /> 
<local:FlaggyConverter x:Key="fc" /> 

<DataTemplate x:Key="FlagEditorTemplate"> 
    <ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}"> 
    <ItemsControl.ItemTemplate> 
     <DataTemplate> 
     <CheckBox Content="{Binding}"> 
     </CheckBox> 
     </DataTemplate> 
    </ItemsControl.ItemTemplate> 
    </ItemsControl> 
</DataTemplate> 

現在我們需要顯示的複選框,根據標誌是否是開還是關檢查。這需要兩件事:首先是一個IMultiValueConverter,以便它可以考慮手邊的標誌和上下文值,其次是單獨複選框讀取上下文值的方式。 (通過上下文值我的意思是實際的屬性值,例如背景值可能是標誌1 | Flag4的| Flag32。)這裏的轉換器:

public class FlaggyConverter : IMultiValueConverter 
{ 
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
    int flagValue = (int)values[0]; 
    int propertyValue = (int)values[1]; 

    return (flagValue & propertyValue) == flagValue; 
    } 

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) 
    { 
    throw new NotImplementedException(); 
    } 
} 

對於傳播的上下文值,我要走捷徑和使用標籤。您可能更願意使用更有意義的名稱創建附加屬性。

現在控制系統將顯示爲所設置的標誌檢查,但尚未更新的價值,當你點擊或關閉複選框。不幸的是,我發現完成這項工作的唯一方法是處理Checked和Unchecked事件並手動設置上下文值。爲了做到這一點,我們需要將上下文值放置在可以從複選框事件處理程序更新的地方。這意味着將複選框的一個屬性雙向綁定到上下文值。再次,我會使用標籤,雖然你可能想要一些更清潔的東西;另外,我將使用直接事件處理,儘管取決於您的設計,您可能希望將其包含到附加行爲中(如果您創建附加屬性以傳遞上下文值,這將會特別有效)。

<DataTemplate x:Key="FlagEditorTemplate"> 
    <ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}" Tag="{Binding Value, Mode=TwoWay}"> 
    <ItemsControl.ItemTemplate> 
     <DataTemplate> 
     <CheckBox Content="{Binding}" Tag="{Binding Tag, ElementName=ic, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked"> 
      <CheckBox.IsChecked> 
      <MultiBinding Converter="{StaticResource fc}" Mode="OneWay"> 
       <Binding /> 
       <Binding Path="Tag" ElementName="ic" /> 
      </MultiBinding> 
      </CheckBox.IsChecked> 
     </CheckBox> 
     </DataTemplate> 
    </ItemsControl.ItemTemplate> 
    </ItemsControl> 
</DataTemplate> 

注意雙向標籤的結合:是這樣的話,當我們從事件處理代碼設置標籤,它傳播回ic.Tag,並從那裏到屬性的值。

事件處理程序大多是明顯的,但有一個皺紋:

<DataTemplate x:Key="FlagEditorTemplate"> 
    <ItemsControl Name="ic" ItemsSource="{Binding Value, Converter={StaticResource evc}}" Tag="{Binding Value, Mode=TwoWay}"> 
    <ItemsControl.ItemTemplate> 
     <DataTemplate> 
     <CheckBox Content="{Binding}" Tag="{Binding Tag, ElementName=ic, Mode=TwoWay}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked"> 
      <CheckBox.IsChecked> 
      <MultiBinding Converter="{StaticResource fc}" Mode="OneWay"> 
       <Binding /> 
       <Binding Path="Tag" ElementName="ic" /> 
      </MultiBinding> 
      </CheckBox.IsChecked> 
     </CheckBox> 
     </DataTemplate> 
    </ItemsControl.ItemTemplate> 
    </ItemsControl> 
</DataTemplate> 

事件處理程序:

private void CheckBox_Checked(object sender, RoutedEventArgs e) 
{ 
    CheckBox cb = (CheckBox)sender; 
    int val = (int)(cb.Tag); 
    int flag = (int)(cb.Content); 
    val = val | flag; 
    cb.Tag = (Curses)val; 
} 

private void CheckBox_Unchecked(object sender, RoutedEventArgs e) 
{ 
    CheckBox cb = (CheckBox)sender; 
    int val = (int)(cb.Tag); 
    int flag = (int)(cb.Content); 
    val = val & ~flag; 
    cb.Tag = (Curses)val; 
} 

注投設置cb.Tag時。如果沒有這個,當WPF嘗試將其傳播回源時,內部無法將該值轉換爲枚舉類型。這裏Curses是我的枚舉類型。如果您想要一個完全靈活的,類型不可知的編輯器,您需要在外部提供此信息,例如作爲複選框上的附加屬性。你可以使用轉換器來推斷它,或者從編輯器EditContext中傳播它。

最後,我們需要把它連接到網格。您可以在財產的屬性基礎上做這一點:

<ms:PropertyGrid> 
    <ms:PropertyGrid.Editors> 
    <ms:PropertyEditor PropertyName="Curses" EditorTemplate="{StaticResource FlagEditorTemplate}" /> 
    </ms:PropertyGrid.Editors> 
</ms:PropertyGrid> 

,或者通過使用智能編輯聲明掛鉤,其類型有一個FlagsAttribute的所有屬性。有關創建和使用智能編輯器的信息,請參閱http://www.mindscape.co.nz/blog/index.php/2008/04/30/smart-editor-declarations-in-the-wpf-property-grid/

如果您想節省空間,您可以將ItemsControl更改爲ComboBox,但您需要做一些額外的工作來處理摺疊的顯示;我沒有詳細探討這一點。

0

在互聯網上發現,稍有改善,但沒有時間去測試它。

/// <summary> 
/// Two-way conversion from flags to bool and back using parameter as mask 
/// Warning: The trick is in storing value locally between calls to Convert and ConvertBack 
/// You must have a single instance of this converter per flags property per object 
/// Do not share this converter between different objects or properties 
/// Typical usage: 
/// [Flags] enum FlagType { None = 0, Trade = 1, Quote = 2, Report = 4, All = 255 } 
/// <local:EditableFlagsToBooleanConverter x:Key="FlagsToBooleanConverter" /> 
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay, 
///  ConverterParameter={x:Static local:FlagType.Trade}}" >Trade</CheckBox> 
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay, 
///  ConverterParameter={x:Static local:FlagType.Quote}}" >Quote</CheckBox> 
/// <CheckBox IsChecked="{Binding Prop1, Converter={StaticResource FlagsToBooleanConverter}, Mode=TwoWay, 
///  ConverterParameter={x:Static local:FlagType.Report}}" >Report</CheckBox> 
/// </summary> 
public class EditableFlagsToBooleanConverter : IValueConverter 
{ 
    private ulong _target; 

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     if (parameter is Enum && value is Enum) 
     { 
      var mask = (ulong) parameter; 
      _target = (ulong) value; 
      return ((mask & _target) != 0); 
     } 

     return Binding.DoNothing; 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     if (value is bool && parameter is Enum) 
     { 
      var mask = (ulong)parameter; 
      if ((bool)value) 
      { 
       _target |= mask; 
      } 
      else 
      { 
       _target &= ~mask; 
      } 
      return _target; 
     } 

     return Binding.DoNothing; 
    } 
}