2008-11-28 9 views
13

Für diejenigen, die eine gute WPF verbindlich Herausforderung mag:Wie können Sie ein Kontrollkästchen an ein einzelnes Bit einer Flags-Enumeration binden?

Ich habe eine fast Funktionsbeispiel von Zwei-Wege eine Checkbox auf ein einzelnes Bit eines Flags Enumeration (dank Ian Oakes, original MSDN post) verbindlich. Das Problem besteht jedoch darin, dass sich die Bindung so verhält, als ob es eine Richtung wäre (UI zu DataContext, nicht umgekehrt). Daher wird das Kontrollkästchen nicht initialisiert, aber wenn es umgeschaltet wird, wird die Datenquelle korrekt aktualisiert. Angehängt ist die Klasse, die einige angefügte Abhängigkeitseigenschaften definiert, um die Bit-basierte Bindung zu aktivieren. Was ich bemerkt habe, ist, dass ValueChanged niemals aufgerufen wird, selbst wenn ich den DataContext erzwinge.

Was ich versucht habe: Ändern der Reihenfolge der Eigenschaftsdefinitionen, eine Beschriftung und Textfeld Mit der Datacontext sprudelt aus Updates, eine plausible FrameworkMetadataPropertyOptions (AffectsRender, BindsTwoWayByDefault), um zu bestätigen, Explizit Bindungsmodus = TwoWay Einstellung, Beating Kopf an der Wand, Ändern von ValueProperty zu EnumValueProperty im Konfliktfall.

Alle Vorschläge oder Ideen würden sehr geschätzt werden, danke für alles, was Sie anbieten können!

Die Aufzählung:


    [Flags] 
    public enum Department : byte 
    { 
     None = 0x00, 
     A = 0x01, 
     B = 0x02, 
     C = 0x04, 
     D = 0x08 
    } // end enum Department 

Die XAML Nutzung:


    CheckBox Name="studentIsInDeptACheckBox" 
      ctrl:CheckBoxFlagsBehaviour.Mask="{x:Static c:Department.A}" 
      ctrl:CheckBoxFlagsBehaviour.IsChecked="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}" 
      ctrl:CheckBoxFlagsBehaviour.Value="{Binding Department}" 

Die Klasse:


    /// 
    /// A helper class for providing bit-wise binding. 
    /// 
    public class CheckBoxFlagsBehaviour 
    { 
     private static bool isValueChanging; 

     public static Enum GetMask(DependencyObject obj) 
     { 
      return (Enum)obj.GetValue(MaskProperty); 
     } // end GetMask 

     public static void SetMask(DependencyObject obj, Enum value) 
     { 
      obj.SetValue(MaskProperty, value); 
     } // end SetMask 

     public static readonly DependencyProperty MaskProperty = 
      DependencyProperty.RegisterAttached("Mask", typeof(Enum), 
      typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(null)); 

     public static Enum GetValue(DependencyObject obj) 
     { 
      return (Enum)obj.GetValue(ValueProperty); 
     } // end GetValue 

     public static void SetValue(DependencyObject obj, Enum value) 
     { 
      obj.SetValue(ValueProperty, value); 
     } // end SetValue 

     public static readonly DependencyProperty ValueProperty = 
      DependencyProperty.RegisterAttached("Value", typeof(Enum), 
      typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(null, ValueChanged)); 

     private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
     { 
      isValueChanging = true; 
      byte mask = Convert.ToByte(GetMask(d)); 
      byte value = Convert.ToByte(e.NewValue); 

      BindingExpression exp = BindingOperations.GetBindingExpression(d, IsCheckedProperty); 
      object dataItem = GetUnderlyingDataItem(exp.DataItem); 
      PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path); 
      pi.SetValue(dataItem, (value & mask) != 0, null); 

      ((CheckBox)d).IsChecked = (value & mask) != 0; 
      isValueChanging = false; 
     } // end ValueChanged 

     public static bool? GetIsChecked(DependencyObject obj) 
     { 
      return (bool?)obj.GetValue(IsCheckedProperty); 
     } // end GetIsChecked 

     public static void SetIsChecked(DependencyObject obj, bool? value) 
     { 
      obj.SetValue(IsCheckedProperty, value); 
     } // end SetIsChecked 

     public static readonly DependencyProperty IsCheckedProperty = 
      DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), 
      typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(false, IsCheckedChanged)); 

     private static void IsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
     { 
      if (isValueChanging) return; 

      bool? isChecked = (bool?)e.NewValue; 
      if (isChecked != null) 
      { 
       BindingExpression exp = BindingOperations.GetBindingExpression(d, ValueProperty); 
       object dataItem = GetUnderlyingDataItem(exp.DataItem); 
       PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path); 

       byte mask = Convert.ToByte(GetMask(d)); 
       byte value = Convert.ToByte(pi.GetValue(dataItem, null)); 

       if (isChecked.Value) 
       { 
        if ((value & mask) == 0) 
        { 
         value = (byte)(value + mask); 
        } 
       } 
       else 
       { 
        if ((value & mask) != 0) 
        { 
         value = (byte)(value - mask); 
        } 
       } 

       pi.SetValue(dataItem, value, null); 
      } 
     } // end IsCheckedChanged 

     /// 
     /// Gets the underlying data item from an object. 
     /// 
     /// The object to examine. 
     /// The underlying data item if appropriate, or the object passed in. 
     private static object GetUnderlyingDataItem(object o) 
     { 
      return o is DataRowView ? ((DataRowView)o).Row : o; 
     } // end GetUnderlyingDataItem 
    } // end class CheckBoxFlagsBehaviour 

