+1  A: 

Check your DataObject which binds to the CheckBoxes contains Department property has an INotifyPropertyChnaged.PropertyChanged called on its Setter?

Jobi Joy
I am binding to a strongly typed DataRow which does successfully publish PropertyChanged events. I confirmed this by binding it to other UI controls (Label, TextBox) which would update correctly.Thank you for the suggestion though. :)
Steve Cadwallader
A: 

How can u do the same without WPF but just a checkbox list.

Ask your own question, as it's impossible to answer properly in comments.
Cameron MacFarland
+9  A: 

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

PaulJ
Thanks for the suggestion Paul, but if there are multiple check boxes then the ConvertBack from any one of them will override and lose the data for the other bits. It is the ConvertBack part that makes this a tricky problem.
Steve Cadwallader
Indeed, the sample is a little simplistic; however, I think this solution still applies as you could look at the incoming bool? value and then ^= the value based on mask supplied in the ConverterParameter; make sense? If not lemme know and I'll post some code when I get some time over the holidays.
PaulJ
Updated the post to include the ConvertBack scenario, also note that I've posted a link to a working copy of the application.
PaulJ
Your suggestion definitely does seem cleaner. I avoid fields on a converter object because I'm not clear how WPF handles creating separate instances of converters. If you had two sets of four check boxes for two copies of the enum - do you think your solution would work?
Steve Cadwallader
Absolutely! To make it all play nicely you'll need to create an instance of the converter for each "flags" value you need to convert. In my sample code above you would simply add another converter to the Windows.Resources section of the XAML, and you're done (obviously w/ a different x:Key value).
PaulJ
Thanks for clearing that up Paul and your solution.
Steve Cadwallader
+1  A: 

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
Steve Cadwallader
This seems awfully complex - why wouldn't a simple value converter do the job?
Daniel Paull
A value converter is great for one-way binding, but when it comes to two-way binding the ConvertBack falls apart because you can't know what the other bits are set to in order to return a valid value.
Steve Cadwallader