WPF 3.5 SP1 feature: IEditableCollectionView

Motivation

A CollectionView is your interface into manipulating a collection of data items in an ItemsControl. Common tasks with this view often involve applying sorting, filtering, and grouping. In lieu of supporting a DataGrid control, transactional adding/editing/removing is an essential feature for data manipulation and a new view has been added in WPF 3.5 SP1 to support this functionality.

What is it?

IEditableCollectionView is a new collection view that you can use to supports adding and removing new items, as well as editing items in a transactional way. It is implemented by ListCollectionView (the default view for ObservableCollection) and BindingListCollectionView (the default view for DataTable). I will go through an example to further describe it and summarize afterwards.

Background on the example

I will be creating a ListBox with some data that you can add/remove/edit each item. You also have the option to cancel in the middle of editing the item. I will use buttons to trigger an item for editing, adding, removing, etc.  Here is the ListBox xaml:                               

<!--defined in resource section-->  

<Style x:Key="listBoxDefaultStyle" TargetType="ListBoxItem" >

    <Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />

</Style>

<ListBox Name="itemsList"

  ItemsSource="{StaticResource products}"

  ItemContainerStyle="{StaticResource listBoxDefaultStyle}"/>

When the ListBox is not in edit mode it will use a template of TextBlocks.                 

               

<DataTemplate x:Key="DefaultTemplate">

  <StackPanel Orientation="Horizontal">

    <TextBlock Text="{Binding Path=Book, StringFormat=Title: {0};}"></TextBlock>

    <TextBlock Text="{Binding Path=Author, StringFormat=Author: {0};}"></TextBlock>

    <TextBlock Text="{Binding Path=Price, StringFormat=Price: {0:C}}"></TextBlock>

  </StackPanel>

</DataTemplate>

 

When it is in edit mode, it will use a template of TextBoxes (I will set the DataTemplate dynamically in code).

<DataTemplate x:Key="EditingTemplate">

  <StackPanel Orientation="Horizontal">

    <TextBox Text="{Binding Path=Book}" />

    <TextBox Text="{Binding Path=Author}" />

    <TextBox Text="{Binding Path=Price}" />

  </StackPanel>

</DataTemplate>

 

How do I use it?

Just like the other collection views, you can obtain a reference to this collection using the CollectionViewSource.GetDefaultView,

// retrieve a reference to the view

ICollectionView view = CollectionViewSource.GetDefaultView(itemsList.Items);

IEditableCollectionView iecv = (IEditableCollectionView)view;

 

Before I go any further, there is an important point to note about delegation of work. When the data source implements IEditableObject, the IEditableCollectionView will call BeginEdit() when a new item is added or an existing item is opened for edit. It will call CancelEdit() when the item is cancelled and EndEdit() when the item is committed. IEditableCollectionView lets the app author handle this part of the transaction.

In my data source, I will be creating a copy of the selected data item when it is opened for edit. If the procedure is cancelled, I will use the copy to reset the original state, otherwise the new data will update the data source. I’ve only included the relevant information of the data source here.

 

public class Product : INotifyPropertyChanged, IEditableObject

{

  private Product copy;

  #region IEditableObject Members

  public void BeginEdit()

  {

    if (this.copy == null)

        this.copy = new Product();

    copy.Book = this.Book;

    copy.Author = this.Author;

    copy.Price = this.Price;

  }

  public void CancelEdit()

  {
this.Book = copy.Book;

    this.Author = copy.Author;

    this.Price = copy.Price;
}

  public void EndEdit()

  {
copy.book = null;

    copy.author = null;

    copy.price = 0;
}

  #endregion IEditableObject Members

}

 

Let’s first focus on editing items. To initiate an item to be edited you call IEditableCollectionView.EditItem(). As I just discussed, this will call BeginEdit on my selected data item. Here is the code that is called when the edit button is clicked (Notice the template of the item container is updated here):    

private void edit_Click(object sender, RoutedEventArgs e)

{

  // edit the item

  iecv.EditItem(itemsList.SelectedItem);

  // update the template

  ListBoxItem lbItem = (ListBoxItem)itemsList.ItemContainerGenerator.ContainerFromItem(iecv.Current EditItem);

  lbItem.ContentTemplate = (DataTemplate)this.myGrid.FindResource("EditingTemplate");

}

 

So now that the current selected item is editable, the changes to it can either be submitted or cancelled. We shall look at the submitted scenario next. To commit changes, you call IEditableCollectionView.CommmitEdit(). This will then call EndEdit() on my data item where I reset my copy of the data as it is not needed anymore. Here is the code when the submit button is clicked:

private void submit_Click(object sender, RoutedEventArgs e)

{

  // update the template

  ListBoxItem lbItem = (ListBoxItem)itemsList.ItemContainerGenerator.ContainerFromItem(iecv.Current EditItem);

  iecv.CommitEdit();

  lbItem.ContentTemplate = (DataTemplate)this.myGrid.FindResource("DefaultTemplate");

}

 

The cancel scenario is very similar to the submit code, but instead CancelEdit() is called on my data item where I reset it’s values to the copy that I stored from BeginEdit():

private void cancel_Click(object sender, RoutedEventArgs e)

{

  // update the template

  ListBoxItem lbItem = (ListBoxItem)itemsList.ItemContainerGenerator.ContainerFromItem(iecv.Current EditItem);

  iecv.CancelEdit();

  lbItem.ContentTemplate = (DataTemplate)this.myGrid.FindResource("DefaultTemplate");

}

 

Adding new items and removing items follow a similar pattern where the view will call BeginEdit, CancelEdit, and/or EndEdit on the data item. One important difference however is how it is managed for you. While I was managed part of the editing transaction, the IEditableCollectionView will managed the addition and removal of an item. When IEditableCollectionView.AddNew() is called, a new data item is actually added to the data source by the collection view. In BeginEdit, you have the option to initialize the new item to default data. Same goes when CancelNew() or Remove() is called. The item that was added or selected is actually removed from the data source by the collection view. There is no additional code that you need to write to create the new data item and manually add it to the data source. You can check out the full project attached to view the source for adding and removing as well as the editing that I talk about above. It works with the WPF 3.5 SP1 bits.

For completeness, here is the full list of APIs from IEditableCollection. I try to make use of most of them in my project.

      

public interface IEditableCollectionView

{

    bool CanAddNew { get; }

    bool CanCancelEdit { get; }

    bool CanRemove { get; }

    object CurrentAddItem { get; }

    object CurrentEditItem { get; }

    bool IsAddingNew { get; }

    bool IsEditingItem { get; }

    NewItemPlaceholderPosition NewItemPlaceholderPosition { get; set; }

    object AddNew();

    void CancelEdit();

    void CancelNew();

  void CommitEdit();

    void CommitNew();

    void EditItem(object item);

    void Remove(object item);

    void RemoveAt(int index);

}

IEditableCollectionViewSample.zip