IanG on Tap

Ian Griffiths in Weblog Form (RSS 2.0)

Blog Navigation

April (2018)

(1 item)

August (2014)

(1 item)

July (2014)

(5 items)

April (2014)

(1 item)

March (2014)

(1 item)

January (2014)

(2 items)

November (2013)

(2 items)

July (2013)

(4 items)

April (2013)

(1 item)

February (2013)

(6 items)

September (2011)

(2 items)

November (2010)

(4 items)

September (2010)

(1 item)

August (2010)

(4 items)

July (2010)

(2 items)

September (2009)

(1 item)

June (2009)

(1 item)

April (2009)

(1 item)

November (2008)

(1 item)

October (2008)

(1 item)

September (2008)

(1 item)

July (2008)

(1 item)

June (2008)

(1 item)

May (2008)

(2 items)

April (2008)

(2 items)

March (2008)

(5 items)

January (2008)

(3 items)

December (2007)

(1 item)

November (2007)

(1 item)

October (2007)

(1 item)

September (2007)

(3 items)

August (2007)

(1 item)

July (2007)

(1 item)

June (2007)

(2 items)

May (2007)

(8 items)

April (2007)

(2 items)

March (2007)

(7 items)

February (2007)

(2 items)

January (2007)

(2 items)

November (2006)

(1 item)

October (2006)

(2 items)

September (2006)

(1 item)

June (2006)

(2 items)

May (2006)

(4 items)

April (2006)

(1 item)

March (2006)

(5 items)

January (2006)

(1 item)

December (2005)

(3 items)

November (2005)

(2 items)

October (2005)

(2 items)

September (2005)

(8 items)

August (2005)

(7 items)

June (2005)

(3 items)

May (2005)

(7 items)

April (2005)

(6 items)

March (2005)

(1 item)

February (2005)

(2 items)

January (2005)

(5 items)

December (2004)

(5 items)

November (2004)

(7 items)

October (2004)

(3 items)

September (2004)

(7 items)

August (2004)

(16 items)

July (2004)

(10 items)

June (2004)

(27 items)

May (2004)

(15 items)

April (2004)

(15 items)

March (2004)

(13 items)

February (2004)

(16 items)

January (2004)

(15 items)

Blog Home

RSS 2.0

Writing

Programming C# 5.0

Programming WPF

Other Sites

Interact Software

IPropertyChange Today: Whidbey-Style Data Binding with PropertyDescriptor

Saturday 23 October, 2004, 02:10 PM

There's a common misconception that data binding has to have something to do with databases or at least data access classes such as .NET's DataSet. In fact this is not the case - you can bind any property of a Windows Forms control to any property of any object. Consider this ridiculously simple class:

public class Person
{
    public string FirstName
    {
        get { return firstName; }
        set { firstName = value; }
    }
    private string firstName;

    public string LastName
    {
        get { return lastName; }
        set { lastName = value; }
    }
    private string lastName;
}

Windows Forms is quite happy to bind to this. Here's an excerpt from a form:


private Person p;
public Form1()
{
    InitializeComponent();

    p = new Person();
    p.FirstName = "Ian";
    p.LastName = "Griffiths";

    txtFirstName.DataBindings.Add("Text", p, "FirstName");
    txtLastName.DataBindings.Add("Text", p, "LastName");
}

When the form opens, the two text boxes show the values contained in the Person object. Moreover, if the user types new values into either of these text fields, the relevant property of the Person object will be updated automatically. You don't need anything beyond the code shown above to achieve this.

You can even bind to arrays of objects - the DataGrid will happily present an array of such objects as a table. Again, no special coding is required. This all works thanks to the magic of reflection - the data binding infrastructure can discover what's in your object and adapt its behaviour accordingly. Sadly, the design-time support in .NET version 1.1 for singular objects is not so great, but that's being addressed in VS.NET 2005 - the DataConnector system lets you use any old object as a data source in the designer.

Handling Changes

There is one snag. Although changes made by the user are propagated automatically into the object, the converse is not true. With the Person class as defined above, if some code changes one of the properties after the initial binding, the UI will not reflect that change.

There's a perfectly straightforward way to deal with this. All you have to do is add so-called property change notification events to your class. These are simple events that are raised when properties are changed, and which are named after the property they apply to. For example, the change notification event for our Person class's FirstName property would be called FirstNameChanged. These events all have to use the standard System.EventHandler delegate type.

Here's the relevant event definition for the FirstName property, along with the necessary changes to the set handler to raise the event.

