Databinding Magic with Dynamic PropertyDescriptors and Anonymous Methods

At the end of my post, Implementing ITypedList for Virtual Properties, I mentioned the possibility of having one, very-versatile, subclass of PropertyDescriptor that could handle all of your virtual property databinding needs. I was implementing ITypedList again and I didn’t want to write yet another set of type-specific PropertyDescriptor subclasses so I knocked this out. Everything went really well until I was creating anonymous methods inside of a foreach loop. Be sure to see the caveat below on this before you try to use this class. Without further ado, here is DynamicPropertyDescriptor (props to manoli.net for providing the code formatter I used in this post):

public delegate object DynamicGetValue(object component);
public delegate void DynamicSetValue(object component, object newValue);

public class DynamicPropertyDescriptor : PropertyDescriptor {
    protected Type _componentType;
    protected Type _propertyType;
    protected DynamicGetValue _getDelegate;
    protected DynamicSetValue _setDelegate;

    public DynamicPropertyDescriptor(Type componentType, string name, Type propertyType, 
        DynamicGetValue getDelegate, DynamicSetValue setDelegate)
        :
        base(name, null) {
        _componentType = componentType;
        _propertyType = propertyType;
        _getDelegate = getDelegate;
        _setDelegate = setDelegate;
    }

    public override bool CanResetValue(object component) {
        return false;
    }

    public override Type ComponentType {
        get { return _componentType; }
    }

    public override object GetValue(object component) {
        return _getDelegate(component);        
    }

    public override bool IsReadOnly {
        get { return _setDelegate == null; }
    }

    public override Type PropertyType {
        get { return _propertyType; }
    }

    public override void ResetValue(object component) {
    }

    public override void SetValue(object component, object value) {
        _setDelegate(component, value);
    }

    public override bool ShouldSerializeValue(object component) {
        return true;
    }
}

I will reuse the Person and related classes from the prior post. This time, I will demonstrate an actual crosstab with editable values by adding an “hours worked” array. I will also include a running solution at the end.

Here’s the upated Person Class:

public class Person {
    protected string _firstName;
    protected string _lastName;
    protected string _midName;
    protected DateTime _dob;
    protected decimal[] _hoursWorked;

    public Person(string firstName, string lastName, string midName, DateTime dob) {
        _firstName = firstName;
        _lastName = lastName;
        _midName = midName;
        _dob = dob;
        _hoursWorked = new decimal[7];
    }
    public string FirstName {
        get { return _firstName; }
    }
    public string LastName {
        get { return _lastName; }
    }
    public string MiddleName {
        get { return _midName; }
    }
    public DateTime DateOfBirth {
        get { return _dob; }
    }
    public decimal this[int day] {
        get { return _hoursWorked[day]; }
        set { _hoursWorked[day] = value; }
    }
}

The only change is the addition of the _hoursWorked member and the indexor property to allow getting and setting the values of _hoursWorked. Now to the good stuff. The method I demonstrated in the last post required me to create new subclasses of PropertyDescriptor for each type of component I wanted to work with. DynamicPropertyDescriptor uses anonymous methods to provide a reusable implementation of the PropertyDescriptor class. Here’s what the new view code looks like:

public class PersonFullNameAgeHoursView : IPersonViewBuilder {
    public PropertyDescriptorCollection GetView() {
        List<PropertyDescriptor> props = new List<PropertyDescriptor>();

        props.Add(new DynamicPropertyDescriptor(
            typeof(Person),
            "FullName",
            typeof(string),
            delegate(object p) { 
                return ((Person)p).FirstName + " " 
                    + ((Person)p).MiddleName + " " 
                    + ((Person)p).LastName; },
            null
        ));

        props.Add(new DynamicPropertyDescriptor(
            typeof(Person),
            "Age",
            typeof(string),
            delegate(object p) {
                return DateTime.Today.Year - ((Person)p).DateOfBirth.Year;
            },
            null
        ));

        for ( int day = 0; day < 7; day++ ) {
            // without this variable declared inside the loop, 
            // the anon methods won't work correctly
            // they will all reference the value of the 
            // foreach loop variables as set on the last iteration
            int bindableDay = day;
            props.Add(new DynamicPropertyDescriptor(
                typeof(Person),
                Enum.GetName(typeof(DayOfWeek), day),
                typeof(Decimal),
                delegate(object p) { 
                    return ((Person)p)[bindableDay];
                },
                delegate(object p, object newPropVal) { 
                    ((Person)p)[bindableDay] = (decimal) newPropVal; 
                }
            ));
        }


        PropertyDescriptor[] propArray = new PropertyDescriptor[props.Count];
        props.CopyTo(propArray);
        return new PropertyDescriptorCollection(propArray);
    }
}

