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:
Observable.FromEventPattern<PropertyChangedEventArgs>(Instance.Address, "PropertyChanged")
.Where(e => e.EventArgs.PropertyName == "City")
.ObserveOn(Scheduler.ThreadPool)
.Subscribe(search => OnNewSearch(search.EventArgs));
(I have a similar one for "PropertyChanging")
The EventArgs don't give me much. What I would like is an extension of the EventArgs that would give me the ability to see the previous and new values, as well as the ability to mark the event in the 'changing' listener, such that the change wouldn't actually persist. How can this be done? Thanks.
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;
}
}
}
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
Related
I have the following problem that has got me stuck. In the below example I'm creating class A and class B and inheriting class A from class B. I then create an ObservableCollection<A> to hold a collection of people. What I then wish to do is expand class A to include the properties in class B and create this as a referenced copy ObservableCollection which I can update.
The error I'm getting when this complies is:
Unable to cast object of type
'<CastIterator>d__b1'1[iheriatance_obseravale_collection.Form1+B]' to
type
'System.Collections.ObjectModel.ObservableCollection'1[iheriatance_obseravale_collection.Form1+B]'.
public class A : INotifyPropertyChanged
{
string _firstName;
public string firstName
{
get
{
return _firstName;
}
set
{
if (_firstName != value)
{
_firstName = value;
NotifyPropertyChanged("firstName");
}
}
}
string _surname;
public string surname
{
get
{
return _surname;
}
set
{
if (_surname != value)
{
_surname = value;
NotifyPropertyChanged("surname");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string PropName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropName));
}
}
}
public class B : A, INotifyPropertyChanged
{
bool _isSelected;
public bool isSelected
{
get
{
return _isSelected;
}
set
{
if (_isSelected != value)
{
_isSelected = value;
NotifyPropertyChanged("isSelected");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string PropName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropName));
}
}
}
ObservableCollection<A> peopleColl = new ObservableCollection<A>();
private void addData()
{
peopleColl.Add(new A() { firstName = "James", surname = "Smith" });
peopleColl.Add(new A() { firstName = "John", surname = "Woods" });
}
ObservableCollection<B> selectedPeople;
private void selectAllPeople()
{
selectedPeople = (ObservableCollection<B>)peopleColl.Cast<B>();
foreach (B person in selectedPeople)
{
person.isSelected = true;
Console.WriteLine("Surname:{0}, IsSelected:{1}", person.surname, person.isSelected);
}
}
Many thanks
Stuart Turbefield
You have to iterate the source, cast each object, then create a new collection :
selectedPeople = new ObservableCollection<B>(peopleColl.OfType<B>());
this works only if peopleColl contains B objects. If it contains only A, you need to create new instances of B from A :
selectedPeople = new ObservableCollection<B>(peopleColl.Select(i => new B(i));
public class B : A
{
public B()
{
}
public B(A a)
{
this.firstName = a.firstName;
this.surname = a.surname;
}
}
Just for info, if A implements INotifyPropertyChanged, and if B inherits from A, there's no need to implement INotifyPropertyChanged again in the B class.
I'm wondering about how to do validation the mvvm way. I saw lots of content on this topic on the web, but nothing seems to cover my situation, but maybe I'm just approaching it the wrong way. I have a ValidableModel base class from which my other models inherit:
public abstract class ValidableModel : IDataErrorInfo
{
protected Type _type;
protected readonly Dictionary<string, ValidationAttribute[]> _validators;
protected readonly Dictionary<string, PropertyInfo> _properties;
public ValidableModel()
{
_type = this.GetType();
_properties = _type.GetProperties().ToDictionary(p => p.Name, p => p);
_validators = _properties.Where(p => _getValidations(p.Value).Length != 0).ToDictionary(p => p.Value.Name, p => _getValidations(p.Value));
}
protected ValidationAttribute[] _getValidations(PropertyInfo property)
{
return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
}
public string this[string columnName]
{
get
{
if (_properties.ContainsKey(columnName))
{
var value = _properties[columnName].GetValue(this, null);
var errors = _validators[columnName].Where(v => !v.IsValid(value)).Select(v => v.ErrorMessage).ToArray();
Error = string.Join(Environment.NewLine, errors);
return Error;
}
return string.Empty;
}
}
public string Error
{
get;
set;
}
}
public class SomeModelWithManyFields : ValidableModel {
[Required(ErrorMessage = "required stuff")]
public string Stuff { get; set; }
[Required(ErrorMessage = "another required stuff")]
public string OtherStuff { get; set; }
// and so on
}
This is just an example - in reality my models have more fields (obviously :) ). Now, in my ViewModel I'm exposing whole instance of my model. All this seemed natural - if I'd be exposing every field of every model then I'd have lots of duplicated code. Recently I started wondering if I'm approaching this problem correct. Is there a way to validate my models without code duplication, and not by doing this on the model, but on the ViewModel?
Try this,
EntityBase.cs //This class has Validation logic and all the entites you want to validate must inherit this class
[DataContract(IsReference = true)]
[Serializable]
public abstract class EntityBase : INotifyPropertyChanged, IDataErrorInfo
{
#region Fields
//This hold the property name and its value
private Dictionary<string, object> _values = new Dictionary<string, object>();
#endregion Fields
#region Action
//Subscribe this event if want to know valid changed
public event Action IsValidChanged;
#endregion
#region Protected
protected void SetValue<T>(Expression<Func<T>> propertySelector, T value)
{
string propertyName = GetPropertyName(propertySelector);
SetValue(propertyName, value);
}
protected void SetValue<T>(string propertyName, T value)
{
if (string.IsNullOrEmpty(propertyName))
throw new ArgumentException("Invalid property name", propertyName);
_values[propertyName] = value;
NotifyPropertyChanged(propertyName);
if (IsValidChanged != null)
IsValidChanged();
}
protected T GetValue<T>(Expression<Func<T>> propertySelector)
{
string propertyName = GetPropertyName(propertySelector);
return GetValue<T>(propertyName);
}
protected T GetValue<T>(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
throw new ArgumentNullException("invalid property name",propertyName);
object value;
if (!_values.TryGetValue(propertyName, out value))
{
value = default(T);
_values.Add(propertyName, value);
}
return (T)value;
}
protected virtual string OnValidate(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
throw new ArgumentNullException("propertyName","invalid property name");
string error = string.Empty;
object value = GetValue(propertyName);
//Get only 2 msgs
var results = new List<ValidationResult>(2);
bool result = Validator.TryValidateProperty(value,new ValidationContext(this, null, null){MemberName = propertyName},results);
//if result have errors or for the first time dont set errors
if (!result && (value == null || ((value is int || value is long) && (int)value == 0) || (value is decimal && (decimal)value == 0)))
return null;
if (!result)
{
ValidationResult validationResult = results.First();
error = validationResult.ErrorMessage;
}
return error;
}
#endregion Protected
#region PropertyChanged
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null)
return;
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
protected void NotifyPropertyChanged<T>(Expression<Func<T>> propertySelector)
{
PropertyChangedEventHandler propertyChanged = PropertyChanged;
if (propertyChanged == null)
return;
string propertyName = GetPropertyName(propertySelector);
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion PropertyChanged
#region Data Validation
string IDataErrorInfo.Error
{
get
{
throw new NotSupportedException("IDataErrorInfo.Error is not supported, use IDataErrorInfo.this[propertyName] instead.");
}
}
string IDataErrorInfo.this[string propertyName]
{
get { return OnValidate(propertyName); }
}
#endregion Data Validation
#region Privates
private static string GetPropertyName(LambdaExpression expression)
{
var memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
{
throw new InvalidOperationException();
}
return memberExpression.Member.Name;
}
private object GetValue(string propertyName)
{
object value = null;
if (!_values.TryGetValue(propertyName, out value))
{
PropertyDescriptor propertyDescriptor = TypeDescriptor.GetProperties(GetType()).Find(propertyName, false);
if (propertyDescriptor == null)
throw new ArgumentNullException("propertyName","invalid property");
value = propertyDescriptor.GetValue(this);
if (value != null)
_values.Add(propertyName, value);
}
return value;
}
#endregion Privates
#region Icommand Test
public bool IsValid
{
get
{
if (_values == null)
return true;
//To validate each property which is in _values dictionary
return _values
.Select(property => OnValidate(property.Key))
.All(errorMessages => errorMessages != null && errorMessages.Length <= 0);
}
}
#endregion Icommand Test
}
Order Entity
public class OrderEntity:EntityBase
{
[Required(ErrorMessage="Name is Required")]
public string Name
{
get { return GetValue(() => Name); }
set { SetValue(() => Name, value); }
}
[Required(ErrorMessage = "OrderNumber is Required")]
public string OrderNumber
{
get { return GetValue(() => OrderNumber); }
set { SetValue(() => OrderNumber, value); }
}
[Required(ErrorMessage = "Quantity is Required")]
[Range(20,75,ErrorMessage="Quantity must be between 20 and 75")]
public int Quantity
{
get { return GetValue(() => Quantity); }
set { SetValue(() => Quantity, value); }
}
public short Status { get; set; }
}
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
Order = new OrderEntity { Name = "someOrder",OrderNumber="my order", Quantity = 23 };
Order.IsValidChanged += Order_IsValidChanged;
}
void Order_IsValidChanged()
{
if (SaveCommand != null)//RaiseCanExecuteChanged so that Save button disable on error
SaveCommand.RaiseCanExecuteChanged();
}
OrderEntity order;
public OrderEntity Order
{
get { return order; }
set { order = value; OnPropertychanged("Order"); }
}
MyCommand saveCommand;
public MyCommand SaveCommand
{
get { return saveCommand ?? (saveCommand = new MyCommand(OnSave, () => Order != null && Order.IsValid)); }
}
void OnSave(object obj)
{
//Do save stuff here
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertychanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
xaml
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="4"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="4"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="4"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Order Name" Grid.Row="0" Grid.Column="0"/>
<TextBox Text="{Binding Order.Name, ValidatesOnDataErrors=True}" Grid.Row="0" Grid.Column="2"/>
<TextBlock Text="Order Number" Grid.Row="2" Grid.Column="0"/>
<TextBox Text="{Binding Order.OrderNumber, ValidatesOnDataErrors=True}" Grid.Row="2" Grid.Column="2"/>
<TextBlock Text="Quantity" Grid.Row="4" Grid.Column="0"/>
<TextBox Text="{Binding Order.Quantity, ValidatesOnDataErrors=True}" Grid.Row="4" Grid.Column="2"/>
</Grid>
<Button Command="{Binding SaveCommand}" Content="Save"/>
</StackPanel>
You can try and test this code if it fit your needs. Currently it works for PropertyChange however we can make some changes can make it to work for bot PropertyChange or on some button click . Its 3:00 am already so i gotta sleep.
Update Validating from ViewModel using ValidationExtension
public static class ValidationExtension
{
public static void ValidateObject<T>(this T obj) where T : INotifyErrorObject
{
if (obj == null)
throw new ArgumentNullException("object to validate cannot be null");
obj.ClearErrors();//clear all errors
foreach (var item in GetProperties(obj))
{
obj.SetError(item.Name, string.Join(";", ValidateProperty(obj,item).ToArray())); //Set or remove error
}
}
public static void ValidateProperty<T>(this T obj,string propName) where T : INotifyErrorObject
{
if (obj == null || string.IsNullOrEmpty(propName))
throw new ArgumentNullException("object to validate cannot be null");
var propertyInfo = GetProperty(propName, obj);
if (propertyInfo != null)
{
obj.SetError(propertyInfo.Name, string.Join(";", ValidateProperty(obj,propertyInfo).ToArray())); //Set or remove error
}
}
public static IEnumerable<string> ValidateProperty<T>(this T obj,PropertyInfo propInfo)
{
if (obj == null || propInfo == null)
throw new ArgumentNullException("object to validate cannot be null");
var results = new List<ValidationResult>();
if (!Validator.TryValidateProperty(propInfo.GetValue(obj), new ValidationContext(obj, null, null) { MemberName = propInfo.Name }, results))
return results.Select(s => s.ErrorMessage);
return Enumerable.Empty<string>();
}
static IEnumerable<PropertyInfo> GetProperties(object obj)
{
return obj.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(ValidationAttribute), true).Length > 0).Select(p => p);
}
static PropertyInfo GetProperty(string propName, object obj)
{
return obj.GetType().GetProperties().FirstOrDefault(p =>p.Name==propName && p.GetCustomAttributes(typeof(ValidationAttribute), true).Length > 0);
}
}
EntityBase
public interface INotifyErrorObject : INotifyPropertyChanged, IDataErrorInfo
{
void SetError(string propertyName, string error);
void ClearErrors();
}
public class EntityBaseBase : INotifyErrorObject
{
Dictionary<string, string> validationErrors;
public void SetError(string propName, string error)
{
string obj=null;
if (validationErrors.TryGetValue(propName, out obj))
{
if (string.IsNullOrEmpty(error)) //Remove error
validationErrors.Remove(propName);
else if (string.CompareOrdinal(error, obj) == 0) //if error is same as previous return
return;
else
validationErrors[propName] = error; //set error
}
else if (!string.IsNullOrEmpty(error))
validationErrors.Add(propName, error);
RaisePropertyChanged(propName);
}
public void ClearErrors()
{
var properties = validationErrors.Select(s => s.Value).ToList();
validationErrors.Clear();
//Raise property changed to reflect on UI
foreach (var item in properties)
{
RaisePropertyChanged(item);
}
}
public EntityBaseBase()
{
validationErrors = new Dictionary<string, string>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null && !string.IsNullOrEmpty(propName))
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string obj=null;
if (validationErrors.TryGetValue(columnName, out obj))
return obj;
else
return null;
}
}
}
Entity
public class OrderEntity : EntityBaseBase
{
string name;
[Required(ErrorMessage = "Name is Required")]
public string Name
{
get { return name; }
set { name = value; RaisePropertyChanged("Name"); }
}
string orderNumber;
[Required(ErrorMessage = "OrderNumber is Required")]
public string OrderNumber
{
get { return orderNumber; }
set { orderNumber = value; RaisePropertyChanged("OrderNumber"); }
}
int quantity;
[Required(ErrorMessage = "Quantity is Required")]
[Range(20, 75, ErrorMessage = "Quantity must be between 20 and 75")]
public int Quantity
{
get { return quantity; }
set { quantity = value; RaisePropertyChanged("Quantity"); }
}
public short Status { get; set; }
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
Order = new OrderEntity { Name = "someOrder",OrderNumber="my order", Quantity = 23 };
}
OrderEntity order;
public OrderEntity Order
{
get { return order; }
set { order = value; OnPropertychanged("Order"); }
}
MyCommand saveCommand;
public MyCommand SaveCommand
{
get { return saveCommand ?? (saveCommand = new MyCommand(OnSave, () => Order != null)); }
}
//ValidateObject on Some button Command
void OnSave(object obj)
{
Order.ValidateObject();
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertychanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
xaml and xaml.cs is same as above. Order.ValidateObject validates the object on SaveCommand. Now if you want to Validate on PropertyChange from ViewModel then your ViewModel will have to Listen PropertyChanged event of Order and will have to call ValidateProperty of ValidationExtension like
public ViewModel()
{
Order = new OrderEntity { Name = "someOrder",OrderNumber="my order", Quantity = 23 };
Order.PropertyChanged += (o, args) => ((INotifyErrorObject)o).ValidateProperty(args.PropertyName);
}
I hope this will help.
I have an object with five properties and each of these properties has two states ("before" and "after").
How do I get the information about which properties changed their state?
The only way that I am familiar with is to get a list of all the properties (using Reflection?), then use a loop to compare each property between two objects and store information about those which were changed.
Is there a simple way to do it, perhaps using LINQ?
The interface INotifyProprtyChanged requires you to implement an event PropertyChanged. You can subscribe to this interface in the class itself and track properties for which they get called.
For example:
internal class SampleClass : INotifyPropertyChanged{
public event PropertyChangedEventHandler PropertyChanged;
private string _SampleProperty;
internal List<string> _ChangedProperties;
public SampleClass() {
this.PropertyChanged += SampleClass_PropertyChanged;
_ChangedProperties = new List<string>();
}
protected virtual void OnPropertyChanged( string propertyName ) {
PropertyChangedEventHandler handler = PropertyChanged;
if ( handler != null )
handler( this, new PropertyChangedEventArgs( propertyName ) );
}
void SampleClass_PropertyChanged( object sender, PropertyChangedEventArgs e ) {
if ( _ChangedProperties.Contains( e.PropertyName ) ) return;
_ChangedProperties.Add( e.PropertyName );
}
public string SampleProperty {
get { return _SampleProperty; }
set {
if (_SampleProperty == value )
return;
_SampleProperty = value;
OnPropertyChanged( "SampleProperty" );
}
}
}
Now you have a list of changed properties. You can work further by remembering values, etc.
I have not considered thread safety, I would not consider this sample production ready.
You can do something like this:
public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
public class PropertyChangedEventArgs : EventArgs
{
public PropertyChangedEventArgs(string propertyName, dynamic oldValue, dynamic newValue)
{
this.PropertyName = propertyName;
this.OldValue = oldValue;
this.NewValue = newValue;
}
public virtual string PropertyName { get; private set; }
public virtual dynamic OldValue { get; private set; }
public virtual dynamic NewValue { get; private set; }
}
public class PropertyClass
{
public event PropertyChangedEventHandler PropertyChanged;
private void Set<T>(string propertyName, ref T field, T value)
{
if (field.Equals(value))
return;
T oldValue = value;
field = value;
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName, oldValue, value));
}
// Properties
private string _name;
private string _message;
private bool _isMember;
public string Name
{
get { return _name; }
set { Set("Name", ref _name, value); }
}
public string Message
{
get { return _message; }
set { Set("Message", ref _message, value); }
}
public bool IsMember
{
get { return _isMember; }
set { Set("IsMember", ref _isMember, value); }
}
}
Using reflection can I get the name of the property from inside that property's accessor?
The quest :=
public string FindMyName
{
get
{
string thisPropertyName = ??
}
}
Simply: don't. You can get the compiler to tell you:
public static string WhoAmI([CallerMemberName] string caller=null)
{
return caller;
}
...
public string FindMyName
{
get
{
string thisPropertyName = WhoAmI();
//...
}
}
This is great for things like OnPropertyChanged:
protected virtual void OnPropertyChanged([CallerMemberName] string caller = null)
{
var handler = PropertyChanged;
if(handler != null) handler(this, new PropertyChangedEventArgs(caller));
}
...
public int Foo {
get { return foo; }
set { this.foo = value; OnPropertyChanged(); }
}
public string Bar {
get { return bar; }
set { this.bar = value; OnPropertyChanged(); }
}
class Program
{
static void Main()
{
var propertyName = Nameof<SampleClass>.Property(e => e.Name);
MessageBox.Show(propertyName);
}
}
public class GetPropertyNameOf<T>
{
public static string Property<TProp>(Expression<Func<T, TProp>> exp)
{
var body = exp.Body as MemberExpression;
if(body == null)
throw new ArgumentException("'exp' should be a member expression");
return body.Member.Name;
}
}
As CallerMemberName was introduced in.NET 4.5 I believe, you can use this workaround:
public static class StackHelper
{
public static string GetCurrentPropertyName()
{
StackTrace st = new StackTrace();
StackFrame sf = st.GetFrame(1);
MethodBase currentMethodName = sf.GetMethod();
return currentMethodName.Name.Replace("get_", "");
}
}
with usage:
public class SomeClass
{
public string SomeProperty
{
get
{
string s = StackHelper.GetCurrentPropertyName();
return /* ... */;
}
}
}
What is the best way to create a class with an event that fires when one of its Properties is changed? Specifically, how do you convey to any subscribers which Property was changed?
Ex:
public class ValueChangedPublisher
{
private int _prop1;
private string _prop2;
public static event ValueChangedHandler(/*some parameters?*/);
public int Prop1
{
get { return _prop1; }
set
{
if (_prop1 != value)
{
_prop1 = value;
ValueChangedHandler(/*parameters?*/);
}
}
}
public string Prop2
{
get { return _prop2; }
set
{
if (_prop2 != value)
{
_prop2 = value;
ValueChangedHandler(/*parameters?*/);
}
}
}
}
public class ValueChangedSubscriber
{
private int _prop1;
private string _prop2;
public ValueChangedSubscriber()
{
ValueChangedPublisher.ValueChanged += ValueChanged;
}
private void ValueChanged(/*parameters?*/)
{
/*how does the subscriber know which property was changed?*/
}
}
My goal is to make this as extensible as possible (e.g. I don't want a bunch of huge if/else if/switch statements lumbering around). Does anybody know of a technique to achieve what I'm looking for?
EDIT:
What I'm really looking for is how to utilize the INotifyPropertyChanged pattern on the subscriber side. I don't want to do this:
private void ValueChanged(string propertyName)
{
switch(propertyName)
{
case "Prop1":
_prop1 = _valueChangedPublisher.Prop1;
break;
case "Prop2":
_prop2 = _valueChangedPublisher.Prop2;
break;
// the more properties that are added to the publisher, the more cases I
// have to handle here :/ I don't want to have to do it this way
}
}
.NET provides the INotifyPropertyChanged interface. Inherit and implement it:
public class ValueChangedPublisher : INotifyPropertyChanged
{
private int _prop1;
private string _prop2;
public event PropertyChangedEventHandler ValueChangedHandler;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int Prop1
{
get { return _prop1; }
set
{
if (_prop1 != value)
{
_prop1 = value;
NotifyPropertyChanged();
}
}
}
public string Prop2
{
get { return _prop2; }
set
{
if (_prop2 != value)
{
_prop2 = value;
NotifyPropertyChanged();
}
}
}
}
Here is what I did to solve my problem. It's a bit large, so maybe not performant, but works for me.
Edit
See this question for performance details: C# using properties with value types with Delegate.CreateDelegate
Base Class for publishing property change stuff:
public abstract class PropertyChangePublisherBase : INotifyPropertyChanged
{
private Dictionary<string, PropertyInfo> _properties;
private bool _cacheProperties;
public bool CacheProperties
{
get { return _cacheProperties; }
set
{
_cacheProperties = value;
if (_cacheProperties && _properties == null)
_properties = new Dictionary<string, PropertyInfo>();
}
}
protected PropertyChangePublisherBase(bool cacheProperties)
{
CacheProperties = cacheProperties;
}
public bool ContainsBinding(PropertyChangedEventHandler handler)
{
if (PropertyChanged == null)
return false;
return PropertyChanged.GetInvocationList().Contains(handler);
}
public object GetPropertValue(string propertyName)
{
if (String.IsNullOrEmpty(propertyName) || String.IsNullOrWhiteSpace(propertyName))
throw new ArgumentException("Argument must be the name of a property of the current instance.", "propertyName");
return ProcessGetPropertyValue(propertyName);
}
protected virtual object ProcessGetPropertyValue(string propertyName)
{
if (_cacheProperties)
{
if (_properties.ContainsKey(propertyName))
{
return _properties[propertyName].GetValue(this, null);
}
else
{
var property = GetType().GetProperty(propertyName);
_properties.Add(propertyName, property);
return property.GetValue(this, null);
}
}
else
{
var property = GetType().GetProperty(propertyName);
return property.GetValue(this, null);
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Base Class for receiving property change stuff:
public abstract class PropertyChangeSubscriberBase
{
protected readonly string _propertyName;
protected virtual object Value { get; set; }
protected PropertyChangeSubscriberBase(string propertyName, PropertyChangePublisherBase bindingPublisher)
{
_propertyName = propertyName;
AddBinding(propertyName, this, bindingPublisher);
}
~PropertyChangeSubscriberBase()
{
RemoveBinding(_propertyName);
}
public void Unbind()
{
RemoveBinding(_propertyName);
}
#region Static Fields
private static List<string> _bindingNames = new List<string>();
private static List<PropertyChangeSubscriberBase> _subscribers = new List<PropertyChangeSubscriberBase>();
private static List<PropertyChangePublisherBase> _publishers = new List<PropertyChangePublisherBase>();
#endregion
#region Static Methods
private static void PropertyChanged(object sender, PropertyChangedEventArgs args)
{
string propertyName = args.PropertyName;
if (_bindingNames.Contains(propertyName))
{
int i = _bindingNames.IndexOf(propertyName);
var publisher = _publishers[i];
var subscriber = _subscribers[i];
subscriber.Value = publisher.GetPropertValue(propertyName);
}
}
public static void AddBinding(string propertyName, PropertyChangeSubscriberBase subscriber, PropertyChangePublisherBase publisher)
{
if (!_bindingNames.Contains(propertyName))
{
_bindingNames.Add(propertyName);
_publishers.Add(publisher);
_subscribers.Add(subscriber);
if (!publisher.ContainsBinding(PropertyChanged))
publisher.PropertyChanged += PropertyChanged;
}
}
public static void RemoveBinding(string propertyName)
{
if (_bindingNames.Contains(propertyName))
{
int i = _bindingNames.IndexOf(propertyName);
var publisher = _publishers[i];
_bindingNames.RemoveAt(i);
_publishers.RemoveAt(i);
_subscribers.RemoveAt(i);
if (!_publishers.Contains(publisher))
publisher.PropertyChanged -= PropertyChanged;
}
}
#endregion
}
Actual class to use for subscribing to property change stuff:
public sealed class PropertyChangeSubscriber<T> : PropertyChangeSubscriberBase
{
private PropertyChangePublisherBase _publisher;
public new T Value
{
get
{
if (base.Value == null)
return default(T);
if (base.Value.GetType() != typeof(T))
throw new InvalidOperationException(String.Format("Property {0} on object of type {1} does not match the type Generic type specified {2}.", _propertyName, _publisher.GetType(), typeof(T)));
return (T)base.Value;
}
set { base.Value = value; }
}
public PropertyChangeSubscriber(string propertyName, PropertyChangePublisherBase bindingPublisher)
: base(propertyName, bindingPublisher)
{
_publisher = bindingPublisher;
}
}
Here is an example of the Class with properties that you wish to be notified about:
public class ExamplePublisher: PropertyChangedPublisherBase
{
private string _id;
private bool _testBool;
public string Id
{
get { return _id; }
set
{
if (value == _id) return;
_id = value;
RaisePropertyChanged("Id");
}
}
public bool TestBool
{
get { return _testBool; }
set
{
if (value.Equals(_testBool)) return;
_testBool = value;
RaisePropertyChanged("TestBool");
}
}
}
Here is an example of the Class that will be notified when the properties in the class above change:
public class ExampleReceiver
{
public PropertyChangeSubscriber<string> Id { get; set; }
public PropertyChangeSubscriber<bool> TestBool { get; set; }
public MyExampleClass(PropertyChangePublisherBase publisher)
{
Id = new PropertyChangeSubscriber<string>("Id", publisher);
TestBool = new PropertyChangeSubscriber<bool>("TestBool", publisher);
}
}