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.