ItemsControl: 'E' is for Editable Collection

Dear Dr. WPF,

As of late, a serein has come upon me in the world of wpf and databinding, hence I’m writing to you in my desperate search for some kind of remedy before there will be a hey rube.

Whilst having an ObservableCollection of “Person” objects, and creating a collectionview for this and also adding a SortDescription on a “Name” property on the “Person” object, I noticed that if I change the “Name” property on one of the objects (they are shown in a listbox, having the “Name” property databound to a textblock), and thereafter calling Refresh() on the CollectionView, the refresh will actually take more than a second with only about 50 items.

Ok, if I measure the call to Refresh() it doesn’t take more than 100 ms or so, but like the call sets in motion alot of other things that will happen after my method has exited, which is understandable since the documentation states that the whole view is recreated and therefore I assume the whole tree of items of the visualtree concerned is re-created too.

If I instead just remove the item, and just re-insert it, it’s as close as I can get to instantaneous. perfect! BUT! Since I’m a quidnunc I won’t be satisfied with this! So I’m asking the great Dr, that has given us all such great in depth knowledge in the past, what can we do about this in a more generic fashion? Can we speed up the “Refresh()” somehow? What would you suggest I do if I have a collection of a type that I don’t know about at compile time? How can I remove / add an item then!

Or am I simply lost in my path to enlightenment ?

I shall not dissert anymore, but hope to hear from the good Dr, perhaps in a blogpost touching this subject as I feel it to be of greater interest to the public health in the land of wpf.

Best regards,
Patient-X


Dear Patient-X,

I’ve been wondering what I could file under the letter ‘E’ in this series, and now you’ve afforded me the perfect opportunity! :)

No, you are not lost in your path to enlightenment. The true path will lead you to a new feature in .NET 3.5 SP1 called an “editable” collection view. More on that momentarily… First, let’s examine exactly what is happening in your scenario.

Understanding the ListCollectionView Class

You are binding to a list of items. More specifically, you are binding to a collection of Person objects (namely an ObservableCollection<Person>) that, in turn, implements the IList interface. The binding engine will automatically generate a ListCollectionView for such a collection. The ListCollectionView class provides support for sorting, grouping, and filtering your collection when it is the ItemsSource for an ItemsControl. (See ‘C’ is for Collection for more information.)

For the purposes of this discussion, let’s look at a similar scenario using the following collection of Character objects (borrowed from ‘I’ is for Item Container):

    <src:CharacterCollection x:Key="Characters">
      <src:Character First="Homer" Last="Simpson"
          Gender="Male" Image="images/homer.png" />
      <src:Character First="Marge" Last="Bouvier"
          Gender="Female" Image="images/marge.png" />
      <src:Character First="Bart" Last="Simpson"
          Gender="Male" Image="images/bart.png" />
      <src:Character First="Lisa" Last="Bouvier"
          Gender="Female" Image="images/lisa.png" />
      <src:Character First="Maggie" Last="Simpson"
          Gender="Female" Image="images/maggie.png" />
    </src:CharacterCollection>

To demonstrate grouping and sorting for our collection, we can define a CollectionViewSource object in markup, as shown below:

    <CollectionViewSource x:Key="CharacterCollectionView"
        Source="{Binding Source={StaticResource Characters}}">
      <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="Gender"/>
      </CollectionViewSource.GroupDescriptions>
      <CollectionViewSource.SortDescriptions>
        <cm:SortDescription PropertyName="First" />
      </CollectionViewSource.SortDescriptions>
    </CollectionViewSource>

When this CollectionViewSource is set as the ItemsSource of a ListBox, it will yield a view of the collection (namely, a ListCollectionView) in which the characters are grouped by gender and sorted by first name. Here is what the ListBox declaration might look like:

  <ListBox Name="lb" HorizontalAlignment="Center" VerticalAlignment="Center"
      ItemsPanel="{StaticResource HorizontalItemsPanel}"
      ItemsSource="{Binding Source={StaticResource CharacterCollectionView}}"
      ItemContainerStyle="{StaticResource CharacterContainerStyle}">
    <ListBox.GroupStyle>
      <GroupStyle Panel="{StaticResource HorizontalItemsPanel}" />
    </ListBox.GroupStyle>
  </ListBox>

The image below depicts our ListBox with grouping and sorting.

You can download the sample for this post to see the complete item template, item container style, and group item style.

Dynamically Updating Items is Problematic