public string FirstName
{
    get { return firstName; }
    set
    {
        if (firstName != value)
        {
            firstName = value;
            if (FirstNameChanged != null)
                FirstNameChanged(this, EventArgs.Empty);
        }
    }
}
private string firstName;

public event EventHandler FirstNameChanged;

This works like a charm - if your code changes these properties, the UI will now be updated automatically. This works because the data binding infrastructure goes looking for these event handlers. If you bind a control to some data source, it uses reflection to find out whether the data source has a notification change event for the property being bound to.

It's straightforward, and has worked since .NET 1.0. The main problem is it's a bit tedious to do this for all your properties.

Simplifying with IPropertyChange

.NET 2.0 (aka Whidbey) simplifies things a little by introducing a new interface called IPropertyChange, defined in the System.ComponentModel namespace. (The Avalon previews have also shown an IPropertyChange interface defined in the MSAvalon.ComponentModel namespace. Since these interfaces appear to be identical, presumably Avalon will move over to using the one in System.ComponentModel at some point.)

IPropertyChange is a very simple interface. It has just one member, an event called PropertyChanged, of type PropertyChangedEventHandler. The idea is that a data source can raise this event whenever any property changes. This simplifies things because you only need the one event, rather than one event per property, we now need just one event for the entire class.


public class Person : IPropertyChange
{
    public string FirstName
    {
        get { return firstName; }
        set
        {
            if (firstName != value)
            {
                firstName = value;
                OnPropertyChanged("FirstName");
            }
        }
    }
    private string firstName;

    public string LastName
    {
        get { return lastName; }
        set
        {
            if (lastName != value)
            {
                lastName = value;
                OnPropertyChanged("LastName");
            }
        }
    }
    private string lastName;

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
} 

So now all we need is for each property set handler to check whether the property value has really changed, and then call the helper function to raise the event. Still mildly tedious, but better than the v1.x approach.

This is all very well if you're using Whidbey, but as I write this (October 2004) that's still in beta, and will be for some time yet. So what can you do today?

Using IPropertyChange Today through TypeDescriptor

There's nothing stopping you from defining your own version of IPropertyChange of course - it really is a very simple interface. The main problem of course is that the data binding architecture in .NET 1.x won't use it. However, it's quite possible to make it do so with the .NET TypeDescriptor infrastructure.

The TypeDescriptor system is very powerful, and yet a lot of people don't seem to know about it. (Obviously not enough people have read my book. :-) ) It essentially provides a level of indirection over reflection, and is used by both Windows Forms data binding, and the design-time architecture. (And also the PropertyGrid, whether you're using that at design time or runtime.) So earlier, when I said that Windows Forms data binding is using reflection, it's not using it directly. It's using it via the TypeDescriptor system.

So what does that mean in practice? In short, it enables us to pretend our class has features that are not in fact present. For example, a class might have no properties at all, but could nonetheless tell the TypeDescriptor infrastructure to report the presence of properties. Visual Studio .NET uses this at design time to modify the set of properties that components and controls appear to have. (For example, it makes sure that the Properties panel will always show a Name property whether the component in question has one or not.)

I'm going to use this mechanism to make it look like our data source raises property change notifications even though it doesn't support the V1.x-style events. This will enable classes to use V2.0-style property change notifications on today's .NET framework. (At the time of writing, today's .NET framework is V1.1. If VS.NET 2005 has shipped by the time you're reading this, then you don't need to use this technique, because the framework already supports IPropertyChange intrinsically.)

The technique I will use is to write an adapter class for objects that use V2.0-style change notifications. This adapter will raise V1.x-compatible events. Or more accurately, it will appear to raise those kinds of events to anything that uses the TypeDescriptor infrastructure. In use it will look something like this:

p = new Person();
p.FirstName = "Ian";
p.LastName = "Griffiths";

object ds = PropertyChangeAdapter.Wrap(p);

txtFirstName.DataBindings.Add("Text", ds, "FirstName");
txtLastName.DataBindings.Add("Text", ds, "LastName");

The Person class is as above - providing a single PropertyChanged event.

Notice that we don't bind the controls directly to the underlying data source - instead we bind to the wrapper returned by PropertyChangeAdapter.Wrap method. That wrapper performs the necessary TypeDescriptor magic to pretend that it has exactly the same set of properties as the object it wraps, and to generate change notifications when the source object raises its PropertyChanged event.

So it's pretty simply to use - just wrap the source object using one line of code and bind to the result. Data binding now works exactly as you'd expect - changes typed in by the user are pushed into the object as before, but now, and changes made to the underlying object will result in the UI being updated.

