c# - 如何创建双向数据绑定(bind)代理?

标签 c# .net winforms data-binding 2-way-object-databinding

我有以下类(class)...

class ExpressionBinder<T>
{
    public Func<T> Getter;
    public Action<T> Setter;

    public T Value
    {
        get { return Getter.Invoke(); }
        set { Setter.Invoke(value); }
    }

    public ExpressionBinder(Func<T> getter, Action<T> setter)
    {
        Getter = getter;
        Setter = setter;
    }
}

class ComparisonBinder<TSource, TValue> : ExpressionBinder<bool>, INotifyPropertyChanged
{
    private TSource instance;
    private TValue comparisonValue;
    private PropertyInfo pInfo;

    public event PropertyChangedEventHandler PropertyChanged;

    public ComparisonBinder(TSource instance, Expression<Func<TSource, TValue>> property, TValue comparisonValue) : base(null,null)
    {
        pInfo = GetPropertyInfo(property);

        this.instance = instance;
        this.comparisonValue = comparisonValue;

        Getter = GetValue;
        Setter = SetValue;
    }

    private bool GetValue()
    {
        return comparisonValue.Equals(pInfo.GetValue(instance, null));
    }

    private void SetValue(bool value)
    {
        if (value)
        {
            pInfo.SetValue(instance, comparisonValue,null);

            NotifyPropertyChanged("CustomerName");
        }
    }

    private void NotifyPropertyChanged(string pName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(pName));
        }
    }

    /// <summary>
    /// Adapted from surfen's answer (https://stackoverflow.com/a/10003320/219838)
    /// </summary>
    private PropertyInfo GetPropertyInfo(Expression<Func<TSource, TValue>> propertyLambda)
    {
        Type type = typeof(TSource);

        MemberExpression member = propertyLambda.Body as MemberExpression;

        if (member == null)
            throw new ArgumentException(string.Format(
                "Expression '{0}' refers to a method, not a property.",
                propertyLambda));

        PropertyInfo propInfo = member.Member as PropertyInfo;
        if (propInfo == null)
            throw new ArgumentException(string.Format(
                "Expression '{0}' refers to a field, not a property.",
                propertyLambda));

        if (type != propInfo.ReflectedType &&
            !type.IsSubclassOf(propInfo.ReflectedType))
            throw new ArgumentException(string.Format(
                "Expresion '{0}' refers to a property that is not from type {1}.",
                propertyLambda,
                type));

        return propInfo;
    }
}

我使用 ComparisonBinder 类的代码如下:

radioMale.DataBindings.Add(
    "Checked", 
    new ComparisonBinder<DataClass, GenderEnum>(DataObj, (x) => x.Gender, GenderEnum.Male), 
    "Value", 
    false, 
    DataSourceUpdateMode.OnPropertyChanged);

radioFemale.DataBindings.Add(
    "Checked",
    new ComparisonBinder<DataClass, GenderEnum>(DataObj, (x) => x.Gender, GenderEnum.Male), 
    "Value", 
    false, 
    DataSourceUpdateMode.OnPropertyChanged);

它们允许我使用表达式将许多控件绑定(bind)到同一个属性,从而为控件的绑定(bind)属性生成值。从控件到对象,它都工作得很漂亮。 (如果您想了解更多关于使用此类和 this question 的信息)

现在,我需要换个方向。当我更新我的对象(这将改变 get 表达式的结果)时,我需要更新绑定(bind)控件。我尝试实现 INotifyPropertyChanged 但它没有任何区别。

一件奇怪的事:将控件 DataSourceUpdateMode 设置为 OnPropertyChanged 以某种方式阻止我更改选中的 radio 。

最佳答案

快速回答:

  • 必须使用绑定(bind)源
  • 在您的对象中实现 INotifyPropertyChanged
  • 让代理监听上面的事件
  • 在代理中实现 INotifyPropertyChanged
  • 在代理中过滤从您的对象收到的 PropertyChanged 事件,并仅在 ((PropertyChangedEventArgs) args).PropertyName == pInfo.Name
  • 时引发
  • 将代理添加到 BindSource.DataSource
  • 使用 BindingSource 将控件绑定(bind)到数据对象的属性

