Introduction
Using databinding in WPF obliges to use and implement the INotifyPropertyChanged
interface. This task is usually boring, and even unsafe since properties have to be specified by name via simple string
s. The property by string
issue can be solved using lambda expression, so we can find some helper method that allows us some syntax sugar like...
NotifyPropertyChanged((NotifyPropertyChanged(()=>MyProperty);
...but we need to derive our ViewModel
from some special class, and in any case we have to write some boring code every time we set a property. In order to avoid writing such things, we can use AOP to intercept property setters, but this usually involves some 3rd party library to add to our package, and sometimes this is an issue. In this article, we will see a solution to this problem using AOP, but so circumstantial that we will need just a few classes to embed in our solution in order to solve the problem.
Background
Using INotifyPropertyChanged
the plain vanilla way:
public class Customer :INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set {
if(value!=name)
OnPropertyChanged("Name");
name = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
With the AutoNotifyPropertyChange
helper class, we can reduce the code above to this:
public class Customer
{
public virtual string Name
}
Simpler, isn't it?
In order to have the job done with the simple library we are talking about, the following requirements are mandatory to accomplish the result:
ViewModel
class has to be public
- Property to notify changes on must be
public
and virtual
Notification will occur only when property is changed through the public
interface, proxy does not know anything about internal backing fields.
If a custom implementation of INotifyPropertyChanged
is needed, we can pass to the proxy generator a class implementing INotifyPropertyChanged
, but in this case we have to ensure the existence of a public
or protected
function called OnPropertyChanged(string propertyName)
firing the event. The proxy generator assumes the function name and supposes the implementation works by firing the PropertyChange
event in a proper and consistent way. Below is a model class that already implements INotifyPropertyChanged
:
public class ModelSample:INotifyPropertyChanged
{
public virtual int MyProperty1 { get; set; }
public virtual double MyProperty2 { get; set; }
public virtual DateTime MyProperty3 { get; set; }
public float NoNotify { get; set; }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged(string property)
{
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
The code below derives a class from Customer
and wires the code to fire the PropertyChanged
event every time we set a property value with a different value. Please note than the TypeFactory
class is in a certain sense fault tolerant. If the interface is implemented, implementing the OnPropertyChanged(string propName)
is mandatory. We can leverage fault tolerance in order to avoid notification for certain properties: just declare them as non virtual.
Customer model = Activator.CreateInstance(
TypeFactory.AutoNotifier<Customer>()
);
How It Works
The AutoNotifyPropertyChange.TypeFactory
class uses CodeDom to internally generate a subclass of the model and wire the code in the property setter. Class is internally compiled and returned as a type.
Let's see this example (POCO) class:
public class ClockViewModel
{
public virtual int Hour { get; set; }
public virtual int Minute { get; set; }
public virtual int Second { get; set; }
public virtual int Millisecond { get; set; }
public virtual int Centiseconds { get { return Millisecond / 10; } }
}
and see the autogenerated class (you will never have to use this code, it is just to show what happens behind the scenes):
namespace @__autonotifypropertychanged
{
internal class @__autonotifyClockViewModel : Autonotify.Demo.ClockViewModel,
System.ComponentModel.INotifyPropertyChanged
{
public override int Hour
{
get
{
return base.Hour;
}
set
{
if ((false == base.Hour.Equals(value)))
{
base.Hour = value;
this.OnPropertyChanged("Hour");
}
}
}
public override int Minute
{
get
{
return base.Minute;
}
set
{
if ((false == base.Minute.Equals(value)))
{
base.Minute = value;
this.OnPropertyChanged("Minute");
}
}
}
public override int Second
{
get
{
return base.Second;
}
set
{
if ((false == base.Second.Equals(value)))
{
base.Second = value;
this.OnPropertyChanged("Second");
}
}
}
public override int Millisecond
{
get
{
return base.Millisecond;
}
set
{
if ((false == base.Millisecond.Equals(value)))
{
base.Millisecond = value;
this.OnPropertyChanged("Millisecond");
this.OnPropertyChanged("Centiseconds");
}
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
System.ComponentModel.PropertyChangedEventHandler handler;
handler = this.PropertyChanged;
if ((null != handler))
{
handler(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
}
All the plumbing is done and you obtain a proxy implementing INotifyPropertyChange
. You probably noted that the property "Centiseconds
" is a read only calculated property, but in the generated code we see the event raising in the correct place: changing the milliseconds force a change to the Centiseconds
property too. This is because internally the proxy generator investigates the code for every property getter in order to see if they use other property value, if so the property is considered to be dependent, proper events are fired. The inflector does not look at the code behavior, it simply assumes that if a getter is used then this will influence the result (this is correct while we are not using a property value inside a getter to produce some side effect, in this case a change event will be fired without reason, but in such a situation this will probably not be an issue).
To create the proxy, just write this:
var obj = Activator.CreateInstance(
AutoNotifyPropertyChange.TypeFactory.AutoNotifier<ClockViewModel>() );
Autogenerated classes are cached internally, so asking many times for the same type does not require the code for the proxy to recreate and rebuild.
Points of Interest
The code is compiled as a DLL, but the classes for the proxy are just a few and can be merged into an existing solution code without any pain, there are no additional dependencies. In the sample application provided, a binding with a simple view is shown, using the ClockViewModel
.
History
- 30th December, 2010: Initial version
- 4th January, 2011: Article updated