2008-11-28 9 views
13

Para aquellos que les gusta un buen reto vinculante WPF:¿Cómo se puede vincular una casilla de verificación bidireccional a un bit individual de una enumeración de indicadores?

Tengo un ejemplo casi funcional de unirse a una casilla de verificación para un bit individual de una enumeración banderas (gracias Ian Oakes, original MSDN post) de dos vías. Sin embargo, el problema es que el enlace se comporta como si fuera de una sola vía (UI a DataContext, no viceversa). Entonces, efectivamente, la casilla de verificación no se inicializa, pero si se alterna, la fuente de datos se actualiza correctamente. Se adjunta la clase que define algunas propiedades de dependencia adjuntas para habilitar el enlace basado en bits. Lo que noté es que nunca se llama a ValueChanged, incluso cuando obligo a cambiar el DataContext.

Lo que he intentado: Cambiar el orden de las definiciones de propiedad, uso de una etiqueta de texto y cuadro para confirmar la DataContext está burbujeando a cabo actualizaciones, cualquier FrameworkMetadataPropertyOptions plausibles (AffectsRender, BindsTwoWayByDefault), establecer explícitamente modo de unión = TwoWay, Beating head on wall, Cambiando ValueProperty a EnumValueProperty en caso de conflicto.

Cualquier sugerencia o idea sería muy apreciada, ¡gracias por todo lo que puede ofrecer!

La enumeración:


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

El uso de XAML:


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

La clase:


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

Respuesta

37

Puede usar un convertidor de valor. He aquí una aplicación muy específica para la enumeración de destino, pero no sería difícil ver cómo hacer que el convertidor más genérico:

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

y luego usar el convertidor en el 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> 

EDITAR : No tengo suficiente "representante" (todavía!) Para comentar a continuación, así que tengo que actualizar mi propia publicación :(

En el último comentario demwiz.myopenid.com dice "pero cuando se trata de dos- manera de atar el ConvertBack se derrumba ", bueno, tengo una actualización d mi código de ejemplo anterior para manejar el escenario ConvertBack; También publiqué una aplicación de trabajo de ejemplo here (edit: tenga en cuenta que la descarga del código de muestra también incluye una versión genérica del convertidor).

Personalmente creo que esto es mucho más simple, espero que esto ayude.

+0

Gracias por la sugerencia de Paul, pero si hay varias casillas de verificación, el ConvertBack de cualquiera de ellos anulará y perderá los datos de los otros bits. Es la parte ConvertBack que hace que este sea un problema complicado. –

+0

De hecho, la muestra es un poco simplista; sin embargo, creo que esta solución aún se aplica, ya que podría ver el bool entrante? value y luego^= el valor basado en la máscara provista en el ConvertidorParameter; ¿tener sentido? Si no, déjame saber y publicaré un código cuando tenga tiempo durante las vacaciones. – PaulJ

+0

Actualicé la publicación para incluir el escenario ConvertBack, también tenga en cuenta que he publicado un enlace a una copia de trabajo de la aplicación. – PaulJ

1

Compruebe su DataObject que se une a las casillas de verificación contiene la propiedad del Departamento tiene una INotifyPropertyChnaged. ¿PropertyChanged llamó a su Setter?

+0

Estoy vinculando a un DataRow fuertemente tipado que publica con éxito eventos PropertyChanged. Lo confirmé uniéndolo a otros controles de UI (Label, TextBox) que se actualizarían correctamente. Gracias por la sugerencia. :) –

2

Gracias por la ayuda de todos, finalmente lo descubrí.

Estoy vinculando a un DataSet fuertemente tipado, por lo que las enumeraciones se almacenan como tipo System.Byte y no System.Enum. Observé una excepción de conversión de enlace silenciosa en mi ventana de resultados de depuración que me indicó esta diferencia. La solución es la misma que la anterior, pero con ValueProperty siendo de tipo Byte en lugar de Enum.

Aquí está la clase CheckBoxFlagsBehavior repetida en su revisión final. ¡Gracias de nuevo a Ian Oakes por la implementación original!

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

Esto parece tremendamente complejo, ¿por qué un simple convertidor de valor? hacer el trabajo? –

+1

Un convertidor de valor es ideal para vinculación unidireccional, pero cuando se trata de vinculación bidireccional, el ConvertBack se derrumba porque no se puede saber en qué se configuran los otros bits para devolver un valor válido. –

Cuestiones relacionadas