none
How can you two-way bind a checkbox to an individual bit of a flags enumeration? RRS feed

  • Question

  •     /// <summary> 
        /// An enumeration of departments. 
        /// </summary> 
        [Flags] 
        public enum Department : byte 
        { 
            None = 0x00, 
            Math = 0x01, 
            Science = 0x02, 
            English = 0x04, 
            Gym = 0x08 
        } // end enum Department 
     
     

    Can anyone suggest a way to perform two-way WPF data binding to individual bits of a flags enumeration?

    Take the code example as a sample for a simple flags (bitwise) based enumeration.  Is there any way to bind the IsChecked field of four check boxes to each of the individual bits bi-directionally?  The Convert method is pretty straight-forward, you can pass the relevant enum value as a ConverterParameter and perform a bitwise-and to return a boolean result.  The problem is in the ConvertBack method, where you only have the true/false from that check box - you've lost the context for all of the other bits in the value and there does not seem to be a way to return a complete value any longer.

    Any help would be greatly appreciated.  I've searched high and low and tried several approaches (ex: IMultiValueConverter) but so far have been stumped and have encountered multiple occasions where this functionality would be useful.

    Thanks greatly for any help!

    Tuesday, October 21, 2008 2:07 AM

Answers

  • I've got this working now, here's a link to another forum where I got additional suggestions: http://stackoverflow.com/questions/326802/how-can-you-two-way-bind-a-checkbox-to-an-individual-bit-of-a-flags-enumeration

    Here is the solution repeated:

    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 


    Sunday, December 21, 2008 6:25 PM