Antwort

37

Sie könnten einen Wertkonverter verwenden. Hier ist eine sehr spezifische Implementierung für das Ziel-Enum, würde aber nicht schwer zu sehen, wie der Konverter allgemeinere zu machen:

[Flags] 
public enum Department 
{ 
    None = 0, 
    A = 1, 
    B = 2, 
    C = 4, 
    D = 8 
} 

public partial class Window1 : Window 
{ 
    public Window1() 
    { 
     InitializeComponent(); 

     this.DepartmentsPanel.DataContext = new DataObject 
     { 
      Department = Department.A | Department.C 
     }; 
    } 
} 

public class DataObject 
{ 
    public DataObject() 
    { 
    } 

    public Department Department { get; set; } 
} 

public class DepartmentValueConverter : IValueConverter 
{ 
    private Department target; 

    public DepartmentValueConverter() 
    { 
    } 

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     Department mask = (Department)parameter; 
     this.target = (Department)value; 
     return ((mask & this.target) != 0); 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     this.target ^= (Department)parameter; 
     return this.target; 
    } 
} 

Und dann verwenden Sie den Konverter in der XAML:

<Window.Resources> 
    <l:DepartmentValueConverter x:Key="DeptConverter" /> 
</Window.Resources> 

<StackPanel x:Name="DepartmentsPanel"> 
    <CheckBox Content="A" 
       IsChecked="{Binding 
          Path=Department, 
          Converter={StaticResource DeptConverter}, 
          ConverterParameter={x:Static l:Department.A}}"/> 
    <!-- more --> 
</StackPanel> 

EDIT : Ich habe nicht genug "rep" (noch!), Um unten zu kommentieren, also muss ich meinen eigenen Beitrag aktualisieren :(

In der letzten Anmerkung sagt demwiz.myopenid.com "aber wenn es zu zwei kommt- Wegbindung das ConvertBack zerfällt ", naja ich habe update d meinen Beispielcode oben, um das ConvertBack-Szenario zu behandeln; Ich habe auch eine Beispielanwendung here (bearbeiten: beachten Sie, dass der Beispielcode Download auch eine generische Version des Konverters enthält).

Persönlich denke ich, das ist viel einfacher, ich hoffe, das hilft.

+0

Danke für den Vorschlag Paul, aber wenn es mehrere Kontrollkästchen gibt, wird die ConvertBack von jedem von ihnen überschreiben und die Daten für die anderen Bits verlieren. Es ist der ConvertBack-Teil, der dies zu einem kniffligen Problem macht. –

+0

In der Tat ist die Probe ein wenig vereinfachend; Aber ich denke, dass diese Lösung immer noch gilt, da Sie sich den eingehenden Bool ansehen können? value und dann^= der Wert, der auf der im ConverterParameter angegebenen Maske basiert; Sinn ergeben? Wenn nicht, lemme es, und ich poste etwas Code, wenn ich etwas Zeit in den Ferien habe. – PaulJ

+0

Aktualisierte den Post, um das ConvertBack-Szenario einzuschließen. Beachten Sie außerdem, dass ich einen Link zu einer Arbeitskopie der Anwendung gepostet habe. – PaulJ

1

Ihre Datenobjekt prüfen, die an die Kontrollkästchen bindet enthält Abteilung Eigentum eine INotifyPropertyChnaged hat. PropertyChanged hat seinen Setter aufgerufen?

+0

Ich bin an eine stark typisierte DataRow gebunden, die PropertyChanged-Ereignisse erfolgreich veröffentlicht. Ich habe dies bestätigt, indem ich es an andere UI-Steuerelemente (Label, TextBox) gebunden habe, die korrekt aktualisiert werden. Vielen Dank für den Vorschlag obwohl. :) –

