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
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. –
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
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