Introduction
I'd guess anyone who delved a little bit deeper into WPF has learned how the class ObservableCollection
integrates so smoothly into WPF's databinding.
The problem is, once you start doing simple things like linking a ViewModel's properties to, say, a CheckBox
control, you inevitably start to wonder if you really need to implement the INotificationPropertyChanged
interface all the time by foot.
To me, one of the biggest drawbacks of implementing this interface directly is that you need to duplicate a property's name as a string, since the PropertyChangedEventArgs
parameter needs the property name. Once you start refactoring, better not forget to update all places referencing the property - if you forget one place, you'll pay that back at runtime :-)
Another drawback is the amount of code you have to write each time you add a new class to your ViewModel. Also, usually you construct a "new PropertyChangedEventArgs
" object every time any property changes. This impacts the managed heap and reduces performance.
A good summary of existing solutions to this is here (great site as well, by the way).
This article tries - to my knowledge - a new way of getting around the hassle. Using AutoPropertyChanged
, you can get around most of the mentioned problems.
The demo project as well as the Utility.AutoPropertyChangedNotification project are both created using Visual Studio 2010 and .NET 4.0. This blog entry describes a way how 2010 solution and project files can be opened in the 2008 version - never tried it myself though.
Using the Code
First of all, the class which wants to expose its properties needs to be derived from the class ImplementsPropertyChanged
. This could be a big drawback, but on the other hand, you're free to bind your properties not to your ViewModel directly but to a property of it, so you could as well leave your ViewModel as it is and contain the ImplementsPropertyChanged
object instead of deriving directly.
The first step looks like this:
public class ListItem : ImplementsPropertyChanged
{
public ListItem()
{
}
}
Okay, well this does next to nothing. ImplementsPropertyChanged
adds a PropertyChangedEventHandler
event to the class and provides a default implementation of OnPropertyChanged
.
Let's add a property to the class:
public class ListItem : ImplementsPropertyChanged
{
public ListItem()
{
}
[AutoPropertyChangedNotification]
public AutoPropertyChanged<ListItem, bool, Property0> IsChecked
{
get;
set;
}
}
This code wouldn't run yet. You need to initialize IsChecked
to get it to work:
public class ListItem : ImplementsPropertyChanged
{
public ListItem()
{
IsChecked = Prop.New(IsChecked, this);
}
[AutoPropertyChangedNotification]
public AutoPropertyChanged<ListItem, bool, Property0> IsChecked
{
get;
set;
}
}
Done - that's all you need (save calling AutoPropertyInitializer.InitializeProperties()
on startup).
The AutoPropertyChanged
has a property of its own, Value
. Accessing the property from code or XAML would thus look like this:
ListItem myItem = new ListItem();
myItem.IsChecked.Value = true;
You need to keep this in mind when using it - I didn't find a way to workaround this, since indexers need at least one parameter.
The code-behind in the XAML could look like this:
<CheckBox Content="Check this box" Name="checkBox1"
IsChecked="{Binding Path=IsChecked.Value}" />
But what's this generic parameter Property0
?
That's the not-so-nice aspect of this solution. As I mentioned above, the problem was to tell the .NET runtime to keep the static PropertyChangedEventArgs
fields different, and I didn't find a nicer approach.
But still, it works, and if you mess something up (say, using the same PropertyN
field for more than just one property), you'll learn this during the one-time initialization phase by catching a DuplicatePropertyFoundException
:-).
How does it work?
AutoPropertyChangedNotification
First of all, there is obviously this attribute AutoPropertyChangedNotification
. It's implemented without any fancy stuff, just a plain and simple attribute which can be set to properties only:
[AttributeUsage(AttributeTargets.Property)]
public sealed class AutoPropertyChangedNotificationAttribute : Attribute
{
}
Its purpose is to keep the AutoPropertyChanged
-properties (easily) identifiable by Reflection - and also a little bit to indicate to the reader that there is a bit more going on in the background.
Prop.New
The static method Prop.New
is just a simplification - usually, you'd need to create the properties like this:
IsChecked = new AutoPropertyChanged<ListItem, bool, Property0>(this);
This looks errorprone. Prop.New
alleviates this:
public static class Prop
{
public static AutoPropertyChanged<T0, T1, T2> New<T0, T1, T2>(
AutoPropertyChanged<T0, T1, T2> field, T0 hostClass)
where T0 : ImplementsPropertyChanged
{
return new AutoPropertyChanged<T0, T1, T2>(hostClass);
}
}
Possibly, this doesn't look nice. It quite surely doesn't, but it compiles to quite a few IL instructions and incurs no runtime overhead - and makes the resulting code much more readable.
AutoPropertyChanged
The generic class AutoPropertyChanged
is a little bit more involved:
public class AutoPropertyChanged<TBase, TProperty,
TPropertyName> where TBase : ImplementsPropertyChanged
{
public static PropertyChangedEventArgs propertyChanged;
public AutoPropertyChanged(TBase parent)
{
_parent = parent;
}
private TBase _parent;
private TProperty _value;
public TProperty Value
{
get
{
return _value;
}
set
{
_value = value;
_parent.OnPropertyChanged(propertyChanged);
}
}
}
There's the aforementioned static PropertyChangedEventArgs
field. As you can see, still nothing special going on here: two dynamic fields (_parent
and _value
), and the rest is plain-and-simple invoking the event if needed.
Obviously, this introduces a little bit of overhead - the _parent
reference is needed for invoking the PropertyChanged
event; we wouldn't need this though if we were implementing the IPropertyChangedNotification
interface by foot.
This is everything the runtime needs to execute. No Reflection and no lambda expressions until here - just once Reflection is needed, and this happens in the one-time initialisation phase.
AutoPropertyInitializer.InitializeProperties
This static method triggers the one-time initialisation of the properties. Most of the ugliness is contained there. Let's have a look:
PropertyInfo[] props = type.GetProperties();
foreach (PropertyInfo prop in props)
{
if (prop.GetCustomAttributes(typeof(
AutoPropertyChangedNotificationAttribute), false).Length > 0)
{
if (!prop.PropertyType.Name.StartsWith("AutoPropertyChanged"))
{
throw new AutoPropertyChangedNotificationAttributeIncorrectlyUsedException(
BuildExceptionMessage(type, prop, "The attribute " +
"[AutoPropertyChangedNotification] may only be used " +
"if the following property is an AutoPropertyChanged property.") );
}
if( prop.PropertyType.GetField("propertyChanged").GetValue(null)!=null )
{
throw new DuplicatePropertyFoundException(
BuildExceptionMessage(type, prop, "AutoPropertyChanged properties " +
"must be distinguished within one class by the last template " +
"parameter (Property0...Property19)."));
}
prop.PropertyType.GetField("propertyChanged").SetValue(null,
new System.ComponentModel.PropertyChangedEventArgs(prop.Name));
}
}
The most interesting line - aside from the exception section - is the line with the SetValue
. That's where the static field of AutoPropertyChanged
is initialized. And that's it.
The two checks directly above this line pinpoint typical error scenarios.
If you set more than one property of one class to the same PropertyN
generic parameter, you'll receive a DuplicatePropertyFoundException
. The exception message will describe which class and which property was the cause, so fixing this shouldn't be problematic.
The other situation is if the attribute [AutoPropertyChangedNotification]
was set on a property which isn't of the type AutoPropertyChanged
. In that case, a AutoPropertyChangedNotificationAttributeIncorrectlyUsedException
is thrown (thanks to God, there is auto-completion). You'll also receive a message which is detailed enough to directly point you to the offending property.
The Demo Application
The demo application is greatly inspired by this article (thanks, Philip) - which, by the way, has one of the best TreeView implementations for WPF I ever encountered. I learned quite a lot about WPF by looking closer into this project :-)
It looks like this:
On the left hand side, there are some controls which interact with the controls on the right hand side (and vice versa).
The application itself uses the MVVM view model, having one ViewModel (DemoViewModel
) and two Views (LeftView
and RightView
).
Since there's not that much code contained in it, I think it is probably best you explore the source yourself.
Summary
Pro's
- Low run-time overhead (still more than coding by foot of course):
- One additional field per property and
- One reference per property to its parent
- No dynamic creation of
PropertyChangedEventArgs
- Type safety and by this, refactoring safety
- Quite easy to use
Con's
- Property0...PropertyN required to tell different properties apart (but no runtime penalty for this)
- Needs property exposing classes to be derived from one base class (I'm quite sure you could get rid of the base class, I'm looking into this)
Change log
- 1.0: Initial public release (2010-05-24)