更好的PropertyChanged和PropertyChanging事件处理

我正在为我们的应用程序实现观察者模式 – 目前正在使用RX Framework。

我目前有一个看起来像这样的例子:

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

(我有一个类似的“PropertyChanging”)

EventArgs不会给我太多。 我想要的是EventArgs的扩展,它使我能够查看先前和新值,以及在“更改”侦听器中标记事件的能力,以便实际上不会持续更改。 如何才能做到这一点? 谢谢。

我认为这取决于你如何实现INotifyPropertyChanging和INotifyPropertyChanged接口。

遗憾的是,PropertyChangingEventArgs和PropertyChangedEventArgs类不提供属性的前后值或取消更改的能力,但您可以派生自己的事件args类来提供该function。

首先,定义以下事件args类。 请注意,这些派生自PropertyChangingEventArgs类和PropertyChangedEventArgs类。 这允许我们将这些对象作为参数传递给PropertyChangingEventHandler和PropertyChangedEventHandler委托。

 class PropertyChangingCancelEventArgs : PropertyChangingEventArgs { public bool Cancel { get; set; } public PropertyChangingCancelEventArgs(string propertyName) : base(propertyName) { } } class PropertyChangingCancelEventArgs : 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 : 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(string propertyName, T originalValue, T newValue) { var handler = this.PropertyChanging; if (handler != null) { var args = new PropertyChangingCancelEventArgs(propertyName, originalValue, newValue); handler(this, args); return !args.Cancel; } return true; } protected void OnPropertyChanged(string propertyName, T previousValue, T currentValue) { var handler = this.PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(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)e).OriginalValue; int newValue = ((PropertyChangingCancelEventArgs)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)e).PreviousValue; int currentValue = ((PropertyChangedEventArgs)e).CurrentValue; } } } 

接受的响应非常糟糕,只需使用Buffer()即可。

 Observable.FromEventPattern(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 

对于任何想要最好的RX和能够取消的人来说,这两种想法都是混合的

ViewModel基类的东西

 public abstract class INPCBase : INotifyPropertyChanged, INotifyPropertyChanging { public event PropertyChangingEventHandler PropertyChanging; public event PropertyChangedEventHandler PropertyChanged; protected bool OnPropertyChanging(string propertyName, T originalValue, T newValue) { var handler = this.PropertyChanging; if (handler != null) { var args = new PropertyChangingCancelEventArgs(propertyName, originalValue, newValue); handler(this, args); return !args.Cancel; } return true; } protected void OnPropertyChanged(string propertyName, T previousValue, T currentValue) { var handler = this.PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName, previousValue, currentValue)); } } public class PropertyChangingCancelEventArgs : PropertyChangingEventArgs { public bool Cancel { get; set; } public PropertyChangingCancelEventArgs(string propertyName) : base(propertyName) { } } public class PropertyChangingCancelEventArgs : 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 : 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; } } 

然后我有这几个扩展。

一个从Expression树获取属性名称

 public static class ExpressionExtensions { public static string GetPropertyName(this Expression> 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(this Expression> 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(this Expression> action) { MemberExpression ex = (MemberExpression)action.Body; return ex.Member.Name; } public static void CheckIsNotNull(this Expression> 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> ObserveSpecificPropertyChanging( this TItem target, Expression> propertyName) where TItem : INotifyPropertyChanging { var property = propertyName.GetPropertyName(); return ObserveSpecificPropertyChanging(target, property) .Select(i => new ItemPropertyChangingEvent() { OriginalEventArgs = (PropertyChangingCancelEventArgs)i.OriginalEventArgs, Property = i.Property, Sender = i.Sender }); } public static IObservable> ObserveSpecificPropertyChanging( this TItem target, string propertyName = null) where TItem : INotifyPropertyChanging { return Observable.Create>(obs => { Dictionary properties = new Dictionary(); 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() { Sender = target, Property = prop, OriginalEventArgs = a, }; obs.OnNext(change); } }; target.PropertyChanging += handler; return () => { target.PropertyChanging -= handler; }; }); } public class ItemPropertyChangingEvent { 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 { public TSender Sender { get; set; } public PropertyInfo Property { get; set; } public PropertyChangingCancelEventArgs 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); } } } } } 

工作一种享受