Ideally, applying a sort order to a CollectionView would result in a grouped and sorted view of the collection that is fully maintained even when items within the collection are changed. Unfortunately, a ListCollectionView only groups and sorts its items when the view is first created (or when new items are added to the source collection, assuming that the source collection is observable). A ListCollectionView will *not* monitor changes to properties on each existing item within the collection.

For example, if you execute code at runtime to dynamically change Bart’s name to “Mandy”, the ListCollectionView will be unaware of that change and the item will not appear in its proper sorted location after the change. Furthermore, if Bart finally gets the operation he deserves (and secretly desires) and his gender is updated accordingly, the changed item will not move to its proper group. Here is some simple code to perform these changes:

    Character bart = lb.Items[0] as Character;
    bart.First = "Mandy";
    bart.Gender = Gender.Female;

The image below depicts the sorting and grouping errors that are present after these changes:

Note, that the item template did indeed update according to the name and gender change (because the Character object raises the appropriate change notifications for these properties), but the ListCollectionView did not update its view of the items. As a result, the changed item is no longer in the correct sort location or group.

Refreshing a CollectionView

If you are using .NET 3.5 or earlier, you can force a CollectionView to be updated at runtime by calling its Refresh() method. Then the items will once again be properly grouped and sorted. Here is a routine that includes the Refresh() call:

    Character bart = lb.Items[0] as Character;
    bart.First = "Mandy";
    bart.Gender = Gender.Female;
    (lb.ItemsSource as ListCollectionView).Refresh();

After calling Refresh(), the modified character now appears in the correct group and sort location, as shown here:

Unfortunately, the Refresh() method results in a complete regeneration of the view. It is a rather drastic operation, to say the least. Furthermore, when a Refresh() occurs within the view, it raises a CollectionChanged notification and supplies the Action as “Reset”. The ItemContainerGenerator for the ListBox receives this notification and responds by discarding all the existing visuals for the items. It then completely regenerates new item containers and visuals. (See ‘I’ is for Item Container and ‘G’ is for Generator for more information on item containers and container generation.) Again, this is a drastic (and often very expensive) operation.

Increasing Refresh Performance is Difficult

You raised the question, “Can we speed up the “Refresh()” somehow?”

Unfortunately, the very nature of the refresh operation makes this difficult. For the mundane reasons, keep reading… otherwise, feel free to skip ahead to A Better Option.

Theoretically, you could derive your own custom collection view from the ListCollectionView class and override its Refresh() method. Then you could perhaps implement a routine that intelligently regenerates the view in a minimalistic manner by comparing the source collection to the view collection.

Such a routine would need to move existing items to their new locations in the view, add items that were not previously present, and remove items that should no longer be present. All of these changes would need to be made per the view’s sorting, grouping, and filter criteria. For each change, the view would need to raise a very specific CollectionChanged notification (Add, Remove, or Move) for consumption by the ItemContainerGenerator of the owning ItemsControl.

Not only would such a synchronization routine be complex, but it is also likely to be even more expensive than simply resetting the entire collection. There will be some number, n, where discrete Add, Remove, and Move change notifications for n or fewer changes is indeed more performant than a single Reset notification. Unfortunately, it will be very difficult to heuristically determine the appropriate value for n.

A Better Option: Remove and Re-Add

A better approach, especially if you are stuck on pre-3.5 SP1 bits, is to do precisely what you describe in your email. Namely, do not call Refresh() at all, but instead, remove the target item from the observable collection, modify its properties, and then re-add it to the collection, as shown here:

    Character bart = lb.Items[0] as Character;
    CharacterCollection characters = FindResource("Characters") as CharacterCollection;
    characters.Remove(bart);
    bart.First = "Mandy";
    bart.Gender = Gender.Female;
    characters.Add(bart);

The ListCollectionView will detect the removal of the old item and raise the appropriate Remove notification so that the ItemContainerGenerator can remove the associated container and visuals. It will also detect the addition of the modified item and insert it at the correct location within its view of the source collection (again, per the grouping, sorting, and filter criteria). Then it will raise the necessary Add notification so that a new item container and item visuals can be generated.

This solution is obviously not perfect! One problem that may not be immediately evident is that adding and removing an item from the source collection effects the ListCollectionView’s notion of currency (”current item”). For example, if the Bart character was the current item before it was removed from the collection, that state will be lost. If this is important to your scenario, you must add additional code around the remove/re-add routine to save and restore currency.

An Even Better Option: IEditableObject

