Подтвердить что ты не робот

Улучшение управления PropertyChanged и PropertyChanging

Я реализую шаблон наблюдателя для нашего приложения - в настоящее время играю с RX Framework.

В настоящее время у меня есть пример, который выглядит так:

Observable.FromEventPattern<PropertyChangedEventArgs>(Instance.Address, "PropertyChanged")
    .Where(e => e.EventArgs.PropertyName == "City")
    .ObserveOn(Scheduler.ThreadPool)
    .Subscribe(search => OnNewSearch(search.EventArgs));

(У меня есть аналогичный для PropertyChanging)

EventArgs не дают мне многого. Я бы хотел, чтобы расширение EventArgs дало мне возможность видеть предыдущие и новые значения, а также возможность отмечать событие в "изменяющемся" прослушивателе, так что изменение на самом деле не сохранилось. Как это может быть сделано? Спасибо.

4b9b3361

Ответ 1

Я думаю, что это сводится к тому, как вы реализуете интерфейсы INotifyPropertyChanging и INotifyPropertyChanged.

К сожалению, классы PropertyChangingEventArgs и PropertyChangedEventArgs не предоставляют значения до и после значения свойства или возможности отменить изменение, но вы можете получить собственные классы аргументов событий, которые предоставляют эту функциональность.

Сначала определите следующие классы аргументов событий. Обратите внимание, что это происходит из класса PropertyChangingEventArgs и класса PropertyChangedEventArgs. Это позволяет нам передавать эти объекты в качестве аргументов делегатам PropertyChangingEventHandler и PropertyChangedEventHandler.

class PropertyChangingCancelEventArgs : PropertyChangingEventArgs
{
    public bool Cancel { get; set; }

    public PropertyChangingCancelEventArgs(string propertyName)
        : base(propertyName)
    {
    }
}

class PropertyChangingCancelEventArgs<T> : PropertyChangingCancelEventArgs
{
    public T OriginalValue { get; private set; }

    public T NewValue { get; private set; }

    public PropertyChangingCancelEventArgs(string propertyName, T originalValue, T newValue)
        : base(propertyName)
    {
        this.OriginalValue = originalValue;
        this.NewValue = newValue;
    }
}

class PropertyChangedEventArgs<T> : PropertyChangedEventArgs
{
    public T PreviousValue { get; private set; }

    public T CurrentValue { get; private set; }

    public PropertyChangedEventArgs(string propertyName, T previousValue, T currentValue)
        : base(propertyName)
    {
        this.PreviousValue = previousValue;
        this.CurrentValue = currentValue;
    }
}

Далее вам нужно будет использовать эти классы в реализации интерфейсов INotifyPropertyChanging и INotifyPropertyChanged. Пример реализации следующий:

class Example : INotifyPropertyChanging, INotifyPropertyChanged
{
    public event PropertyChangingEventHandler PropertyChanging;

    public event PropertyChangedEventHandler PropertyChanged;

    protected bool OnPropertyChanging<T>(string propertyName, T originalValue, T newValue)
    {
        var handler = this.PropertyChanging;
        if (handler != null)
        {
            var args = new PropertyChangingCancelEventArgs<T>(propertyName, originalValue, newValue);
            handler(this, args);
            return !args.Cancel;
        }
        return true;
    }

    protected void OnPropertyChanged<T>(string propertyName, T previousValue, T currentValue)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs<T>(propertyName, previousValue, currentValue));
    }

    int _ExampleValue;

    public int ExampleValue
    {
        get { return _ExampleValue; }
        set
        {
            if (_ExampleValue != value)
            {
                if (this.OnPropertyChanging("ExampleValue", _ExampleValue, value))
                {
                    var previousValue = _ExampleValue;
                    _ExampleValue = value;
                    this.OnPropertyChanged("ExampleValue", previousValue, value);
                }
            }
        }
    }
}

Обратите внимание, что обработчики событий для событий PropertyChanging и PropertyChanged все равно должны взять исходный класс PropertyChangingEventArgs и класс PropertyChangedEventArgs в качестве параметров, а не более конкретную версию. Тем не менее, вы сможете передавать объекты args события в свои более конкретные типы, чтобы получить доступ к новым свойствам.

Ниже приведен пример обработчиков событий для этих событий:

class Program
{
    static void Main(string[] args)
    {
        var exampleObject = new Example();

        exampleObject.PropertyChanging += new PropertyChangingEventHandler(exampleObject_PropertyChanging);
        exampleObject.PropertyChanged += new PropertyChangedEventHandler(exampleObject_PropertyChanged);

        exampleObject.ExampleValue = 123;
        exampleObject.ExampleValue = 100;
    }