2

Danke für die Hilfe von allen, ich habe es endlich herausgefunden.

Ich bin an ein stark typisiertes DataSet gebunden, so dass die Enumerationen als Typ System.Byte und nicht System.Enum gespeichert werden. In meinem Debug-Ausgabefenster bemerkte ich zufällig eine Silent-Binding-Casting-Ausnahme, die mich auf diesen Unterschied hinwies. Die Lösung ist die gleiche wie oben, aber mit der ValueProperty vom Typ Byte anstelle von Enum.

Hier wird die CheckBoxFlagsBehavior-Klasse in ihrer letzten Revision wiederholt. Danke nochmal an Ian Oakes für die originale Implementierung!

public class CheckBoxFlagsBehaviour 
{ 
    private static bool isValueChanging; 

    public static Enum GetMask(DependencyObject obj) 
    { 
     return (Enum)obj.GetValue(MaskProperty); 
    } // end GetMask 

    public static void SetMask(DependencyObject obj, Enum value) 
    { 
     obj.SetValue(MaskProperty, value); 
    } // end SetMask 

    public static readonly DependencyProperty MaskProperty = 
     DependencyProperty.RegisterAttached("Mask", typeof(Enum), 
     typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(null)); 

    public static byte GetValue(DependencyObject obj) 
    { 
     return (byte)obj.GetValue(ValueProperty); 
    } // end GetValue 

    public static void SetValue(DependencyObject obj, byte value) 
    { 
     obj.SetValue(ValueProperty, value); 
    } // end SetValue 

    public static readonly DependencyProperty ValueProperty = 
     DependencyProperty.RegisterAttached("Value", typeof(byte), 
     typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(default(byte), ValueChanged)); 

    private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     isValueChanging = true; 
     byte mask = Convert.ToByte(GetMask(d)); 
     byte value = Convert.ToByte(e.NewValue); 

     BindingExpression exp = BindingOperations.GetBindingExpression(d, IsCheckedProperty); 
     object dataItem = GetUnderlyingDataItem(exp.DataItem); 
     PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path); 
     pi.SetValue(dataItem, (value & mask) != 0, null); 

     ((CheckBox)d).IsChecked = (value & mask) != 0; 
     isValueChanging = false; 
    } // end ValueChanged 

    public static bool? GetIsChecked(DependencyObject obj) 
    { 
     return (bool?)obj.GetValue(IsCheckedProperty); 
    } // end GetIsChecked 

    public static void SetIsChecked(DependencyObject obj, bool? value) 
    { 
     obj.SetValue(IsCheckedProperty, value); 
    } // end SetIsChecked 

    public static readonly DependencyProperty IsCheckedProperty = 
     DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), 
     typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(false, IsCheckedChanged)); 

    private static void IsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     if (isValueChanging) return; 

     bool? isChecked = (bool?)e.NewValue; 
     if (isChecked != null) 
     { 
      BindingExpression exp = BindingOperations.GetBindingExpression(d, ValueProperty); 
      object dataItem = GetUnderlyingDataItem(exp.DataItem); 
      PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path); 

      byte mask = Convert.ToByte(GetMask(d)); 
      byte value = Convert.ToByte(pi.GetValue(dataItem, null)); 

      if (isChecked.Value) 
      { 
       if ((value & mask) == 0) 
       { 
        value = (byte)(value + mask); 
       } 
      } 
      else 
      { 
       if ((value & mask) != 0) 
       { 
        value = (byte)(value - mask); 
       } 
      } 

      pi.SetValue(dataItem, value, null); 
     } 
    } // end IsCheckedChanged 

    private static object GetUnderlyingDataItem(object o) 
    { 
     return o is DataRowView ? ((DataRowView)o).Row : o; 
    } // end GetUnderlyingDataItem 
} // end class CheckBoxFlagsBehaviour 
+0

Dies scheint schrecklich komplex - warum würde nicht ein einfacher Wertkonverter –

+1

Ein Wertkonverter eignet sich hervorragend für unidirektionale Bindungen, aber wenn es um bidirektionale Bindungen geht, fällt ConvertBack auseinander, weil Sie nicht wissen können, auf was die anderen Bits gesetzt sind, um einen gültigen Wert zurückzugeben. –