Showing posts with label INotifyPropertyChanged. Show all posts
Showing posts with label INotifyPropertyChanged. Show all posts

Sunday, May 23, 2010

INotifyPropertyChanged without the magic strings. A neat alternative....

Source Code: http://www.box.net/shared/5kjn0tr90c

A while back I blogged about the INotifyPropertyChanged interface, and how to properly use it. Back in that post I noted at the end that using magic strings to specify which property has changed, has its drawbacks. A little refresher as to what I'm talking about; this is the common way that the INotifyPropertyChanged interface is used:



public class MyClass : INotifyPropertyChanged
{
private int myInt;

public int MyInt
{
get
{
return this.myInt;
}
set
{
if (this.myInt != value)
{
this.myInt = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("MyInt"));
}
}
}
}

public event PropertyChangedEventHandler PropertyChanged;
}


while this does work, the problem is immediately obvious. Actually hard-coding the "MyInt" when the event gets raised is very fragile and extremely error prone. If someone changes the property name for example, and forgets to update the "magic string" in the event-raising code, all your binding goes straight to hell.

Since joining Lab49 I've been using WPF a lot, and the INotifyPropertyChanged interface is used all throughout WPF. Therefore, I've started using a new approach, and while it does require more plumbing code, I think it adds a lot of value since we get rid of the magic strings, and get compile time checking. Here's what I came up with:



public class NotifyProperty<TContaining, TProperty>
{
#region Members

private string propertyName;
private TProperty backingStore;
private Comparison<TProperty> comparer;

#endregion

#region Constructors

public NotifyProperty(Expression<Func<TContaining, TProperty>> expression)
: this(expression, Comparer<TProperty>.Default.ToComparison())
{

}

public NotifyProperty(Expression<Func<TContaining, TProperty>> expression, IComparer<TProperty> comparer)
: this(expression, comparer.ToComparison())
{

}

public NotifyProperty(Expression<Func<TContaining, TProperty>> expression, Comparison<TProperty> comparer)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}

if (comparer == null)
{
comparer = Comparer<TProperty>.Default.ToComparison();
}

this.comparer = comparer;

var memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
{
throw new InvalidOperationException("Only properties can be used.");
}

this.propertyName = memberExpression.Member.Name;
}

#endregion

#region Methods

public TProperty GetValue()
{
return this.backingStore;
}

public void SetValue(TContaining containing, TProperty value, PropertyChangedEventHandler propertyChanged)
{
if (this.IsNewValue(value))
{
this.backingStore = value;
this.RaisePropertyChanged(containing, propertyChanged);
}
}

private void RaisePropertyChanged(TContaining containing, PropertyChangedEventHandler propertyChanged)
{
if (propertyChanged != null)
{
propertyChanged(containing, new PropertyChangedEventArgs(this.propertyName));
}
}

private bool IsNewValue(TProperty newValue)
{
if (this.comparer == null) { return true; }

return this.comparer(this.backingStore, newValue) != 0;
}

#endregion
}


And this is how you would use it:



public class MyClass : INotifyPropertyChanged
{
private NotifyProperty<MyClass,int> myInt;

public MyClass()
{
this.myInt = new NotifyProperty<MyClass, int>(m => m.MyInt);
}

public int MyInt
{
get
{
return this.myInt.GetValue();
}
set
{
this.myInt.SetValue(this, value, this.PropertyChanged);
}
}

public event PropertyChangedEventHandler PropertyChanged;
}


Using Expressions, we can take advantage of the fact that we can pass a Func to the constructor of our NotifyProperty class, and still get compile time checking.

I'll briefly run through the code and explain what's happening. The first and most important part to understand is the Expression stuff. While I'm no expert on Expressions, I've used them in the past for this purpose. The compiler allows you to pass a Func wherever an Expression of type Func is expected. Under the covers, the compiler converts it from a Func to an Expression, but at the same time, it extracts some important Metadata about the Func, including the name, which we can then grab through the Name property.

The other piece here is all the Comparison stuff. This isn't really needed, but it just follows the same pattern used when raising the PropertyChanged event. You only want to raise the event if the underlying value actually changed. Therefore, I fleshed it out and overloaded the constructors to allow for different methods of comparison to be passed in. To make that possible, I added this extension method:



public static Comparison<T> ToComparison<T>(this IComparer<T> comparer)
{
return new Comparison<T>(comparer.Compare);
}


This just converts an instance of IComparer<T> to an instance of Comparison so that you can either pass in an interface implementation or a delegate.

Now, like I said earlier, this does require more plumbing code and writing out a property does look more cumbersome. I'll admit that it's a tradeoff, but I personally try to avoid magic strings like the plague, which is why I think this is in fact a much better approach. I added a link to the top of the page with the source code. It contains the class along with some unit tests. Drop me a line and let me know what you think. Until then, Good luck and Happy coding!

Monday, June 15, 2009

INotifyPropertyChanged - How to and when to?