Pay very close attention to the bindableDay variable. This whole thing would have been a piece of cake if not for that little issue. If you want to have a deep understanding of the issue, take a look at this post that explains how anonymous methods are implemented and this post that details the issue of generating anonymous methods that reference local variables in a loop.

Here’s the result of binding PersonCollection to a DataGridview (note that I typed the hour values into the grid, they did not come from the source):

Demo Output

Click here for a complete VS2005 solution.

I haven’t done any performance comparisons between using this method versus type specific subclasses other than to see that was “fast enough” for what I was using it for. However, in performance critical scenarios, you may want to go with the other method.

Implementing ITypedList for Virtual Properties

ITypedList allows you to create “views” of objects for databinding purposes without actually having to modify the object(s) underlying the view. You can prevent public properties from show up by using attributes. However, attributes won’t allow you to present methods as properties or crosstab an array of values. Prior to developing this technique, I would implement a method that returned a DataTable that I used for databinding. This works OK for read-only displays but you still end up duplicating data in memory at least for a small time. Once you allow updating, you must keep both the view (datatable) and the model (object collection) in memory and synchronized. At this point, implementing ITypedList becomes a very elegant, effecient solution. (see a later post for more on this topic).

In the .net 2.0 world, you should be using a generic collection class as the base for your underlying data. So the first question is, since List<X> is a strongly typed list, will databinding still recognize the ITypedList implementation? Fortunately, the answer is yes. The databinding system in .net 2.0 windows forms will display class defined as

public class PersonCollection : List<Person>, ITypedList {...}

using the ITypedList implementation and instead of the default view consisting of all of the Person public properties.

On the surface, implementing ITypedList looks easy — you only have to write two methods

PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors);

string GetListName(PropertyDescriptor[] listAccessors);

In fact, you only have to write one method — GetItemProperties. GetListName isn’t used by the .net 2.0 DataGridView. Wow, just one method. How hard can that be? Unfortunately, the documentation is less than stellar and the steps required are less than obvious. First, you can’t just create an empty PropertyDescriptorCollection and start adding items to it. No, that would make too much sense. Instead, you have to create a PropertyDescription array and pass that to the PropertyDescriptorCollection constructor.

Now that you’ve figured out how to create a PropertyDescriptorCollection, you must find a way to create a PropertyDescriptor. PropertyDescriptor is an abstract class. If you search the documentation, you won’t find any obviously useful concrete subclasses, either. Since PropertyDescriptor is not sealed, you can subclass it. When you subclass it, you find that there is really only one useful base constructor which takes a property name and an attribute array as a parameter. However, you still have 8 members that you must implement. I was hoping for a constructor that would take some of these as parameters but you really do have to implement them all yourself.

In the paragraphs that follow, I will step you through implementing a List<Person> that implements ITypedList. I will define an interface for buildng a Person view. I will implement that interface and pass an instance of that implmentation in to the PersonCollection class using the Strategy design pattern. Finally, in order to create the “virtual” properties, I will also implement a concrete subclass of PropertyDescriptor.

Below is the code for a Person class. There’s nothing special about it but we need something to work with to demonstrate ITypedList.

public class Person {
 protected string _firstName;
 protected string _lastName;
 protected string _midName;
 protected DateTime _dob;
 
 public Person(string firstName, string lastName, string midName, DateTime dob) {
  _firstName = firstName;
  _lastName = lastName;
  _midName = midName;
  _dob = dob;
 }
 
 public string FirstName {
  get { return _firstName; }
 }

 public string LastName {
  get { return _lastName; }
 }

 public string MiddleName {
  get { return _midName; }
 }

 public DateTime DateOfBirth {
  get { return _dob; }
 }
}

Here’s the complete PersonCollection class definition — all it does is implement ITypedList by deferring all of the work to another class (note that using System.ComponentModel is assumed in all of the following classes):

