Wednesday, September 28, 2011

A custom IEnumerable implementation does not work properly with DataGrid

I am developing a DataGrid that looks at a data store that does not have a public collection. I implemented an IEnumerable interface for this data-store BUT
the problem is: I am not able to edit cells; every time I try to edit a cell I get this exception saying, "'EditItem' is not allowed for this view.".

You may ask, why not to create a list for your data-store and pass it to the DataGrid. The answer is: Because my data-store might get very large.

Analysis: I did some digging and I found that DataGrid casts Items property (property of its grand base class: ItemsControl) it casts it to IEditableCollectionView. And here where it fails (we will talk about it later). But what is the type of Items Property ?
Items is a property of type ItemCollection (which is a CollectionView) that is created for the Enumerable class that you assigned to ItemsSource. The collection-view is created by calling CollectionViewSource.GetDefaultCollectionView on your Enumerable class.

The analysis' results: DataGrid fails to create an editable CollectionView for your Enumerable class. Why? because you did not implement the correct interface for your Enumerable class.

So, the question is what interfaces your collection (your Enumerable class) should implement, so that the DataGrid can create an editable collection-view for it?

OK , now , it started to make sense.. I dag deeper and deeper and I found this piece of comments inside ViewManager class in .Net framework that clarifies the problem: (ViewManager is the creator of the collection-view for your Enumerable class that eventually will be passed to and used by Items)
The comments say:
// Order of precendence in acquiring the View:
// 0) If collection is already a CollectionView, return it.
// 1) If the CollectionView for this collection has been cached, then
// return the cached instance.
// 2) If a CollectionView derived type has been passed in collectionViewType
// create an instance of that Type
// 3) If the collection is an ICollectionViewFactory use ICVF.CreateView()
// from the collection
// 4) If the collection is an IListSource call GetList() and perform 5),
// etc. on the returned list
// 5) If the collection is an IBindingList return a new BindingListCollectionView
// 6) If the collection is an IList return a new ListCollectionView
// 7) If the collection is an IEnumerable, return a new CollectionView
// (it uses the ListEnumerable wrapper)
// 8) return null
// An IListSource must share the view with its underlying list.

// if the view already exists, just return it
// Also, return null if it doesn't exist and we're called in "lazy" mode

So , all what we need to do now is to implement one of the following interfaces that would generate automatically one of the Collection Views that implements IEditableCollectionView. so that your grid will work correctly.

Finally, I implemented IList in my Enumerable class and it worked without problems :) great ..!!
Helpful Notes:
- The generic version of IList interface is not the one you should implement. You have to implement the non-gernic version of IList.
- You can try, implementing ICollectionViewFactory , but you have to know it is a little bit more complicated. Because you're going to end up implementing few more interfaces in the process.
- You can implement the IEditableObject interface for that items in your list to have more control over editing and validating each item in the list (each row in the DataGrid).

Problem solved.

Update: another complication I created .. read here about it

No comments: