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

24 浏览
0 Comments

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

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

我有一个几乎可行的例子,可以将CheckBox双向绑定到标志枚举的单个位(感谢Ian Oakes,原来的MSDN帖子)。但问题是绑定的行为表现为单向(从UI到DataContext,而不是反过来)。因此,CheckBox实际上没有初始化,但如果切换它,则数据源会正确更新。附上了定义一些附加依赖属性的类,以启用基于位的绑定。我注意到即使强制更改DataContext,也从未调用ValueChanged。

我尝试过的:更改属性定义顺序,使用标签和文本框来确认DataContext是否向外部冒泡更新,任何合理的FrameworkMetadataPropertyOptionsAffectsRenderBindsTwoWayByDefault),明确设置Binding Mode= TwoWay,在墙上撞头,将ValueProperty更改为EnumValueProperty以防冲突。

任何建议或想法都将非常感谢,感谢您提供的一切!

枚举:

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

XAML用法:

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

类:

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

admin 更改状态以发布 2023年5月20日
0
0 Comments

这是我想出来的一些东西,使视图保持干净整洁(不需要静态资源,也不需要填写新的附加属性,在绑定中不需要转换器或转换器参数),并且使 ViewModel 保持干净整洁(没有额外的属性进行绑定)。\n\n视图看起来像这样:





\n\nViewModel 看起来像这样:

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

\n\n如果您要分配新值给 Department 属性,请不要这样做。让 Department 保持原样。将新值写入 Department.Value 中。\n\n这就是魔法发生的地方(此通用类可用于任何标志枚举):

public class EnumFlags : 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
}

0
0 Comments

您可以使用值转换器。这是一个非常特定的实现目标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中使用转换器:


    

 
    
    
 

编辑: 我还没有足够的"rep"(尚未!)在下面发表评论,所以我必须更新自己的帖子:(

在最后一条评论中,Steve Cadwallader说:"但是在双向绑定方面,ConvertBack会失败",那么我已经更新了上面的示例代码来处理ConvertBack场景; 我还发布了一个样例工作应用程序这里编辑:请注意,示例代码下载还包括转换器的通用版本)。

个人认为这非常简单,希望能帮到您。

0