Simulating object properties with ITypedList and custom PropertyDescriptors

In newsgroup post in the Developer Express support newsgroup for their XPO product, I was recently asked if it was possible to show information that’s really stored in a one-to-many relationship as additional columns on the main object. Actually, .NET makes this possible using an implementation of the ITypedList interface together with a custom property descriptor – in this way, arbitrary additional properties on an object can be “simulated”, regardless of the real source of the data.

First, let me explain the problem in the sample case (a download link is at the bottom of the post) a little further. Let’s assume you have a class (MasterRecord) that stores a collection of detail values (DetailValue) objects in a typed collection. The following diagram shows the structure, just as an overview of the classes involved:

MasterRecord UML

Now, for presentation purposes, the data is to be represented in a grid. But the user doesn’t want to have a long hierarchical structure where all the detail values are listed underneath the corresponding master record. Instead, the detail values are supposed to be represented by additional columns in the grid row of the master record. Or maybe you know exactly which detail values (with which names, in this example) will be there, so you actually know which and how many columns you will need.

Consider you have the following data:

  • MasterRecord: “TestRecord1”
    • DetailValue: Name: “TheAnswer”, Value: 42
    • DetailValue: Name: “TestRecord1Value”, Value: 101
  • MasterRecord: “Another test record”
    • DetailValue: Name: “TheAnswer”, Value: 52
    • DetailValue: Name: “another value”, Value: 824

This data should result in a grid view like this:

MasterRecord TheAnswer TestRecord1Value another value
TestRecord1 42 101  
Another test record 52   824

The custom property descriptor

The custom property descriptor is the first step of the implementation. The concept behind property descriptors is simple: Every time a property of an object is accessed by the .NET data binding mechanisms, a property descriptor for that property is used. The property descriptor is the class that has the real knowledge of how exactly the data for the property is to be accessed. This is true for every bound property, even the “normal” ones, for these a ReflectionPropertyDescriptor is used internally.

Now say we have created a property descriptor instance for the property called “TheAnswer” from the example. What a property descriptor would have to do to get the value for this “virtual” property from a MasterRecord instance is this:

  1. Iterate over the MasterRecord’s detail values collection and see if there’s an entry called “TheAnswer”.
  2. If such an entry is found, return its value.
  3. If such an entry is not found, possibly return some kind of placeholder for an “empty value”.

Here’s the code from the sample property descriptor that does just that:

public override object GetValue(object component) {
  MasterRecord mr = (MasterRecord) component;

  foreach(DetailValue dv in mr.DetailValues)
    if (dv.Name == this.Name)
      return dv.Value;

  return 0;
}

Obviously, the descriptor could get the needed value from any arbitrary source instead of from the detail value collection. For example, I have used a property descriptor in a project that would get values from specific interfaces if the object implemented it, falling back to defaults if it didn’t. If you’ve ever had the problem that a collection typed for a base class would always show only the properties of the base class in data binding, this is the solution: Just create a property descriptor that will check for the real type of the given object and return properties from derived types if necessary. Note, though: as you’ll see in the next paragraph, all objects in a collection are still expected to have the same set of properties (what would the grid look like if that wasn’t a requirement?), so your descriptor must be able to deal with objects that don’t have the expected data.

Implementing ITypedList

The second part of the implementation is the ITypedList implementation. This is the point where we tell the binding mechanisms which properties it should access for our given collection. It’s an important fact that this interface is implemented on the collection: all objects in the collection are expected to have the same set of properties.

The interesting method of that interface is the GetItemProperties method, which returns a PropertyDescriptorCollection. In many cases, I’ve found that it makes sense to calculate the set of property descriptors once (or at least at some specific point) and store it in a static variable for the collection type, unless there are reasons to want to recalculate it every time it’s being queried. This is the method from the sample that does the calculation:

public void CalculatePropertyDescriptors() {
  PropertyDescriptorCollection origProperties = 
    TypeDescriptor.GetProperties(typeof(MasterRecord));
  ArrayList properties = new ArrayList();

  foreach(PropertyDescriptor desc in origProperties)
    if (!typeof(ICollection).IsAssignableFrom(desc.PropertyType))
      properties.Add(desc);

  ArrayList handledNames = new ArrayList();

  foreach(MasterRecord mr in this)
    foreach(DetailValue dv in mr.DetailValues)
      if (!handledNames.Contains(dv.Name)) {
        properties.Add(new DetailValuePropertyDescriptor(dv.Name));
        handledNames.Add(dv.Name);
      }

  propertyDescriptors = new PropertyDescriptorCollection((PropertyDescriptor[]) 
    properties.ToArray(typeof(PropertyDescriptor)));
}

There are mainly two things happening here, the whole process blurred a bit by the handling code needed for the PropertyDescriptorCollection:

  1. After getting the default property descriptors for a MasterRecord type object, they are filtered while being transferred into our own temporary array. Descriptors for collection types are left out because we don’t want to show the field for the collection in the grid. This is of course an optional step.
  2. For every unique name of a detail value in any of our master records, one instance of our own custom property descriptor is created and also added to the temporary array. This step establishes the “virtual” properties, together with their names, for which our code will be called automatically.

The result

Data is initialized in the sample with the following code, the descriptors calculated and the datasource bound:

MasterRecordCollection coll = new MasterRecordCollection();
MasterRecord mr = new MasterRecord("TestRecord 1");
mr.DetailValues.Add(new DetailValue("TheAnswer", 42));
mr.DetailValues.Add(new DetailValue("TestRecord1Value", 101));
coll.Add(mr);

mr = new MasterRecord("Another test record");
mr.DetailValues.Add(new DetailValue("TheAnswer", 52));
mr.DetailValues.Add(new DetailValue("another Value", 824));
coll.Add(mr);

coll.CalculatePropertyDescriptors();
dataGrid1.DataSource = coll;

This results in the following output:

The result
Download

The sample that has been mentioned in this post can be downloaded here. 

6 Comments on Simulating object properties with ITypedList and custom PropertyDescriptors

  1. John Eyles // March 3, 2005 at 11:44 am // Reply

    Excellent article that shows how to create “virtual” properties and return a value whenbound to a control such as a grid. The TypeDescriptor.GetProperties(MyType) line of codereturns an array of property descriptors for “MyType”- but it seems that it does not alwaysreturn the property descriptors in the same order as they appear in the class. Consequently, the properties can display in the grid out of order. Any ideason why this is or how to get around it?

    Like

  2. Hm… why this is, that’s an interesting question. Why would you assume that the properties should be in any specific order? Anyhow, I have some code that solves this problem and I’m going to put up another post today showing how it can be done. Thanks for asking this!

    Like

  3. Hi Oliver,I implemented ITypedList in a custom collection as you described but although it works OK with WindowsForms GridView control, the ASP.NET DataGrid control ignores it. The GetItemProperties() method does not get called.Is this a problem with the DataGrid control?Thanks!Orlin

    Like

  4. Hi Orlin – yes, last time I looked, the ASP.NET DataGrid wasn’t able to use the ITypedList interface. I don’t do ASP.NET all the time, so I haven’t had a close look into what .NET 2 may change in this regard, though.

    Like

  5. Hi Oliver, thanks a million for this article. It solved my DataGrid binding problem. Actually, my problem was a bit different. My data source is a custom IList, where the rows are arrays of structs, and where the number of columns is variable. However, it was easy to adapt your source code (essentially the implementations of PropertyDescriptor and ITypedList) to this situation.

    Thanks again – good work!
    Sven Boris

    Like

  6. THANKS SO MUCH. I wasn’t sure about how to implement this on my solution, but your examples worked like a charm! YOU THA MAN, OLIVER!!!

    Like

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s