public class PersonCollection : List<Person>, ITypedList {
 protected IPersonViewBuilder _viewBuilder;

 public PersonCollection(IPersonViewBuilder viewBuilder) {
  _viewBuilder = viewBuilder;
 }

 #region ITypedList Members

 protected PropertyDescriptorCollection _props;

 public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) {
  if (_props == null) {
   _props = _viewBuilder.GetView();
  }
  return _props;
 }

 public string GetListName(PropertyDescriptor[] listAccessors) {
  return ""; // was used by 1.1 datagrid
 }

 #endregion
}

I am using the Strategy pattern to allow specification of the view builder. If you needed to provide multiple, simultanous views of the same underlying collection, you would need to use a different design. I have defined an interface to facilitate the Strategy pattern.

public interface IPersonViewBuilder {
 PropertyDescriptorCollection GetView();
}

The next two classes are where are the real work happens with respect to creating the view. In this case we know how many properties we are exposing up front. In other cases, the number of “virtual” properties exposed could be dynamic and the code below demonstrates that by using a List<> instead of a predefined array. Yes, I know that Age isn’t accurate but I kept it simple for demonstration purposes.

public class PersonFullNameAgeView : IPersonViewBuilder {
 public PropertyDescriptorCollection GetView() {
  List<PropertyDescriptor> props = new List<PropertyDescriptor>();
  PersonMethodDelegate del = delegate(Person p) 
   { return p.FirstName + " " + p.MiddleName + " " + p.LastName; };
  props.Add(new PersonMethodDescriptor("FullName", del, typeof(string)));
  del = delegate(Person p) { return DateTime.Today.Year - p.DateOfBirth.Year; };
  props.Add(new PersonMethodDescriptor("Age", del, typeof(int)));
  PropertyDescriptor[] propArray = new PropertyDescriptor[props.Count];
  props.CopyTo(propArray);
  return new PropertyDescriptorCollection(propArray);
 }
}

public delegate object PersonMethodDelegate(Person person);

public class PersonMethodDescriptor : PropertyDescriptor {
 protected PersonMethodDelegate _method;
 protected Type _methodReturnType;

 public PersonMethodDescriptor(string name, PersonMethodDelegate method,
  Type methodReturnType)
  : base(name, null) {
  _method = method;
  _methodReturnType = methodReturnType;
 }

 public override object GetValue(object component) {
  Person p = (Person)component;
  return _method(p);
 }

 public override Type ComponentType {
  get { return typeof(Person); }
 }

 public override Type PropertyType {
  get { return _methodReturnType; }
 }

 public override bool CanResetValue(object component) {
  return false;
 }
 
 public override void ResetValue(object component) { }
 
 public override bool IsReadOnly {
  get { return true; }
 }

 public override void SetValue(object component, object value) { }

 public override bool ShouldSerializeValue(object component) {
  return false;
 }
}

Finally, we have the actual code to create the collection, set the view, and bind it to a datagridview.

 PersonCollection pc = new PersonCollection(new PersonFullNameAgeView());
 pc.Add(new Person("John", "Opincar, Jr", "Thomas", new DateTime(1968, 8, 12)));
 pc.Add(new Person("Abraham", "Lincoln", "X", new DateTime(1825, 1, 1)));
 pc.Add(new Person("John", "Smith", "David", new DateTime(1985, 2, 15)));
 this.dataGridView1.DataSource = pc;

Here’s a screenshot of what is displayed in the grid:

itypedlistdemo.jpg

Some final thoughts. There are many different ways to implement subclasses of PropertyDescriptor. I have demonstrated one and it only provides readonly virtual properties based on methods. You can provide read-write virtual properties that are based on properties or methods. You could write a very specific subclass that takes the component and an index to provide read-write access to an indexed property. You could write very generic subclass that takes a member name, component type, getter and setter delegates that would serve all your needs (I ended up doing this myself) at the cost of being a little harder to follow and probably less effecient.

Bear in mind that this sample code is for demonstration purposes only. Obviously, you could have just implemented FullName and Age as public properties on Person. From a purist standpoint, I would argue that making changes to the Person class solely for display purposes not necessarily a good thing. However, if you have compound or nested data that you wish to display as a single row in a crosstab, this technique is invaluable.

Follow

Get every new post delivered to your Inbox.