Pour ceux qui aiment un bon défi contraignant WPF:Comment pouvez-vous lier deux fois une case à cocher à un bit individuel d'une énumération de drapeaux?
J'ai un exemple presque fonctionnel de deux voies de liaison d'une case à cocher à un bit individuel d'une énumération des drapeaux (merci Ian Oakes, original MSDN post). Le problème est que la liaison se comporte comme si elle était à sens unique (UI to DataContext, et non l'inverse). La case à cocher ne s'initialise donc pas, mais si elle est activée, la source de données est correctement mise à jour. Attachée est la classe définissant certaines propriétés de dépendance attachées pour activer la liaison basée sur les bits. Ce que j'ai remarqué est que ValueChanged n'est jamais appelé, même quand je force le DataContext à changer.
Ce que j'ai essayé: Changer l'ordre des définitions de propriétés, l'aide d'une étiquette et zone de texte pour confirmer la DataContext bouillonne des mises à jour, toutes les FrameworkMetadataPropertyOptions plausibles (AffectsRender, BindsTwoWayByDefault), réglage Explicitement mode de liaison = TwoWay, Battant head on wall, Changer ValueProperty en EnumValueProperty en cas de conflit.
Toutes les suggestions ou idées seraient extrêmement appréciées, merci pour tout ce que vous pouvez offrir!
L'énumération:
[Flags]
public enum Department : byte
{
None = 0x00,
A = 0x01,
B = 0x02,
C = 0x04,
D = 0x08
} // end enum Department
L'utilisation 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 classe:
///
/// 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
Merci pour la suggestion Paul, mais s'il y a plusieurs cases à cocher alors le ConvertBack de l'un d'entre eux va remplacer et perdre les données pour les autres bits. C'est la partie ConvertBack qui en fait un problème délicat. –
En effet, l'échantillon est un peu simpliste; cependant, je pense que cette solution s'applique toujours comme vous pourriez regarder le bool entrant? valeur, puis^= la valeur basée sur le masque fourni dans le paramètre ConverterParameter; avoir un sens? Si ce n'est pas lemme savoir et je vais poster du code quand je reçois un peu de temps pendant les vacances. – PaulJ
Mise à jour du post pour inclure le scénario ConvertBack, notez également que j'ai posté un lien vers une copie de travail de l'application. – PaulJ