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.

About these ads

22 Responses to “Implementing ITypedList for Virtual Properties”

  1. John Opincar’s Blue Corner Dynamic PropertyDescriptors with Anonymous Methods « Says:

    [...] with Anonymous Methods May 12th, 2007 — jopincar At the end of my post, Implementing ITypedList for Virtual Properties, I mentioned the possibility of having one, very-versatile, subclass of PropertyDescriptor that [...]

  2. Peter Widmer Says:

    Thank you so much!!! Your example helped me to make my dynamic entity databindable :-)

    Great Work!!!

  3. Stevejay Says:

    Excellent guide!

  4. Maksim Kozyarchuk Says:

    Very elegant solution. This is cleaner and more readable than an example that came with DevExpress grid.

  5. Karl-Heinz Sohn Says:

    Thank you so much!!! I want to make an object with dynamic properties databindable. I spend hours reading documentation and browsing the internet. With your excellent guide this was done in minutes :-)

  6. Bobby Says:

    Is there a way of dynamically naming the columns, what i am trying to accomplish is my collection has two string properties Key and Value. The string value of Key i want as the column name and the string value of Value as the cell data.

    Any help would be much appreciated.

  7. jopincar Says:

    There is a way, but I”m not sure what you are trying to do. Can you give more context?

  8. Bobby Says:

    Okay sure, Here is a snippet of my classes…

    public class DetailItemCollection : List {…}

    public class DetailItem
    {
    private string m_key;
    private string m_value;
    public DetailItem() { }
    public DetailItem(string key, string value)
    {
    m_key = key;
    m_value = value;
    }
    public string Key
    {
    get { return m_key; }
    set { m_key = value; }
    }
    public string Value
    {
    get { return m_value; }
    set { m_value = value; }
    }
    }

    Using the DetailItemcollection i want to have a column named after each ‘Key’ property and have it’s row display the ‘Value’ property for each of the items within the collection.

    Basically this is used in a hierarchical grid display and the child detail contains alot of different Key\Value pairs and i wanted to avoid having a property for each possible Key as there are so many and they are different depending on what type the Master detail is. I thought i could be clever and just create a column based on the value set in the Key property.

    I hope this helps to explain it a little better.

  9. jopincar Says:

    I’m still not sure I understand. The grid expects to receive a collection that implements the same interface for all members. It sounds like you are saying you could have row one with Birthday, 01/01/2001 and row two with Name, John. When you displayed both rows, would you expext the grid to show two columns with one column blank for each row? Even if you could do that, is that really useful to anyone? Perhaps you are wanting to do an aggregation step that you are leaving out? So each item in the collection corresponds to a property of a row and you want to pivot that data somehow?

  10. Bobby Says:

    for simplicity here is a mockup, assume Person decends from IList…

    public class Person
    {
    private DetailItemCollection m_collection = null;
    public Person() { m_collection = new DetailItemCollection(); }
    public DetailItemCollection Details
    {
    get{return m_collection;}
    set{m_collection = value;}
    }
    }

    Instead of having FirstName, LastName etc… as properties on the Person class I have a collection of DetailItems.

    Person person = new Person();
    person.Details.Add(new DetailItem(“FirstName”, “Dave”);
    person.Details.Add(new DetailItem(“LastName”, “Davis”);

    When I bind to my grid I want it to create one row which has columns for each Key of the DetailItems in each Person class.

    So it would look like this…

    Person…
    + -> FirstName|LastName…
    David |Davis

    I am not sure i can explain it any simpler… maybe what iam hoping to achieve is not possible with this approach.

    Thanks.

    • Ernesto Says:

      Hello

      In a scenario as Bobby described, the DetailItem class should be derived from PropertyDescriptor?

      Thanks a lot.

      Ernesto

  11. jopincar Says:

    Yes, you can do this. Take a look at http://jopinblog.wordpress.com/2007/05/12/dynamic-propertydescriptors-with-anonymous-methods/. It is much more relevant to your scenario. You will still have to know up front what the complete set of columns is for a given level in the hierarchy is though.

    Where I have
    for ( int day = 0; day < 7; day++ ) {
    in the sample code you would iterate over the complete list of properties. I think it will click when you look at the full code sample.

  12. Bobby Says:

    Thanks! I managed to adjusted your sample to fit my objects and it has worked but my display is not correct for the items i have added in for some reason. This is what i see given the following example…

    Person person = new Person();
    person.Details.Add(new DetailItem(”FirstName”, “Dave”);
    person.Details.Add(new DetailItem(”LastName”, “Davis”);

    My Grid looks like this…

    FirstName|LastName
    Dave|Dave
    Davis|Davis

    Why are the records duplicated across the rows?

    Bobby.

  13. jopincar Says:

    Without seeing your code, I can’t tell you but there’s no fundamental reason it shoudn’t be working.

  14. Santiago Regojo Says:

    Thank you for this great job. A very good solution approach.
    I’m using it in Vb.Net if somebody needs this code, feel free to write me an EMail.

    Santiago Regojo

  15. Stephan Weichers Says:

    Great article! Short and very helpful. Thanks!

  16. John Russell Says:

    Great article! Thanks a bunch. Helped me on several projects. I do have one follow-up question:

    Is it possible to have a parent-level ITypedList implementation that exposes a ‘virtual property’ to a child-level ITypedList implementation. I want the results to appear as two bands in the grid. I’ve given it a shot, but seem to be having some trouble when my ‘anonymous GetValue methods’ at the parent level are being invoked. For some reason, the component passed into this delegate varies between the parent’s type and the child’s type. Ultimately, I get the parent’s ITypedList implementation repeated over several bands in the grid.

    Here is some pseudo-code of what I am trying to accomplish:

    public class ParentViewBuilder : List, ITypedList
    {
    public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
    {
    if (this.View == null)
    {
    //add some parent level properties…

    //add the child level…
    Add(dynamicProperties,
    “Test”,
    typeof(ChildViewBuilder),
    this.GetChildViewBuilderDelegate(),
    null);

    //convert dynamicProperties to PropertyDescriptorCollection and assign to this.View
    }

    return this.View;
    }

    private DynamicGetValue GetChildViewBuilderDelegate()
    {
    return delegate(object component)
    {
    return new ChildViewBuilder(component as Parent);
    }
    }

    In another class, I have the ChildViewBuilder:

    public class ChildViewBuilder: List, ITypedList
    {
    //his itypedlist implementation exposes some child-level properties that we want to see on the second band of the grid…
    }

    I hope that all makes sense. Is this even possible? Any thoughts on what could be going on?

  17. Scott Stout Says:

    Apparently ITypedList is not supported by the ASP.NET GridView. As a result, (unless someone knows differently) this solution is only useful for WinForms.

    http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=103536

  18. mike Says:

    youre a wizard mate :-) perfect .. just what i was after

  19. fathomsavvy@gmail.com Says:

    Interesting…I had never thought of using methods to create a view for my properties. Thanks. This article details the internals of using ITypedList. I’m sure I will reference back to it. I hope to follow up later.

  20. Mike Birtwistle Says:

    Everybody with a functional requirement for Entity-Attribute-Value modelling in their application needs to read this article first! Previously the only way we could display, sort and filter any ‘extended’ properties on our business objects in the UI was to create a DataTable, add columns at run-time then cross tab all the information from our business objects in to the DataTable which was RAM and processor intensive. Your article has smashed through all these boundaries and I finally have VirtualMode enabled DataGridViews that sort, filter and talk directly to the underlying business object classes, and that transparently handle extended properties.

    You are a legend, sir.

    p.s. given that I had no clue about ITypedList until I got so desperate last week that I started decompiling BindingSource with .net reflector ‘to see how DataTable did it’, the following keywords would have helped me find this article on google years ago:

    Entity Attribute Value
    sorting custom properties
    data binding custom properties
    data binding custom attributes


Leave a Reply

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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: