代码之家  ›  专栏  ›  技术社区  ›  Steve Cadwallader

如何将复选框双向绑定到标志枚举的单个位?

  •  17
  • Steve Cadwallader  · 技术社区  · 16 年前

    对于那些喜欢好的WPF绑定挑战的人:

    CheckBox original MSDN post )但问题是绑定的行为似乎是单向的(UI) DataContext ,反之亦然)。如此有效地 不初始化,但如果切换,数据源将正确更新。Attached是定义一些附加的依赖属性以启用基于位的绑定的类。我注意到ValueChanged从未被调用,即使我强制 数据上下文 改变。

    我所尝试的: 更改特性定义的顺序,使用标签和文本框确认 数据上下文 有没有什么可信的更新 FrameworkMetadataPropertyOptions AffectsRender , BindsTwoWayByDefault Binding Mode=TwoWay ,头撞在墙上,换衣服 ValueProperty EnumValueProperty 万一发生冲突。

    如果您有任何建议或想法,我们将不胜感激,谢谢您提供的一切!

    枚举:

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

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

    /// <summary>
    /// A helper class for providing bit-wise binding.
    /// </summary>
    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
    
        /// <summary>
        /// Gets the underlying data item from an object.
        /// </summary>
        /// <param name="o">The object to examine.</param>
        /// <returns>The underlying data item if appropriate, or the object passed in.</returns>
        private static object GetUnderlyingDataItem(object o)
        {
            return o is DataRowView ? ((DataRowView)o).Row : o;
        } // end GetUnderlyingDataItem
    } // end class CheckBoxFlagsBehaviour
    
    5 回复  |  直到 5 年前
        1
  •  53
  •   Eliahu Aaron Guru Stron    5 年前

    您可以使用值转换器。下面是针对该目标的一个非常具体的实现 Enum ,但不难看出如何使转换器更通用:

    [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;
        }
    }
    

    然后在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>
    

    Steve Cadwallader 说: “但当涉及到双向绑定时,ConvertBack会分崩离析” ,我已经更新了上面的示例代码来处理ConvertBack场景;我还发布了一个示例工作应用程序 here 编辑:

    我个人认为这要简单得多,我希望这会有所帮助。

        2
  •  3
  •   Nick    7 年前

    我想到了一个方法,可以使视图保持整洁(不需要静态资源,不需要填写新的附加属性,绑定中不需要转换器或转换器参数),并且使ViewModel保持整洁(不需要绑定其他属性)

    视图如下所示:

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

    public class ViewModel : ViewModelBase
    {
      private Department department;
    
      public ViewModel()
      {
        Department = new EnumFlags<Department>(department);
      }
    
      public Department Department { get; private set; }
    }
    

    如果要为Department属性指定新值,请不要这样做。别管这个部门。将新值写入Department.value。

    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
    }
    
        3
  •  2
  •   Steve Cadwallader    16 年前

    谢谢大家的帮助,我终于明白了。

    我绑定到一个强类型数据集,因此枚举存储为System.Byte类型,而不是System.Enum类型。我碰巧在调试输出窗口中注意到一个无声绑定强制转换异常,它将我指向这个差异。解决方案与上述相同,但ValueProperty的类型为Byte而不是Enum。

    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
    
        4
  •  1
  •   Jobi Joy    16 年前

    选中绑定到包含部门属性的复选框的DataObject是否在其Setter上调用了INotifyPropertyChnaged.PropertyChanged?

        5
  •  0
  •   Skelvir    6 年前

    我还没有足够的代表发表评论,此解决方案针对的是用户9999991:
    “我想不能用同一个转换器将多个复选框绑定到页面上的不同值。”
    另一个优点是,使用此解决方案,您还可以绑定标志掩码,而不是硬编码静态引用。

    使用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 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>
    

    要点:声明转换器时,设置x:Shared=“False”,以便创建多个实例:

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