ITypedList and when to use it.Over the past two weeks, I’ve gotten two questions relating to Windows Forms
Databinding and custom collections.
In the first case, the developer had created a collection that held
polymorphic items:
public class MyBaseClass
{
// internals elided.
}
public class Derived1 : MyBaseClass
{
// internals elided.
}
public class Derived2 : MyBaseClass
{
// internals elided.
}
public class Derived3 : MyBaseClass
{
// internals elided.
}
ArrayList myCollection = new ArrayList();
myCollection.Add( new
Derived1());
myCollection.Add(
new Derived2());
myCollection.Add( new
Derived3());
// Use
a data source doesn’t work:
MyDataGrid.DataSource =
myCollection;
This does not work because the WindowsForms DataGrid class makes an
assumption that all the objects in the collection are the exact same type. In
the above example, it assumes all objects are of type Derived1. That fails. This
person wanted the DataGrid to assume that it had a collection of MyBaseClass
objects.
The second question involved navigation. (If you are not familiar with
navigating in a datagrid, see this MSDN article) I had a colleague create a collection where one of the properties was itself a
collection. For example:
public classCustomer
{
public string Name
{
get{ return
_name;}
}
// The contained
collection:
private
ArrayList _orders;
public IList Orders
{
return
_orders;
}
}
He wanted to bind a data grid to a list of customers and allow users to
navigate to the set of orders for that customer. That did not work either.
Well, the answer to both questions is the same: Implement the ITypedList
interface. ITypedList contains two methods:
public interface ITypedList
{
public PropertyDescriptorCollection
GetItemProperties(
PropertyDescriptor[] listAccessors);
public string GetListName(
PropertyDescriptor[]
listAccessors);
}
Both of these routines give information about the elements in the collection.
GetItemProperties() returns a collection that describes all the properties that
should be displayed in the collection. For a single level collection, these
routines are always called with a null listAccessors parameter. That will solve
the first problem. You simply return a name for your collection for
GetListName:
public string GetListName(
PropertyDescriptor[]
listAccessors)
{
return "MyBaseClass";
}
For GetItemProperties, you return a collection of all the properties you want
displayed:
public
PropertyDescriptorCollection
GetItemProperties(
PropertyDescriptor[]
listAccessors)
{
return TypeDescriptor.GetProperties(
typeof(
MyBaseClass ));
}
That solves the first problem. To solve the second problem, you need to
understand the listAccessors parameter. This is an array of PropertyDescriptors
that describe how to navigate from the top-level list to the specific collection
currently being used. My simple example above had only one contained type.
Therefore, the listAccessors parameter will either be null, or contain one
element. That tells you what should be returned:
public string GetListName(
PropertyDescriptor[]
listAccessors)
{
if ( null == listAccessors )
return
"Customers";
else
{
PropertyDescriptor p =
listAccessors[ 0 ];
return
p.DisplayName;
}
}
The GetItemProperties() method is similar. You return the public properties
of the type contained in the collection referenced by listAccessors[0]. In my
simple example above, listAccessors[0] would be the type ArrayList, which is the
type of the collection. You need to return the type of the objects in the
collection (here that would be the Order class).
The whole point of this discussion is that the DataGrid uses reflection to
determine how to bind to your data. If your datasource supports the ITypedList
interface, those methods are used to support navigation and to determine which
columns should be displayed. If not, the DataGrid displays all the properties
found in the first item in the collection, and navigation is not supported.
You can learn more here: http://www.ftponline.com/vsm/2003_06/online/wagner/default_pf.aspx