All replies

  • Here's a solution using an attached behaviour that might interest you. Basically it involves creating three attached properties
    1. Mask
      Contains the enum value the CheckBox represents
    2. Value
      The source enum value for the binding
    3. IsChecked
      Binds to the IsChecked property on the CheckBox

    When the source value is updated, the IsChecked property is also updated to reflect the current enum, likewise when the value of IsChecked changes the enums value is updated to reflect the state of the CheckBox.

    There are a couple of issues that I'm not happy with however, the order the attached properties are declared in the xaml is important, IsChecked must be declared before Value, and it also uses reflection to update the binding sources.

    Taking those issues into account does work, and provides and workable solution to a very valid and interesting problem.

    public class CheckBoxFlagsBehaviour   
    {  
        private static bool isValueChanging;  
     
        public static Enum GetMask(DependencyObject obj)  
        {  
            return (Enum)obj.GetValue(MaskProperty);  
        }  
     
        public static void SetMask(DependencyObject obj, Enum value)  
        {  
            obj.SetValue(MaskProperty, value);  
        }  
     
        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);  
        }  
     
        public static void SetValue(DependencyObject obj, Enum value)  
        {  
            obj.SetValue(ValueProperty, value);  
        }  
     
        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);  
            PropertyInfo pi = exp.DataItem.GetType().GetProperty(exp.ParentBinding.Path.Path);  
            pi.SetValue(exp.DataItem, (value & mask) != 0, null);  
              
            ((CheckBox)d).IsChecked = (value & mask) != 0;  
            isValueChanging = false;  
        }  
     
        public static bool? GetIsChecked(DependencyObject obj)  
        {  
            return (bool?)obj.GetValue(IsCheckedProperty);  
        }  
     
        public static void SetIsChecked(DependencyObject obj, bool? value)  
        {  
            obj.SetValue(IsCheckedProperty, value);  
        }  
     
        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)  
            {  
                byte mask = Convert.ToByte(GetMask(d));  
                byte value = Convert.ToByte(GetValue(d));  
     
                if (isChecked.Value)  
                {  
                    if ((value & mask) == 0)  
                    {  
                        value = (byte)(value + mask);  
                    }  
                }  
                else 
                {  
                    if ((value & mask) != 0)  
                    {  
                        value = (byte)(value - mask);  
                    }  
                }  
     
                BindingExpression exp = BindingOperations.GetBindingExpression(d, ValueProperty);  
                PropertyInfo pi = exp.DataItem.GetType().GetProperty(exp.ParentBinding.Path.Path);  
                pi.SetValue(exp.DataItem, value, null);  
            }  
        }  
    }  
     

    <StackPanel> 
        <TextBlock Text="{Binding Path=Department, ElementName=window}" /> 
        <CheckBox   
            x:Name="test" 
            Content="Math"   
            local:CheckBoxFlagsBehaviour.Mask="{x:Static local:Department.Math}" 
            local:CheckBoxFlagsBehaviour.IsChecked="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}" 
            local:CheckBoxFlagsBehaviour.Value="{Binding Path=Department, ElementName=window}" 
            /> 
        <CheckBox   
            Content="Science" 
            local:CheckBoxFlagsBehaviour.Mask="{x:Static local:Department.Science}" 
            local:CheckBoxFlagsBehaviour.IsChecked="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}" 
            local:CheckBoxFlagsBehaviour.Value="{Binding Path=Department, ElementName=window}" 
            /> 
        <CheckBox   
            Content="English" 
            local:CheckBoxFlagsBehaviour.Mask="{x:Static local:Department.English}" 
            local:CheckBoxFlagsBehaviour.IsChecked="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}" 
            local:CheckBoxFlagsBehaviour.Value="{Binding Path=Department, ElementName=window}" 
            /> 
        <CheckBox   
            Content="Gym" 
            local:CheckBoxFlagsBehaviour.Mask="{x:Static local:Department.Gym}" 
            local:CheckBoxFlagsBehaviour.IsChecked="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}" 
            local:CheckBoxFlagsBehaviour.Value="{Binding Path=Department, ElementName=window}" 
            /> 
    </StackPanel> 

    Ian Oakes www.trillian.com.au
    Sunday, October 26, 2008 11:30 AM
  • Hi Ian - 

    Thank you very much for the example, that got me very close.  I am utilizing the DataContext for my binding, so I found that instead of utilizing the BindingExpression.DataItem, I had to drop down one level into the actual Row instead of the DataRowView.  I added a "GetUnderlyingDataItem" which will return the DataRow if it is given a DataRowView - and I call this from IsCheckedChanged and ValueChanged where the BindingExpression.DataItem is used.

    It works, with the exception that the check boxes are not initializing correctly.  They start out all unchecked, and once I start toggling them the binding updates work fine.  I've played with it for a couple hours but I'm stumped again.  Do you have any further ideas?  Here is the class with the minor alterations I made.  I do have the fields defined in the proper order (mask, ischecked then value).

    Thanks again for any help you can offer,
    -Steve


    1    public class CheckBoxFlagsBehaviour 
    2    { 
    3        private static bool isValueChanging; 
    4 
    5        public static Enum GetMask(DependencyObject obj) 
    6        { 
    7            return (Enum)obj.GetValue(MaskProperty); 
    8        } // end GetMask 
    9 
    10        public static void SetMask(DependencyObject obj, Enum value) 
    11        { 
    12            obj.SetValue(MaskProperty, value); 
    13        } // end SetMask 
    14 
    15        public static readonly DependencyProperty MaskProperty = 
    16            DependencyProperty.RegisterAttached("Mask"typeof(Enum), 
    17            typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(null)); 
    18 
    19        public static Enum GetValue(DependencyObject obj) 
    20        { 
    21            return (Enum)obj.GetValue(ValueProperty); 
    22        } // end GetValue 
    23 
    24        public static void SetValue(DependencyObject obj, Enum value) 
    25        { 
    26            obj.SetValue(ValueProperty, value); 
    27        } // end SetValue 
    28 
    29        public static readonly DependencyProperty ValueProperty = 
    30            DependencyProperty.RegisterAttached("Value"typeof(Enum), 
    31            typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(null, ValueChanged)); 
    32 
    33        private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    34        { 
    35            isValueChanging = true
    36            byte mask = Convert.ToByte(GetMask(d)); 
    37            byte value = Convert.ToByte(e.NewValue); 
    38 
    39            BindingExpression exp = BindingOperations.GetBindingExpression(d, IsCheckedProperty); 
    40            object dataItem = GetUnderlyingDataItem(exp.DataItem); 
    41            PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path); 
    42            pi.SetValue(dataItem, (value & mask) != 0, null); 
    43 
    44            ((CheckBox)d).IsChecked = (value & mask) != 0; 
    45            isValueChanging = false
    46        } // end ValueChanged 
    47 
    48        public static bool? GetIsChecked(DependencyObject obj) 
    49        { 
    50            return (bool?)obj.GetValue(IsCheckedProperty); 
    51        } // end GetIsChecked 
    52 
    53        public static void SetIsChecked(DependencyObject obj, bool? value) 
    54        { 
    55            obj.SetValue(IsCheckedProperty, value); 
    56        } // end SetIsChecked 
    57 
    58        public static readonly DependencyProperty IsCheckedProperty = 
    59            DependencyProperty.RegisterAttached("IsChecked"typeof(bool?), 
    60            typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(false, IsCheckedChanged)); 
    61 
    62        private static void IsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    63        { 
    64            if (isValueChanging) return
    65 
    66            bool? isChecked = (bool?)e.NewValue; 
    67            if (isChecked != null
    68            { 
    69                BindingExpression exp = BindingOperations.GetBindingExpression(d, ValueProperty); 
    70                object dataItem = GetUnderlyingDataItem(exp.DataItem); 
    71                PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path); 
    72 
    73                byte mask = Convert.ToByte(GetMask(d)); 
    74                byte value = Convert.ToByte(pi.GetValue(dataItem, null)); 
    75 
    76                if (isChecked.Value) 
    77                { 
    78                    if ((value & mask) == 0) 
    79                    { 
    80                        value = (byte)(value + mask); 
    81                    } 
    82                } 
    83                else 
    84                { 
    85                    if ((value & mask) != 0) 
    86                    { 
    87                        value = (byte)(value - mask); 
    88                    } 
    89                } 
    90 
    91                pi.SetValue(dataItem, value, null); 
    92            } 
    93        } // end IsCheckedChanged 
    94 
    95        /// <summary> 
    96        /// Gets the underlying data item from an object. 
    97        /// </summary> 
    98        /// <param name="o">The object to examine.</param> 
    99        /// <returns>The underlying data item if appropriate, or the object passed in.</returns> 
    100        private static object GetUnderlyingDataItem(object o) 
    101        { 
    102            return o is DataRowView ? ((DataRowView)o).Row : o; 
    103        } // end GetUnderlyingDataItem 
    104    } // end class CheckBoxFlagsBehaviour 
    105 

    Saturday, November 8, 2008 10:39 PM
  • I've got this working now, here's a link to another forum where I got additional suggestions: http://stackoverflow.com/questions/326802/how-can-you-two-way-bind-a-checkbox-to-an-individual-bit-of-a-flags-enumeration

    Here is the solution repeated:

    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 


    Sunday, December 21, 2008 6:25 PM