DataView for objects: What’s in the box
Posted on October 9, 2006
In my previous post I made the case for having a DataView-style construct for collections of arbitrary objects. In this post, I’ll review what you can (and can’t) do to achieve this with the built-in .NET 2.0 Framework classes.
View vs. Collection
First, why use a “view” rather than just a collection? For the same reason we would use a DataView rather than a DataTable. Let’s say we want to present some customer data in a DataGridView control, filtering a DataTable of customers to only those in Portland, Oregon. In a second DataGridView, we want to show all of the customers, not just the ones in Portland. The two grids are binding to the same table. If the filtering operation of the first grid actually removed rows from the table, the second grid wouldn’t be able to show all of the rows. That’s no good. That’s why we use a DataView. The DataView maintains a sort and filter without changing the contents of the backing DataTable. In this case, we can just create two DataView objects for the same DataTable, and set the RowFilter on the first to include only customers in Portland. Voila.
Aside: In fact, if you bind a DataTable to a DataGridView, you’re really binding to the default DataView for that table. When you set the DataSource property of the DataGridView to a DataTable, the CurrencyManager for the grid obtains the list of items that it should bind to from the IListSource.GetList() implementation of DataTable, which returns a DataView.
The same point applies to our collection of arbitrary objects. It’s not enough for our collection to remove items when it is filtered, or to rearrange it’s items when sorted. If it did, changing the sort on one DataGridView would have the effect of changing the sort on another DataGridView if both were bound to the same collection.
OK, we’ve established that a custom collection itself doesn’t fit the bill. What support is there for a custom view for our collection? To start with, let’s look at the interfaces that DataView implements:
When an object (like our custom view) is provided as a data source to a DataGridView or other bound control, the data source object’s implementations of these interfaces will be used by the control. If we develop our view to fully support these interfaces, the behavior of the DataGridView with our view should be very close to the control’s behavior when used with a DataView. The DataGridView and other controls don’t require all of these interfaces – you could use an IList implementation as a data source. However, if your data source doesn’t support all of the interfaces, the control won’t be able to support all of the behavior expected by the user.
Of these, ITypedList, ISupportInitializeNotification and ISupportInitialize primarily support design mode in Visual Studio, and aren’t strictly required for our purposes. IEnumerable, ICollection and IList allow consumers of DataView to use it as if it is a collection – enumerating the items in the view with foreach, add items, remove items, access items by index, and so on. These are the methods used by DataGridView to determine what items should be displayed in the grid and in what order.
Things start to get more interesting with IBindingList. The methods and properties of this interface expose sorting and searching behavior. Additionally, we have properties that restrict manipulation of the collection through the view: AllowEdit, AllowNew, and AllowRemove. Finally, one can add a new item to the underlying list without knowing how to construct it, via AddNew. The Allow* properties of the data source object constrain the behavior of the DataGridView. AddNew is called when the user enters the “new row” of the grid. As for the sorting and searching methods, for now, just note that only one property can be specified in a sort or search (i.e., only single-column sorting is supported).
IBindingListView provides the premier binding experience. A superset of IBindingList (and thus IList, ICollection and IEnumerable as well), it adds multi-property sorting and filtering. A view class that implements IBindingListView behaves like a list, and supports sorting and filtering over multiple properties – and that’s all we need.
So, what classes implement IBindingListView? DataView and BindingSource. We know that DataView doesn’t support collections of arbitrary objects. What’s BindingSource about?
This fine class doesn’t act as a view, so it doesn’t help us. BindingSource is a wrapper over a data source that makes binding to a list data source easier. It provides indirection between the bound control and the actual data source, allowing the underlying list data source to be changed out on the fly. It also provides it’s own currency management, and is designed to be used with the BindingNavigator component to provide navigation through the data source list. Although BindingSource implements IBindingListView, it delegates the filtering and searching work to the corresponding methods of the underlying data source. So, you can set a filter on a BindingSource, but if the data source that it wraps doesn’t support filtering, it won’t help you.
Where are we?
At the beginning. There’s no implementation of a view for arbitrary objects in the .NET Framework. It’s time to roll up our sleeves and get coding! In my next installment, we’ll do just that.