    static void exampleObject_PropertyChanging(object sender, PropertyChangingEventArgs e)
    {
        if (e.PropertyName == "ExampleValue")
        {
            int originalValue = ((PropertyChangingCancelEventArgs<int>)e).OriginalValue;
            int newValue = ((PropertyChangingCancelEventArgs<int>)e).NewValue;

            // do not allow the property to be changed if the new value is less than the original value
            if(newValue < originalValue)
                ((PropertyChangingCancelEventArgs)e).Cancel = true;
        }

    }

    static void exampleObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "ExampleValue")
        {
            int previousValue = ((PropertyChangedEventArgs<int>)e).PreviousValue;
            int currentValue = ((PropertyChangedEventArgs<int>)e).CurrentValue;
        }
    }
}

Ответ 2

Принятый ответ действительно плохой, вы можете сделать это просто с помощью Buffer().

Observable.FromEventPattern<PropertyChangedEventArgs>(Instance.Address, "PropertyChanged")
    .Where(e => e.EventArgs.PropertyName == "City")
    .Buffer(2,1)  //Take 2 events at a time, every 1 event
    .ObserveOn(Scheduler.ThreadPool)
    .Subscribe(search => ...); //search[0] is old value, search[1] is new value

Ответ 3

Для тех, кто хочет лучшего как RX, так и возможности отменить здесь, это гибрид обеих этих идей.

Материал базового класса ViewModel

public abstract class INPCBase : INotifyPropertyChanged, INotifyPropertyChanging
{
    public event PropertyChangingEventHandler PropertyChanging;

    public event PropertyChangedEventHandler PropertyChanged;

    protected bool OnPropertyChanging<T>(string propertyName, T originalValue, T newValue)
    {
        var handler = this.PropertyChanging;
        if (handler != null)
        {
            var args = new PropertyChangingCancelEventArgs<T>(propertyName, originalValue, newValue);
            handler(this, args);
            return !args.Cancel;
        }
        return true;
    }

    protected void OnPropertyChanged<T>(string propertyName, T previousValue, T currentValue)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs<T>(propertyName, previousValue, currentValue));
    }
}


public class PropertyChangingCancelEventArgs : PropertyChangingEventArgs
{
    public bool Cancel { get; set; }

    public PropertyChangingCancelEventArgs(string propertyName)
        : base(propertyName)
    {
    }
}

public class PropertyChangingCancelEventArgs<T> : PropertyChangingCancelEventArgs
{
    public T OriginalValue { get; private set; }

    public T NewValue { get; private set; }

    public PropertyChangingCancelEventArgs(string propertyName, T originalValue, T newValue)
        : base(propertyName)
    {
        this.OriginalValue = originalValue;
        this.NewValue = newValue;
    }
}

public class PropertyChangedEventArgs<T> : PropertyChangedEventArgs
{
    public T PreviousValue { get; private set; }

    public T CurrentValue { get; private set; }

    public PropertyChangedEventArgs(string propertyName, T previousValue, T currentValue)
        : base(propertyName)
    {
        this.PreviousValue = previousValue;
        this.CurrentValue = currentValue;
    }
}

Тогда у меня есть эти расширения для пары.

Один, чтобы получить имя свойства из дерева выражений

public static class ExpressionExtensions
{

    public static string GetPropertyName<TProperty>(this Expression<Func<TProperty>> expression)
    {
        var memberExpression = expression.Body as MemberExpression;
        if (memberExpression == null)
        {
            var unaryExpression = expression.Body as UnaryExpression;
            if (unaryExpression != null)
            {
                if (unaryExpression.NodeType == ExpressionType.ArrayLength)
                    return "Length";
                memberExpression = unaryExpression.Operand as MemberExpression;

                if (memberExpression == null)
                {
                    var methodCallExpression = unaryExpression.Operand as MethodCallExpression;
                    if (methodCallExpression == null)
                        throw new NotImplementedException();

                    var arg = (ConstantExpression)methodCallExpression.Arguments[2];
                    return ((MethodInfo)arg.Value).Name;
                }
            }
            else
                throw new NotImplementedException();

        }

        var propertyName = memberExpression.Member.Name;
        return propertyName;

    }