If you don't like this wrapping approach, my PropertyChangeAdapter can also be used as a base class - if your data source simply derives from PropertyChangeAdapter and implements IMyPropertyChange, you don't even need the explicit wrapping call - you can just use the same binding setup code we saw at the start. (I chose to support the explicit wrapping approach as well as inheritance because .NET only permits single inheritance, and you may already have a reason to be deriving from something else.)

The Gory Details

So how does it all work, you ask? Sadly, it ends up being a bit messy, mainly because hooking into the TypeDescriptor infrastructure is a bit of an all-or-nothing proposition. We end up with a lot of boilerplate. Conceptually it's pretty simple though. The PropertyChangeAdapter class implements ICustomTypeDescriptor. The TypeDescriptor class looks for this interface - this is what enables us to fake up members. When the adapter is asked for property descriptors, it returns its own special customized PropertyDescriptor objects. These are nearly identical to the ones the TypeDescriptor would normally return, except we have modified them to raise change notifications. And that's it.

So let's look at the code. First, we need a definition of IPropertyChange. I've actually chosen to call the interface IMyPropertyChange, rather than IPropertyChange, simply because using the same name as a forthcoming V2.0 interface seems foolhardy - if I had used the same name, we'd get compiler errors when trying to compile under V2.0, because it would complain that my definition clashes with the system definition. Using a different name avoids this, although it obviously means changes are required when you do eventually want to migrate over to the V2.0 mechanism. (This code will work fine on both v1.x and v2.0, it's just that it fails to take advantage of V2.0's support for IPropertyChange.) Here it is, along with the relevant delegate and event type definitions:

public delegate void MyPropertyChangedEventHandler(object sender, MyPropertyChangedEventArgs e);

public class MyPropertyChangedEventArgs : EventArgs
{
    public MyPropertyChangedEventArgs(string propertyName)
    {
        propName = propertyName;
    }
    private readonly string propName;
    public string PropertyName { get { return propName; } }
}

public interface IMyPropertyChange
{
    event MyPropertyChangedEventHandler PropertyChanged;
}

Now we need our adapter class. This is going to derive from ICustomTypeDescriptor so that it can choose what properties it seems to expose. It also needs to provide suitable constructors for use either as a wrapper or as a base class:

public class PropertyChangeAdapter : ICustomTypeDescriptor
{
    public static object Wrap(IMyPropertyChange o)
    {
        return new PropertyChangeAdapter(o);
    }

    private IMyPropertyChange targetObject;

    // Used when data sources derive from us.
    protected PropertyChangeAdapter()
    {
        IMyPropertyChange pc = (IMyPropertyChange) this;
        Init(pc);
    }

    // Used when we are in wrapper mode.
    private PropertyChangeAdapter(IMyPropertyChange target)
    {
        Init(target);
    }

    private void Init(IMyPropertyChange target)
    {
        targetObject = target;
        targetObject.PropertyChanged += new MyPropertyChangedEventHandler(targetObject_PropertyChanged);

        GeneratePropertyDescriptorWrappers();
    }
...

The initialization code hooks the underlying data source's PropertyChanged event. Remember, this is how the underlying data source wants to raise its events. It is the job of the adapter to convert that into V1.x-style events. Before we see the actual handler function though, we need to see how this class is going to fake it. The heart of this class is the fake PropertyDescriptor objects that it will return to the TypeDescriptor. Here's where we create them:

...
private PropertyDescriptor[] wrappedProps;
private void GeneratePropertyDescriptorWrappers()
{
    PropertyDescriptorCollection originalProps = TypeDescriptor.GetProperties(targetObject, true);

    wrappedProps = new PropertyDescriptor[originalProps.Count];
    for (int i = 0; i < wrappedProps.Length; ++i)
    {
        wrappedProps[i] = PropDescriptorFactory.WrapProp(originalProps[i]);
    }
}
...

Of course we want to expose the exact same set of properties as the underlying object, we merely want to raise change notifications slightly differently. So this starts by asking the TypeDescriptor for the real property descriptors. (The significance of passing true as the final parameter there is that we are asking the TypeDescriptor to show us the real descriptors, rather than the faked up descriptors - this is important in the case where the data source has derived from us - we want the TypeDescriptor to ignore our ICustomTypeDescriptor implementation for the moment.) Then we simply call a helper function, PropDescriptorFactory.WrapProp to create the custom type descriptors. So where does that helper come from?

At this point, the code gets a little more complicated than would be ideal. All we really need is a nested class that derives from PropertyDescriptor providing the custom behaviour we require. Unfortunately, PropertyDescriptor is an abstract base class with a large number of abstract methods, and is consequently pretty tedious to derive from. Especially since we want something that is very nearly standard behaviour, with one minor event handling tweak.

Fortunately the .NET framework provides a class called SimplePropertyDescriptor that does most of the work for us. The one snag is that it's a protected nested class of TypeConverter for some reason. This obliges us to derive a class from TypeConverter in order to derive from SimplePropertyDescriptor! It's messy, but less effort than writing a PropertyDescriptor from scratch. Here it is, along with a couple of internal helper functions:

private class PropDescriptorFactory : TypeConverter
{
    // Create a custom property descriptor based on an original descriptor.
    internal static PropertyDescriptor WrapProp(PropertyDescriptor originalProperty)
    {
        return new CustomPropertyDescriptor(originalProperty);
    }