最终代码

可用(连同其他绑定(bind)类)位于:http://github.com/svallory/BindingTools

class ExpressionBinder<T> : INotifyPropertyChanged
{
    public Func<T> Getter;
    public Action<T> Setter;

    public event PropertyChangedEventHandler PropertyChanged;

    protected IList<string> WatchingProps;

    public T Value
    {
        get { return Getter.Invoke(); }
        set { Setter.Invoke(value); }
    }

    public ExpressionBinder(Func<T> getter, Action<T> setter)
    {
        WatchingProps = new List<string>();
        Getter = getter;
        Setter = setter;
    }

    public ExpressionBinder(Func<T> getter, Action<T> setter, ref PropertyChangedEventHandler listenToChanges, IList<string> propertyNames)
    {
        Getter = getter;
        Setter = setter;

        listenToChanges += SourcePropertyChanged;
        WatchingProps = propertyNames;
    }

    protected void SourcePropertyChanged(object obj, PropertyChangedEventArgs args)
    {
        if (PropertyChanged != null && WatchingProps.Contains(args.PropertyName))
        {
            PropertyChanged(this, args);
        }
    }

    protected PropertyInfo GetPropertyInfo<TSource, TValue>(Expression<Func<TSource, TValue>> propertyLambda)
    {
        Type type = typeof(TSource);

        var member = propertyLambda.Body as MemberExpression;

        if (member == null)
            throw new ArgumentException(string.Format(
                "Expression '{0}' refers to a method, not a property.",
                propertyLambda));

        var propInfo = member.Member as PropertyInfo;
        if (propInfo == null)
            throw new ArgumentException(string.Format(
                "Expression '{0}' refers to a field, not a property.",
                propertyLambda));

        if (type != propInfo.ReflectedType &&
            !type.IsSubclassOf(propInfo.ReflectedType))
            throw new ArgumentException(string.Format(
                "Expresion '{0}' refers to a property that is not from type {1}.",
                propertyLambda,
                type));

        return propInfo;
    }
}

class ComparisonBinder<TSource, TValue> : ExpressionBinder<bool> where TSource : INotifyPropertyChanged
{
    private readonly TSource instance;
    private readonly TValue comparisonValue;
    private readonly PropertyInfo pInfo;

    public ComparisonBinder(TSource instance, Expression<Func<TSource, TValue>> property, TValue comparisonValue)
        : base(null, null)
    {
        pInfo = GetPropertyInfo(property);

        this.instance = instance;
        this.comparisonValue = comparisonValue;

        Getter = GetValue;
        Setter = SetValue;

        instance.PropertyChanged += SourcePropertyChanged;
        WatchingProps.Add(pInfo.Name);
    }

    private bool GetValue()
    {
        return comparisonValue.Equals(pInfo.GetValue(instance, null));
    }

    private void SetValue(bool value)
    {
        if (value)
        {
            pInfo.SetValue(instance, comparisonValue, null);
        }
    }
}

用法

var bs = new BindingSource();
bs.DataSource = new ComparisonBinder<MySourceObjClass, PropType>(
    MySourceObject, 
    p => p.PropertyToBind, 
    ValueToCompare);

rdBeSmart.DataBindings.Add(
    "Checked", 
    bs, 
    "Value");

关于c# - 如何创建双向数据绑定(bind)代理?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10004127/

相关文章:

c# - 替代私有(private)隐式转换运算符

.net - Rfc2898DeriveBytes 与 golang pbkdf2

c# - 判断集合是否相等(集合由集合组成)

c# - 如何 - 从进程列表中取消列出我的程序..?

c# - 进度条 C#

c# - 为什么这个加密的消息损坏了?

c# - 反序列化时从 json 字段名称中删除字符

c# - 语法 [/] 在 C# 字符串中的含义是什么

使用派生接口(interface)的 C# 接口(interface)实现

c# - C#中的两种不同的窗体