Как создать прокси-сервер привязки данных TwoWay?


У меня есть следующие классы...

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

Они позволяют мне привязать множество элементов управления к одному и тому же свойству, используя выражение для генерации значения для связанного свойства элемента управления. Он прекрасно работает от элемента управления до объекта. (Если вы хотите узнать больше об использовании этого класса и этот вопрос)

А теперь мне нужно сделать наоборот. Когда я обновлю свой объект (что изменит результат выражение get) мне нужно обновить связанный элемент управления. Я попытался реализовать INotifyPropertyChanged, но это не имело никакого значения.

Одна странная вещь: установка элементов управления DataSourceUpdateMode на OnPropertyChanged каким-то образом останавливает меня от изменения проверенного радио.

1 3

1 ответ:

Быстрый ответ:

  • Вы должны использовать источник привязки
  • реализовать INotifyPropertyChanged в вашем объекте
  • заставьте прокси-сервер прослушать событие выше
  • реализовать INotifyPropertyChanged в прокси
  • фильтруйте события PropertyChanged, полученные от вашего объекта в прокси, и вызывайте только тогда, когда ((PropertyChangedEventArgs) args).PropertyName == pInfo.Name
  • Добавьте прокси в BindSource.DataSource
  • используйте BindingSource для привязки элемента управления к свойству объекта данных

Финал код

Доступно (наряду с другими классами привязки) по адресу: 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");