    // Cause a custom property descriptor to raise a change notification
    internal static void RaisePropChange(object target, PropertyDescriptor prop)
    {
        CustomPropertyDescriptor pd = (CustomPropertyDescriptor) prop;
        pd.RaiseChange(target);
    }

    protected class CustomPropertyDescriptor : TypeConverter.SimplePropertyDescriptor
    {
        public CustomPropertyDescriptor(PropertyDescriptor originalProperty)
            : base (originalProperty.ComponentType, originalProperty.Name, originalProperty.PropertyType)
        {
            this.originalProperty = originalProperty;
        }
        PropertyDescriptor originalProperty;

        public override object GetValue(object target)
        {
            PropertyChangeAdapter adapter = (PropertyChangeAdapter) target;
            return originalProperty.GetValue(adapter.targetObject);
        }
        public override void SetValue(object target, object value)
        {
            PropertyChangeAdapter adapter = (PropertyChangeAdapter) target;
            originalProperty.SetValue(adapter.targetObject, value);
            RaiseChange(target);
        }
        public void RaiseChange(object target)
        {
            OnValueChanged(target, EventArgs.Empty);
        }
    }
}

The SimplePropertyDescriptor is an abstract class, but unlike PropertyDescriptor, we only need to override GetValue and SetValue. For GetValue, we just defer to the original descriptor for the real property, and we defer to the underlying data source object. SetValue is almost the same, except we make sure to raise the change notification - this is done by the RaiseChange method, which simply calls through to the base class's OnValueChanged. The reason for putting this inside a separate RaiseChange method rather than calling it directly is that there's another place we want to get to be able to raise these events from, and the base class's OnValueChanged method is protected. Adding the RaiseChange method allows the helper function PropDescriptorFactory.RaisePropChange to raise the event too.

Of course we must make sure that this custom property descriptor is what the outside world sees. This is very simple - it just involves a suitable implementation of the ICustomTypeDescriptor.GetProperties method:

public PropertyDescriptorCollection GetProperties()
{
    return new PropertyDescriptorCollection(wrappedProps);
}

And now there's just one thing left to do. We need the code that handles those PropertyChanged events from the underlying data source:

private void targetObject_PropertyChanged(object sender, MyPropertyChangedEventArgs e)
{
    PropertyDescriptor pd = GetProperties()[e.PropertyName];
    PropDescriptorFactory.RaisePropChange(this, pd);
}

This simply locates the appropriate custom property descriptor for the property that just changed, and then uses the PropDescriptorFactory.RaisePropChange helper function we saw earlier to raise the change notification event. This ensures that anything that was using the TypeDescriptor infrastructure to monitor the data source (e.g. the Windows Forms data binding infrastructure) will now be aware of the change.

Actually there s one other thing left to do. There are 11 other methods on ICustomTypeDescriptor. It seems that in this particular context you'll get away with just returning null from all of them. But strictly speaking, it would be better to defer to TypeDescriptor for all of them. E.g., this kind of thing:

public TypeConverter GetConverter()
{
    return TypeDescriptor.GetConverter(targetObject, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
    return TypeDescriptor.GetEvents(targetObject, attributes, true);
}
...etc.

They're all just single-line implementations like that, and as such, not very fun to read, so I'll leave you to imagine the rest.

[Update: In Beta 2 of Whidbey, the IPropertyChange was renamed to INotifyPropertyChanged.]

Copyright © 2002-2024, Interact Software Ltd. Content by Ian Griffiths. Please direct all Web site inquiries to webmaster@interact-sw.co.uk