Source Code: http://www.box.net/shared/nx8uj1rm1b

Databinding has always been fascinating to me; at first it really all looks like magic, how controls just track changes, and keep everything in sync. So much so, that very often I try to use it as little as possible, because I don't like using things I don't fully understand how they work. However, it does often save you from a lot of extra code, so I think it's very useful to explore it a bit, in particular the INotifyPropertyChanged interface.

Let's first start with a quick demo. Say you have a class, let's call is MyClass:



public class MyClass
{
public int MyProperty { get; set; }
}


Now let's say we have a Windows Form that has a DataGridView that we're using to bind to a BindingList of this type:



BindingList<MyClass> bindingList = new BindingList<MyClass>();
private void Bind()
{
bindingList.Add(new MyClass { MyProperty = 10 });
bindingList.Add(new MyClass { MyProperty = 20 });
bindingList.Add(new MyClass { MyProperty = 30 });
bindingList.Add(new MyClass { MyProperty = 40 });
this.dataGridView1.DataSource = bindingList;
}


This will work, and you'll have a DataGridView with one column called MyProperty with the rows 10,20,30,40. Very nice. Suppose however, that somewhere in the application, the contents of this list change (new values from database, or user input updated data, whatever..). You'll notice, that if your code applies changes directly to the BindingList, it will NOT reflect in the DataGridView:



public void UpdateList()
{
foreach (MyClass mc in this.bindingList)
{
mc.MyProperty = 100;
}
}


Just some simple code that changes all the MyProperty's to 100. You'll notice that these changes will NOT be reflected in the DataGridView. Well, the question becomes, how then do we have the changes reflected in the DataGridView? You COULD set the DataGridView's datasource to null and then rebind, but that's hacky and clumsy.

Enter the INotifyPropertyChanged interface. The interface has one property on it, and it's actually an event:



public interface INotifyPropertyChanged
{
// Summary:
// Occurs when a property value changes.
event PropertyChangedEventHandler PropertyChanged;
}


Looks quite simple, let's implement this now properly with a Person class. I've attached the source code to this post so you can download it and see for yourself. Here's the entire Person class:



public class Person : INotifyPropertyChanged
{

private string firstName;
private string lastName;
private int age;

public Person(string firstName, string lastName, int age)
{
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}

public Person()
{

}

protected virtual void OnPropretyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}


public string FirstName
{
get
{
return this.firstName;
}
set
{
if (this.firstName != value)
{
this.firstName = value;
this.OnPropretyChanged("FirstName");
}
}
}

public string LastName
{
get
{
return this.lastName;
}
set
{
if (this.lastName != value)
{
this.lastName = value;
this.OnPropretyChanged("LastName");
}
}
}

public int Age
{
get
{
return this.age;
}
set
{
if (this.age != value)
{
this.age = value;
this.OnPropretyChanged("Age");
}
}
}

#region INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

#endregion
}


The first thing you'll notice is that I implemented INotifyPropertyChanged. Then, in each setter of the properties, I call a method called "OnPRopertyChanged" which actually raises the event. The only downside of this, is that you can't use auto properties because the setters need to have logic. Also, since we'll be raising an event every time the value changes, it's worth putting in the extra if check:


if (this.firstName != value)
{
this.firstName = value;
this.OnPropretyChanged("FirstName");
}


just to make sure that the value actually changed.

I then created a simple Form that has a DataGridView that's bound to a BindingList<Person>. It also has a button, that when clicked, adds 5 years to everyones age.



public partial class Form1 : Form
{

private BindingList<Person> people;

public Form1()
{
InitializeComponent();
this.people = new BindingList<Person>();
this.PopulatePeople();
this.dataGridView1.DataSource = people;
}

private void PopulatePeople()
{
this.people.Add(new Person("Alex", "Friedman", 27));
this.people.Add(new Person("Jack", "Bauer", 45));
this.people.Add(new Person("Tony", "Almeda", 39));
this.people.Add(new Person("Chloe", "O'Brien", 37));
this.people.Add(new Person("Bill", "Buchanan", 50));
}

private void ChangeAges()
{
foreach (Person p in this.people)
{
p.Age += 5;
}
}

private void buttonChange_Click(object sender, EventArgs e)
{
this.ChangeAges();
}
}


If you run this, you'll notice that DataGridView does in fact get updated, even though I just manipulated the underlying list. I never actually touch the DataGridView itself. Internally, the BindingList checks to see if the class of type T (remember, BindingList is generic) implements INotfiyPropertyChanged. If it does, it hooks into that event, and when the event is raised, it updates itself.

The one thing to be careful is that when raising this event, you actually hardcode the name of the property. Personally, I wish there was a better way, but anything I've seen online so far had some serious overhead (like using the StackFrame to figure out which property is changing) so if someone knows of a better way, please let me know!

UPDATE: I recently blogged about a better approach to dealing with the INotifyPropertyChanged event that takes care of the hardcoded strings issue. Check it out here.