c# - 如何将复选框双向绑定(bind)到标志枚举的单个位?

标签 c# wpf data-binding enums bit-manipulation

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

我有一个将 CheckBox 双向绑定(bind)到标志枚举的单个位的几乎实用的示例(感谢 Ian Oakes,original MSDN post)。但问题是绑定(bind)的行为就好像它是一种方式(UI 到 DataContext,反之亦然)。因此,实际上 CheckBox 不会初始化,但如果它被切换,数据源就会正确更新。 Attached 是定义一些附加依赖属性以启用基于位的绑定(bind)的类。我注意到 ValueChanged 从未被调用,即使我强制更改 DataContext 也是如此。

我尝试过的:更改属性定义的顺序,使用标签和文本框确认 DataContext 正在更新,任何看似合理的 FrameworkMetadataPropertyOptions(AffectsRenderBindsTwoWayByDefault),显式设置Binding Mode=TwoWay,撞墙,改变ValuePropertyEnumValueProperty 以防发生冲突。

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

枚举:

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

类(class):

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

最佳答案

您可以使用值转换器。这是目标 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说:“但是当谈到双向绑定(bind)时,ConvertBack 会分崩离析”,好吧,我已经更新了上面的示例代码来处理 ConvertBack 场景;我还发布了一个示例工作应用程序 here (编辑:请注意,示例代码下载还包括转换器的通用版本)。

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

关于c# - 如何将复选框双向绑定(bind)到标志枚举的单个位?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/326802/

相关文章:

wpf - 如何在 ItemsControl 中为 ContentControl 设置动画

c# - 尝试从 ListBox 对象转换为 MyListBox 时出现 invalidcastException : ListBox object in WPF

wpf - MVVM:模型状态更改时通知 View

c# - 使用带有绑定(bind)的自定义附加属性

c# - AvaloniaUI - 如何直接在 Canvas 上绘制

c# - 泛化委托(delegate)代码 (C#)

c# - 每个平台目标的不同程序集名称

c# - 选择 Entity Framework 中另一个表中不存在的记录

c# - 在 ConverterParameter 中使用枚举

apache-flex - 我可以向 Flex 中的数据绑定(bind)操作添加事件监听器吗?