Introduction
WPF… it looks like I need to build workarounds for everything I’m trying to do. Prior to WPF, I rarely needed INotifyPropertyChanged.
But since we need it, why not make the best of it. Since I want a base class for all my future objects, in all my future projects I want something super clean.
- Implement
INotifyPropertyChanged in the best way possible (refactor friendly and optimal performance)
- Automatically set the
IsDirty flag
- Automatic change tracking to be able to log what has been changed (like in SharePoint when you get alerts, I love that!)
- The change tracking should also be exported as text so I can write it to SQL
Of course, I don’t have to tell you that the code must always ensure a minimal amount of work. And, it should be refactor friendly etc… I want perfect code.
The code
public class NotifyPropertyChangeObject : INotifyPropertyChanged
{
private bool trackChanges = false;
public Dictionary<string, object> Changes { get; private set; }
public bool IsDirty
{
get { return Changes.Count > 0; }
set { ; }
}
public event PropertyChangedEventHandler PropertyChanged;
public NotifyPropertyChangeObject()
{
trackChanges = true;
Changes = new Dictionary<string, object>();
}
public void Reset()
{
Changes.Clear();
}
public void StartTracking()
{
trackChanges = true;
}
public void StopTracking()
{
trackChanges = false;
}
public void ApplyPropertyChange<T, F>(ref F field,
Expression<Func<T, object>> property, F value)
{
if (field == null || !field.Equals(value))
{
var propertyExpression = GetMemberExpression(property);
if (propertyExpression == null)
throw new InvalidOperationException("You must specify a property");
string propertyName = propertyExpression.Member.Name;
field = value;
if (trackChanges)
{
Changes[propertyName] = value;
NotifyPropertyChanged(propertyName);
}
}
}
public MemberExpression GetMemberExpression<T>(Expression<Func<T,
object>> expression)
{
MemberExpression memberExpression = null;
if (expression.Body.NodeType == ExpressionType.Convert)
{
var body = (UnaryExpression)expression.Body;
memberExpression = body.Operand as MemberExpression;
}
else if (expression.Body.NodeType == ExpressionType.MemberAccess)
{
memberExpression = expression.Body as MemberExpression;
}
if (memberExpression == null)
throw new ArgumentException("Not a member access",
"expression");
return memberExpression;
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public string ChangesToXml()
{
XDeclaration declaration = new XDeclaration("1.0",
Encoding.UTF8.HeaderName, String.Empty);
XElement root = new XElement("Changes");
XDocument document = new XDocument(declaration, root);
foreach (KeyValuePair<string, object> change in Changes)
root.Add(new XElement(change.Key, change.Value));
return document.Document.ToString();
}
}
Using the code
public class Person : NotifyPropertyChangeObject
{
private string firstName;
private string lastName;
private int age;
[DefaultValue("")]
public string FirstName
{
get { return firstName; }
set { ApplyPropertyChange<Person,
string>(ref firstName, o => o.FirstName, value); }
}
[DefaultValue("")]
public string LastName
{
get { return lastName; }
set { ApplyPropertyChange<Person,
string>(ref lastName, o => o.LastName, value); }
}
[DefaultValue(0)]
public int Age
{
get { return age; }
set { ApplyPropertyChange<Person, int>(ref age, o => o.Age, value); }
}
}
That’s it…
A little word about the code
- Our class
Person inherits from NotifyPropertyChangeObject.
- When we change the value of a property, the magic happens.
- We forward the private variable, the (refactor friendly) property, and the new value.
- After that, we check if the ‘new’ value matches the old one. If this is the case, nothing should happen.
- If the values do not match, we want to change the value and notify that a change has happened.
- In the meanwhile, we also add that change to the Changes dictionary.
- It’s safe to presume that if the dictionary contains changes, the object is dirty.
Finally, there are the methods Reset, StartTracking, and StopTracking. These have to do with the change tracking.
If I’m filling up a DTO in my DAL, I don’t want it to be marked as dirty. So before I start, I call StopTracking, and when I’m done, I call StartTracking.
Later on, if I save my object, I want it to be clean (not dirty), so I call the Reset method.
Finally, you could call ChangesToXml to get a string representation of all the changes. This could the be written to SQL or so...
Example application
At the top of the article, you can download a working project. I’ve also added an example for the cases where you cannot inherit from NotifyPropertyChangeObject, when you’re working with Entity Framework or LINQ to SQL for example…