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…