Check your DataObject which binds to the CheckBoxes contains Department property has an INotifyPropertyChnaged.PropertyChanged called on its Setter?
views:
4682answers:
4You could use a value converter. Here's a very specific implementation for the target Enum, but would not be hard to see how to make the converter more generic:
[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;
}
}
And then use the converter in the 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: I don't have enough "rep" (yet!) to comment below so I have to update my own post :(
In the last comment demwiz.myopenid.com says "but when it comes to two-way binding the ConvertBack falls apart", well I've updated my sample code above to handle the ConvertBack scenario; I've also posted a sample working application here (edit: note that the sample code download also includes a generic version of the converter).
Personally I think this is a lot simpler, I hope this helps.
Thanks for everyone's help, I finally figured it out.
I am binding to a strongly typed DataSet, so the enumerations are stored as type System.Byte and not System.Enum. I happened to notice a silent binding casting exception in my debug output window which pointed me to this difference. The solution is the same as above, but with the ValueProperty being of type Byte instead of Enum.
Here is the CheckBoxFlagsBehavior class repeated in its final revision. Thanks again to Ian Oakes for the original implementation!
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