As mentioned earlier, .NET 3.5 SP1 enables an even better approach that solves many of the problems present in the Refresh() and Remove/Re-Add solutions. Namely, it allows you to implement an IEditableObject interface on your datamodel objects (or on your viewmodel objects, if you are using the model-view-viewmodel pattern). This new interface allows for transactional changes (edit, cancel, and commit) on the object.

The framework supports the transactional model by implementing another new interface called IEditableCollectionView on the existing list-based CollectionView classes (ListCollectionView and BindingListCollectionView). This interface works in conjunction with collection items that implement the IEditableObject interface.

In this new approach, when you need to change an item, you can first call the EditItem() method of the collection view, then make the required changes to the item, and finally, call the CommitEdit() method of the collection view. If you decide that you don’t want to commit the changes, you can call the CancelEdit() method of the collection view.

It is worth noting that the ItemCollection class also implements the IEditableCollectionView interface. Since the Items property of an ItemsControl is of type ItemCollection (see ‘C’ is for Collection), it provides handy access to the methods of IEditableCollectionView. As long as you know the source collection is an IList, you can safely call the IEditableCollectionView methods directly on the Items property and the ItemCollection class will delegate the call to the appropriate underlying collection view.

How it Works

In practice, the properties of an “editable” object will rarely be updated directly in code behind. Rather, these properties will be updated via bindings to properties on controls within the UI.

In WPF, it is fairly common to present data using a “view template”. Such a template will often present a readonly view of the data using elements like TextBlock, Image, etc. Then, when it is necessary to edit the data, the view will switch to an “edit template” that contains editable controls like TextBox, ComboBox, CheckBox, etc.

The IEditableObject interface provides three transactional methods: BeginEdit(), CancelEdit(), and EndEdit(). I prefer to expose an additional readonly property on the editable object called IsInEditMode that can be used as a trigger for swapping between the view template and the edit template.

Below is my typical implementation of IEditableObject, as it would be implemented on the Character class in our example:

    #region IEditableObject support

    public Character _cachedCopy = null;

    public void BeginEdit()
    {
        // save object state before entering edit mode
        _cachedCopy = new Character();
        _cachedCopy._first = _first;
        _cachedCopy._last = _last;
        _cachedCopy._image = _image;
        _cachedCopy._gender = _gender;

        // ensure edit mode flag is set
        IsInEditMode = true;
    }

    public void CancelEdit()
    {
        // restore original object state
        if (_cachedCopy != null)
        {
            First = _cachedCopy._first;
            Last = _cachedCopy._last;
            Image = _cachedCopy._image;
            Gender = _cachedCopy._gender;
        }

        // clear cached data
        _cachedCopy = null;

        // ensure edit mode flag is unset
        IsInEditMode = false;
    }

    public void EndEdit()
    {
        // clear cached data
        _cachedCopy = null;

        // ensure edit mode flag is unset
        IsInEditMode = false;
    }

    private bool _isInEditMode = false;
    public bool IsInEditMode
    {
        get { return _isInEditMode; }
        private set
        {
            if (_isInEditMode != value)
            {
                _isInEditMode = value;
                RaisePropertyChanged("IsInEditMode");
            }
        }
    }

    #endregion

Download the Sample

You can download the complete sample for this post to see the IEditableObject and IEditableCollectionView interfaces in action. When you run the sample, simply double-click an item (or press F2) to enter edit mode.

If you do not wish to commit your changes, press Escape. If you do wish to commit your changes, press Enter or simply select another character. When the changes are committed, you will notice that the item immediately moves to its proper location within the view.

Is IEditableObject Absolutely Required?

You posed the question, “What would you suggest I do if I have a collection of a type that I don’t know about at compile time?”

First, I would advise you to avoid that situation if at all possible. The model-view-viewmodel pattern typically involves wrapping or replicating items (and collections) of known types within observable classes (classes that provide change notifications like INotifyPropertyChanged and INotifyCollectionChanged) and adding additional interfaces (like IEditableObject) to either the data model objects or the viewmodel objects to allow them to play nicely with the view. That is always my preferred approach.

Having said that, there are certainly times when you may not have the option of modifying the entity within your collection. If you simply cannot add the IEditableObject interface to the data model or viewmodel class, you needn’t fret. The IEditableCollectionView interface is still your friend (as long as you are targeting .NET 3.5 SP1, of course). You won’t get all of the transactional goodness of IEditableObject, but you can still call EditItem() followed by CommitEdit() to update the view’s notion of a specific item.

Consider the earlier example where the characters were not correctly sorted or grouped after changing Bart’s name and gender. It turns out that you can simply add the lines below to cause that specific item to be moved to the correct location within the view:

    Character bart = lb.Items[0] as Character;
    bart.First = "Mandy";
    bart.Gender = Gender.Female;
    (lb.Items as IEditableCollectionView).EditItem(bart);
    (lb.Items as IEditableCollectionView).CommitEdit();

Voîla! There is no longer a need to explicitly refresh the entire view!

Order now and receive these bonus gifts!

The downloadable sample included with this post covers the specific questions posed by Patient-X, but there is more to IEditableCollectionView. It also provides a convenient way to add and commit new items to the source collection. You can even use it to remove existing items from the source collection. By leveraging members of the IEditableCollectionView interface for such changes, you can ensure that your view of the collection will remain accurate with respect to grouping, sorting, and filtering.

To see some of the other features of IEditableCollectionView, check out Vincent Sibal’s introduction to IEditableCollectionView, He also includes a nice code sample with his post.

Cheers,
Dr. WPF

P.S. For anyone who is hoping to get a published response to a WPF question, you should note (as Patient-X has) that the doctor is easily manipulated by wordsmithery. If you use lots of obscure terms like serein and hey rube, there is a much greater chance you will receive a public response, as it appeals to my invented persona’s inflated sense of perspicacity! ;)

