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

For those who like a good WPF binding challenge:

I have a nearly functional example of two-way binding a checkbox to an individual bit of a flags enumeration (thanks Ian Oakes, original MSDN post). The problem though is that the binding behaves as if it is one way (UI to DataContext, not vice versa). So effectively the check box does not initialize, but if it is toggled the data source is correctly updated. Attached is the class defining some attached dependency properties to enable the bit-based binding. What I've noticed is that ValueChanged is never called, even when I force the DataContext to change.

What I've tried: Changing the order of property definitions, Using a label and text box to confirm the DataContext is bubbling out updates, Any plausible FrameworkMetadataPropertyOptions (AffectsRender, BindsTwoWayByDefault), Explicitly setting Binding Mode=TwoWay, Beating head on wall, Changing ValueProperty to EnumValueProperty in case of conflict.

Any suggestions or ideas would be extremely appreciated, thanks for anything you can offer!

The enumeration:

    [Flags]
    public enum Department : byte
    {
        None = 0x00,
        A = 0x01,
        B = 0x02,
        C = 0x04,
        D = 0x08
    } // end enum Department

The XAML usage:

    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}"

The class:

    /// 
    /// 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

Answers


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.


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

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


Here's something I came up with that leaves the View nice and clean (no static resources necessary, no new attached properties to fill out, no converters or converter parameters required in the binding), and leaves the ViewModel clean (no extra properties to bind to)

The View looks like this:

<CheckBox Content="A" IsChecked="{Binding Department[A]}"/>
<CheckBox Content="B" IsChecked="{Binding Department[B]}"/>
<CheckBox Content="C" IsChecked="{Binding Department[C]}"/>
<CheckBox Content="D" IsChecked="{Binding Department[D]}"/>

The ViewModel looks like this:

public class ViewModel : ViewModelBase
{
  private Department department;

  public ViewModel()
  {
    Department = new EnumFlags<Department>(department);
  }

  public Department Department { get; private set; }
}

If you're ever going to assign a new value to the Department property, don't. Leave Department alone. Write the new value to Department.Value instead.

This is where the magic happens (this generic class can be reused for any flag enum)

public class EnumFlags<T> : INotifyPropertyChanged where T : struct, IComparable, IFormattable, IConvertible
{
  private T value;

  public EnumFlags(T t)
  {
    if (!typeof(T).IsEnum) throw new ArgumentException($"{nameof(T)} must be an enum type"); // I really wish they would just let me add Enum to the generic type constraints
    value = t;
  }

  public T Value
  {
    get { return value; }
    set
    {
      if (this.value.Equals(value)) return;
      this.value = value;
      OnPropertyChanged("Item[]");
    }
  }

  [IndexerName("Item")]
  public bool this[T key]
  {
    get
    {
      // .net does not allow us to specify that T is an enum, so it thinks we can't cast T to int.
      // to get around this, cast it to object then cast that to int.
      return (((int)(object)value & (int)(object)key) == (int)(object)key);
    }
    set
    {
      if ((((int)(object)this.value & (int)(object)key) == (int)(object)key) == value) return;

      this.value = (T)(object)((int)(object)this.value ^ (int)(object)key);

      OnPropertyChanged("Item[]");
    }
  }

  #region INotifyPropertyChanged
  public event PropertyChangedEventHandler PropertyChanged;

  private void OnPropertyChanged([CallerMemberName] string memberName = "")
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(memberName));
  }
  #endregion
}

I do not have enough rep to comment yet, this solution is targeting user99999991: "Cant have multiple checkboxes binding to different values on a page with the same converter I guess." Another advantage, with this solution you can also bind the Flag mask instead of hardcoding a static reference.

Using a IMultiValueConverter:

public class FlagToBoolConverter : IMultiValueConverter

{
    private YourFlagEnum selection;
    private YourFlagEnum mask;

    public static int InstanceCount = 0;

    public FlagToBoolConverter()
    {
        InstanceCount++;
    }

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        mask = (YourFlagEnum ) values[1];
        selection = (YourFlagEnum ) values[0];
        return (mask & selection) != 0;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value.Equals(true))
        {
            selection |= mask;
        }
        else
        {
            selection &= ~mask;
        }

        object[] o = new object[2];
        o[0] = selection;
        o[1] = mask;
        return o;
    }
}

ItemsControl (CheckBoxTemplates is a List, so you can add multiple Checkboxes during Runtime):

                            <ItemsControl ItemsSource="{Binding CheckBoxTemplates}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <StackPanel Orientation="Vertical" Margin="40,0,0,0"></StackPanel>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                    <CheckBox Content="{Binding Path=Content}" >
                                        <CheckBox.Style>
                                            <Style TargetType="CheckBox">
                                                <Setter Property="IsChecked">
                                                    <Setter.Value>
                                                        <MultiBinding Converter="{StaticResource FlagToBoolConverter}">
                                                            <Binding Path="MyEnumProperty" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"></Binding>
                                                            <Binding Path="MyEnumPropertyMask"></Binding>
                                                        </MultiBinding>
                                                    </Setter.Value>
                                                </Setter>
                                            </Style>
                                        </CheckBox.Style>
                                    </CheckBox>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>

Important: When declaring the converter, set x:Shared="False" so that multiple instances are created:

<UserControl.Resources>
    <ui:FlagToBoolConverter x:Key="FlagToBoolConverter" x:Shared="False"></ui:FlagToBoolConverter>
</UserControl.Resources>

Need Your Help

Intellij break point not working.

java maven intellij-12

I've a maven project deployed in jetty 9 server.

probabilities with small numbers

probability product arbitrary-precision

I am working with large amounts of probabilities that I multiply so i quickly obtain very small numbers. But it seems that python finally store the final result as zero.