如何双向绑定一个复选框到一个标志枚举的一个位? [英] How can you two-way bind a checkbox to an individual bit of a flags enumeration?

查看:164
本文介绍了如何双向绑定一个复选框到一个标志枚举的一个位?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我有一个几何功能的例子,双向绑定一个复选框到一个标志枚举的一个位(感谢Ian Oakes,原始MSDN发布)。问题在于绑定的行为就像是一种方式(DataContext的UI,反之亦然)。因此,复选框无法初始化,但如果切换,数据源将被正确更新。附加是定义一些附加的依赖属性以启用基于位的绑定的类。我注意到的是,即使我迫使DataContext更改,ValueChanged也不会被调用。



我试过的是:
更改属性定义,使用标签和文本框确认DataContext正在冒泡更新,任何合理的FrameworkMetadataPropertyOptions(AffectsRender,BindsTwoWayByDefault),显式设置Binding Mode = TwoWay,Beating head on wall,将ValueProperty更改为EnumValueProperty,以防发生冲突。 p>

非常感谢任何建议或想法,谢谢你可以提供的任何东西!



枚举:

  
[Flags]
public enum部门:byte
{
无= 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}

类:

  
///
///提供逐位绑定的帮助器类。
///
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,枚举值)
{
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(枚举)obj.GetValue(ValueProperty);
} // end GetValue

public static void SetValue(DependencyObject obj,枚举值)
{
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);
对象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)新的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);
对象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 +面具);
}
}
else
{
if((value&mask)!= 0)
{
value =(byte)值 - 掩码);
}
}

pi.SetValue(dataItem,value,null);
}
} // end IsCheckedChanged

///
///从对象获取底层数据项。
///
///要检查的对象。
///底层数据项(如果适用)或传入的对象
private static object GetUnderlyingDataItem(object o)
{
return o是DataRowView? ((DataRowView)o).Row:o;
} // end GetUnderlyingDataItem
} // end class CheckBoxFlagsBehaviour


解决方案

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

 标志] 
公开枚举部门
{
无= 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;组; }
}

public class DepartmentValueConverter:IValueConverter
{
private Department target;

public DepartmentValueConverter()
{
}

公共对象转换(对象值,类型targetType,对象参数,CultureInfo文化)
{
部门面具=(部门)参数;
this.target =(Department)value;
return((mask& this.target)!= 0);


public object ConvertBack(object value,Type targetType,object parameter,CultureInfo culture)
{
this.target ^ =(Department)参数;
return this.target;
}
}

然后在XAML中使用转换器: p>

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

编辑:我没有足够的rep )要评论下面,所以我必须更新我自己的帖子:(



在最后一个评论demwiz.myopenid.com说但是当涉及双向绑定ConvertBack崩溃了,我更新了上面的示例代码来处理ConvertBack场景;我还发布了一个示例工作应用程序 这里 编辑:请注意,示例代码下载还包括转换器的通用版本) / p>

个人我认为这是一个很简单,我希望这有助于。


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

解决方案

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.

这篇关于如何双向绑定一个复选框到一个标志枚举的一个位?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