I am implementing the observer pattern for our application - currently playing around with the RX Framework.
I currently have an example that looks like this:
<The accepted response is really bad, you can do that simply with 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
For anyone that does want the best of both RX and being able to Cancel here is a hybrid of both of these ideas
The ViewModel base class stuff
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;
}
}
Then I have these couple extensions.
One to get the property name from Expression tree
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);
}
}
}
And then the Rx part
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; }
}
}
Then example usage will be like this
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);
}
}
}
}
}
Works a treat
I think that it comes down to how you implement the INotifyPropertyChanging and INotifyPropertyChanged interfaces.
The PropertyChangingEventArgs and PropertyChangedEventArgs classes unfortunately don't provide a before and after value of the property or the ability to cancel the change, but you can derive your own event args classes that do provide that functionality.
First, define the following event args classes. Notice that these derive from the PropertyChangingEventArgs class and PropertyChangedEventArgs class. This allows us to pass these objects as arguments to the PropertyChangingEventHandler and PropertyChangedEventHandler delegates.
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;
}
}
Next, you would need to use these classes in your implementation of the INotifyPropertyChanging and INotifyPropertyChanged interfaces. An example of an implementation is the following:
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);
}
}
}
}
}
Note, your event handlers for the PropertyChanging and PropertyChanged events will still need to take the original PropertyChangingEventArgs class and PropertyChangedEventArgs class as parameters, rather than a more specific version. However, you will be able to cast the event args objects to your more specific types in order to access the new properties.
Below is an example of event handlers for these events:
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;
}
}
}