    public static string GetPropertyName<T, TProperty>(this Expression<Func<T, TProperty>> expression)
    {
        var memberExpression = expression.Body as MemberExpression;

        if (memberExpression == null)
        {
            var unaryExpression = expression.Body as UnaryExpression;

            if (unaryExpression != null)
            {
                if (unaryExpression.NodeType == ExpressionType.ArrayLength)
                    return "Length";
                memberExpression = unaryExpression.Operand as MemberExpression;

                if (memberExpression == null)
                {
                    var methodCallExpression = unaryExpression.Operand as MethodCallExpression;
                    if (methodCallExpression == null)
                        throw new NotImplementedException();

                    var arg = (ConstantExpression)methodCallExpression.Arguments[2];
                    return ((MethodInfo)arg.Value).Name;
                }
            }
            else
                throw new NotImplementedException();
        }
        var propertyName = memberExpression.Member.Name;
        return propertyName;

    }

    public static String PropertyToString<R>(this Expression<Func<R>> action)
    {
        MemberExpression ex = (MemberExpression)action.Body;
        return ex.Member.Name;
    }

    public static void CheckIsNotNull<R>(this Expression<Func<R>> action, string message)
    {
        MemberExpression ex = (MemberExpression)action.Body;
        string memberName = ex.Member.Name;
        if (action.Compile()() == null)
        {
            throw new ArgumentNullException(memberName, message);
        }
    }

}

И затем часть Rx

public static class ObservableExtensions
{

    public static IObservable<ItemPropertyChangingEvent<TItem, TProperty>> ObserveSpecificPropertyChanging<TItem, TProperty>(
        this TItem target, Expression<Func<TItem, TProperty>> propertyName) where TItem : INotifyPropertyChanging
    {
        var property = propertyName.GetPropertyName();

        return ObserveSpecificPropertyChanging(target, property)
               .Select(i => new ItemPropertyChangingEvent<TItem, TProperty>()
               {
                   OriginalEventArgs = (PropertyChangingCancelEventArgs<TProperty>)i.OriginalEventArgs,
                   Property = i.Property,
                   Sender = i.Sender
               });
    }

    public static IObservable<ItemPropertyChangingEvent<TItem>> ObserveSpecificPropertyChanging<TItem>(
        this TItem target, string propertyName = null) where TItem : INotifyPropertyChanging
    {

        return Observable.Create<ItemPropertyChangingEvent<TItem>>(obs =>
        {
            Dictionary<string, PropertyInfo> properties = new Dictionary<string, PropertyInfo>();
            PropertyChangingEventHandler handler = null;

            handler = (s, a) =>
            {
                if (propertyName == null || propertyName == a.PropertyName)
                {
                    PropertyInfo prop;
                    if (!properties.TryGetValue(a.PropertyName, out prop))
                    {
                        prop = target.GetType().GetProperty(a.PropertyName);
                        properties.Add(a.PropertyName, prop);
                    }
                    var change = new ItemPropertyChangingEvent<TItem>()
                    {
                        Sender = target,
                        Property = prop,
                        OriginalEventArgs = a,
                    };

                    obs.OnNext(change);
                }
            };

            target.PropertyChanging += handler;

            return () =>
            {
                target.PropertyChanging -= handler;
            };
        });
    }



    public class ItemPropertyChangingEvent<TSender>
    {
        public TSender Sender { get; set; }
        public PropertyInfo Property { get; set; }
        public PropertyChangingEventArgs OriginalEventArgs { get; set; }

        public override string ToString()
        {
            return string.Format("Sender: {0}, Property: {1}", Sender, Property);
        }
    }


    public class ItemPropertyChangingEvent<TSender, TProperty>
    {
        public TSender Sender { get; set; }
        public PropertyInfo Property { get; set; }
        public PropertyChangingCancelEventArgs<TProperty> OriginalEventArgs { get; set; }
    }

}

Тогда пример использования будет таким:

public class MainWindowViewModel : INPCBase
{
    private string field1;
    private string field2;


    public MainWindowViewModel()
    {
        field1 = "Hello";
        field2 = "World";

        this.ObserveSpecificPropertyChanging(x => x.Field2)
           .Subscribe(x =>
           {
               if (x.OriginalEventArgs.NewValue == "DOG")
               {
                   x.OriginalEventArgs.Cancel = true;
               }
           });

    }

    public string Field1
    {
        get
        {
            return field1;
        }
        set
        {
            if (field1 != value)
            {
                if (this.OnPropertyChanging("Field1", field1, value))
                {
                    var previousValue = field1;
                    field1 = value;
                    this.OnPropertyChanged("Field1", previousValue, value);
                }
            }
        }
    }


    public string Field2
    {
        get
        {
            return field2;
        }
        set
        {
            if (field2 != value)
            {
                if (this.OnPropertyChanging("Field2", field2, value))
                {
                    var previousValue = field2;
                    field2 = value;
                    this.OnPropertyChanged("Field2", previousValue, value);
                }
            }
        }
    }
}

Работает с лечением