17 Responses to “ItemsControl: 'E' is for Editable Collection”

  1. Abdelkrim says:

    thank you, so much helpful.

  2. Pete O'Hanlon says:
    Damn you and the inevitability that you beat me to the punch. I thought I’d got the headstart on you when I wrote about IEditableCollectionView here. I should have known that the good doctor was in, and had beaten me to it. Great post, as usual.
  3. Dr. WPF says:

    The more the merrier, Pete! Nice sample. :-)

  4. JW says:

    Very helpful post. Thanks!

  5. Developer says:

    Hi Dr.WPF,
    Thanx a gud post. In the sample above, the items that are loaded inside the Listbox have been hardcoded, can i load a listbox similarly from a catalog file which has some defined types available in a particulat assembly. Something like a Toolbox control which loads toolbox items from the provided dll in the “Choose Items” dialogue box in VS?

  6. Dr. WPF says:

    Yes Developer, any observable collection will work well as the items source for the ListBox. For more info, check out some of the earlier posts in this series. Particularly, look at ‘C’ is for Collection.

    Cheers,
    -dw

  7. David Nelson says:

    The items that I am binding to are being updated asynchronously inside my model, so there is no opportunity to call Begin/CommitEdit. In my previous WinForms app I wrote a wrapper collection which would watch all of its items and re-filter them when they change. I had hoped to switch to using the ListCollectionView, since it is designed to solve similar problems to my wrapper collection (sorting, filtering, etc), and in certain areas it is more robust. But it is critical that items are re-filtered when they change, and also that selection is maintained. I take it that none of the built-in views meet my requirements, and I am going to have to go back to my own implementation?

  8. Dr. WPF says:

    Hi David,

    If changes are truly occuring on a background thread within the model, then yes, you will likely have to wrap the collection and surface it as an observable collection in your viewmodel. That same wrapper could perform the edit/commit trick when an item changes in the underlying collection.

    The better long-term option would be to modify the model itself so that it supports and leverages IEditableObject for changes to items within the collection.

    Cheers,
    -dw

  9. Justin Case says:

    Hey Dr!
    I thought I should turn to your seemingly bottomless well of knowledge for something that is bothering me regarding IEditableCollectionView.
    Suppose I have an ObservableCollection , let’s call it “People”, and I manually create two ListCollectionViews for this, each collectionview using different sorting and filtering, but still refering to the same “People” collection. Displaying these two collectionviews in listboxes for instance works just fine.
    But let’s say that my application due to some interaction changes a property on one of the items contained within the “People” collection. Ok, now I have to check if the item is shown in CollectionView1 and in that case I just do “EditItem(item);” and “CommitEdit(); and it works fine, it’s refiltered (if needed) and resorted to the correct position. But I now also have to do the same thing on CollectionView2.
    The problem starts when the item beeing changed isn’t available in one of the collectionviews, because even if I have changed a property that should now include it in the collectionview, it won’t pop up there, and I can’t do an “EditItem(item);” and “CommitEdit()” since it’s not in the collectionview (this throws an exception). Now I’m left with the only option of doing the old trick of remove/re-add the item in the “People” collection instead (and I have to take care of any currency issues manually), hence IEditableCollectionView doesn’t help if I have multiple collectionviews of one single collection.

    Am I missing something or is this something it probably wasn’t meant to support? Should I keep two duplicate collections instead maybe? Any tips or bright ideas? Thanks!

    Cheers!

  10. Dr. WPF says:

    SIGHHHhhh…

    Yes, Justin, that is a drawback to leveraging IEditableCollectionView to cause an item to be updated in the view…. namely, the item must already belong to the view. I *totally* feel your pain here. I have the same scenario in an app and the only option is to refresh the entire view (or remove/readd the item, as you suggest).

    The framework developers probably did not anticipate folks using EditItem/CommitItem to update a view like this. I am going to request that they add a specific method to ICollectionView for updating a single item in the view.

    Cheers,
    -dw

  11. Trey says:

    Very helpful. My apologies for posting about my situation on the first CollectionView lesson… it is likely more relevant to this one as it pertains specifically to IEditableCollectionView and bound EntityObjects as ItemsSource.

  12. Seema says:

    This is really amazing, could not find more helpful information on ItemsControls. Continue with the series..looking forward for it till Z.

  13. david@commongenius.com says:

    I have finally been able to install SP1, and after taking a closer look at IEditableCollectionView, I have come to the conclusion that it really doesn’t help me any.

    Objects are being update asynchronously inside the model. The model does not necessarily know all of the collections of which the object may be a member, since those collections only exist in the presentation. So there is no way for EditItem to be called in advance of the edit.

    Combine that with Justin’s point about items that are not yet a part of the collection, and it really seems like the WPF team took the completely wrong approach to solving this problem. IEditableCollectionView and the corresponding implementations in CollectionView and ItemCollection only address certain very narrow use cases. It seems like providing a Refresh(item) method (that works for any item from the underlying collection) would have been a much simpler and much more broadly applicable solution.

  14. Dr. WPF says:

    Just to be clear, David… the WPF team did *not* provide IEditableCollectionView as a mechanism to solve the problem of refreshing a single item. IEditableCollectionView and IEditableObject are only present to support transactional updates.

    What I describe in this article is merely a hack that leverages this new feature to allow the refreshing of a single item. Prior to IEditableCollectionView, this was not even possible.

    I agree that it would be nice to see a method that allows a single item to be manually refreshed within a collection view. I’ve requested this before and I know others have too. There are definitely changes coming for WPF 4.0 in this area, but I don’t know if this particular feature was added. I’ll take a look when I have some bandwidth and post back here.

  15. David Nelson says:

    IEditableCollectionView and IEditableObject are only present to support transactional updates. What I describe in this article is merely a hack…

    Fair enough. I have been struggling this problem for quite a while now; I was hoping this would finally provide a solution that wouldn’t require enormous amounts of custom code. But it looks like I am going to have to go back to writing my own filter/sort collection, essentially re-creating all of the functionality in ListCollectionView, because the framework version is missing one very small extensibility point.

    It’s just very strange to me that this problem wasn’t addressed in the first round of WPF design, much less the second or the third. It seems to me to be an extremely common scenario: change an item and have that item updated accordingly, wherever it may be. Isn’t that the whole point of databinding in the first place? Although I suppose that everyone thinks that their own problems are common scenarios…

  16. Daryl says:

    The real solution here is to have the various CollectionView classes monitor INotifyPropertyChanged events, re-sorting and grouping and filtering as appropriate. I don’t understand why this isn’t being done.

    If a view is sorted on “DOB”, and a PropertyChanged event notifies you that an item’s “DOB” property was modified, it stands to reason that the view should be re-sorted. Same for grouping and filtering.

    It would be easier to get this done if C# had first-class support for weak events. The current “WeakEventManager” approach is a lot of code and static class overhead every time you just want to implement the Observer pattern. Any clue if this is ever going to happen, Doc?

  17. Fahad says:

    Was just going thru your blog, You can refresh a single item by doing the following,

    - implement INotifyPropertyChanged in the business object.
    - before the ItemsSource is set, loop thru all the items and tag the PropertyChanged event,

    northwind.Orders.ForEach(o=>{
    o.PropertyChanged += (s,e)=>{
    var changedItem = s as Order;
    orders[orders.IndexOf(changedItem)] = changedItem;
    };
    });

    - Above code is not very well written, but that should be it. The CollectionView has a INotifyCollectionChanged interface implemented, this will trigger with “e.Action == NotifyChangedAction.Replace” event, if the control listens to this action and refreshes the UI internally, then this should work well.

    Check it out!!!

Leave a Reply