BindingGroups and IEditableCollectionView

Recap

In a previous post I introduced the BindingGroups. Well now I want to get into some of the things you may run into when trying to use IEditableCollectionView with BindingGroup.

BindingGroups and IEditableCollectionView

So now with the BindingGroup feature in WPF 3.5 SP1, there is this slight duplication of transaction methods between the two types, BindingGroup and IEditableCollectionView. The duplication occurs with the two types both having BeginEdit, CancelEdit, and CommitEdit. So what do I use? Well let’s take a refresher on what both of the types offer.

IEditableCollectionView provides editing capabilities to a collection. BindingGroup contains a collection of bindings and ValidationRule objects that are used to validate an object. What they both provide are the transactional editing capabilities by supporting the IEditableObject pattern. What IEditableCollectionView provides over BindingGroup are adding new items to the collection, deleting items from the collection, and keeping currency on the item being edited or added. What BindingGroup offers over IEditableCollectionView is its item-level validation. Both have very attractive attributes and it is most likely that you will need a combination of both. Here are some things to take into account when using both (I’m going to use the sample from my post on IEditableCollectionView as a starting point to build on):

Currency with IEditableCollectionView

If currency during editing is important to you, such as IECV.IsAddingNew, IECV.CurrentAddItem, IECV.IsEditingItem, and IECV.CurrentEditItem, then you have to call the IECV.BeginEdit, IECV.CancelEdit, and IECV.CommitEdit for those currency values to be updated correctly. For example, you cannot call IECV.BeginEdit then BindingGroup.CommitEdit in place of IECV.CommitEdit and expect currency on the IECV to be correct after the commit.

Editing Existing items

To begin editing an existing item in the collection, you call IECV.EditItem. Calling BindingGroup.BeginEdit is unnecessary if IECV.EditItem is called as it only calls IEditableObject.BeginEdit on each item which is already accomplished by IECV.EdtiItem. Also, there is no state kept when calling BindingGroup.BeginEdit. Here is a possible implementation which is basically the same as before:

    

public void EditItem()

{

    if (itemsList.SelectedItem == null)

    {
  MessageBox.Show("No item is selected.");

  return;
}   

  if (iecv.IsEditingItem || iecv.IsAddingNew)

  {

      MessageBox.Show("Please finish editing or adding item before editing or adding new item.");

      return;

    }

    // edit the item

    iecv.EditItem(itemsList.SelectedItem);

    // get the listviewitem

    itemsList.ScrollIntoView(itemsList.SelectedItem);

    ListViewItem lbItem = (ListViewItem)itemsList.ItemContainerGenerator.ContainerFromItem(itemsList.SelectedItem);

    // then update the template to an editing state

    Helpers.UpdateContentTemplate(/* isEditing */true, lbItem);

}

 

To commit an edit of an existing item you have several choices and it also depends on how you want to treat validation failures. Let’s say that if a failure occurs, the item is not committed and no other items can be edited until the failure is fixed or edit is cancelled. Here is one possible implementation:              

public void SubmitItem()

{

  if (!iecv.IsEditingItem && !iecv.IsAddingNew)

  {
  MessageBox.Show("No item is editable for submission.");

      return;
}

    ListViewItem lbItem = null;

    if (iecv.IsEditingItem)

    {

      // get the listviewitem
  itemsList.ScrollIntoView(iecv.CurrentEditItem);

  lbItem = (ListViewItem)itemsList.ItemContainerGenerator.ContainerFromItem(iecv.CurrentEditItem);

    if (lbItem.BindingGroup.CommitEdit())

    {
  // iecv will do any necessary cleanup on commit

      iecv.CommitEdit();

      // update the template to non-editing state

      Helpers.UpdateContentTemplate(/* isEditing */false, lbItem);
}
}

}

 

When calling BindingGroup.CommitEdit I am letting all the validation rules run (include all the different ValidationSteps) and committing the data to the source. If that succeeds I’m still calling IECV.CommitEdit because I want to keep currency udpated. Notice that I’m using iecv.CurrentEditItem to retrieve the ListViewItem so it’s important that currency is correct. There is also clean up that it does relating to grouping. One side effect is that IEditableObject.EndEdit will be called twice. If that is undesirable you can replace BindingGroup.CommitEdit with BindingGroup.UpdateSources which will not call IEditableObject.EndEdit. The only issue is that you won’t be able to run validation on rules marked as ValidationStep.CommittedValue.

Another thing to notice is how the bindings are updated. If I was not using BindingGroup, I would have to do my own updates to the sources. In my IEditableCollectionView post I was doing that by setting UpdateSourceTrigger to PropertyChanged. Now with BindingGroup that is unnecessary as it will take care of that for you with CommitEdit or UpdateSources. But be careful as ValidateWithoutUpdate is a not the same. Say I changed the code in SubmitItem to be like this:

public void SubmitItem()

{

  if (!iecv.IsEditingItem && !iecv.IsAddingNew)

  {
  MessageBox.Show("No item is editable for submission.");

      return;
}

    ListViewItem lbItem = null;

    if (iecv.IsEditingItem)

    {

      // get the listviewitem
  itemsList.ScrollIntoView(iecv.CurrentEditItem);

  lbItem = (ListViewItem)itemsList.ItemContainerGenerator.ContainerFromItem(iecv.CurrentEditItem);

      //NOTE: will NOT update the source unless source is

      // updated manually like setting UpdateSourceTrigger

      // to PropertyChanged

    if (lbItem.BindingGroup.ValidateWithoutUpdate())

    {
  // iecv will do any necessary cleanup on commit

      iecv.CommitEdit();

      // update the template to non-editing state

      Helpers.UpdateContentTemplate(/* isEditing */false, lbItem);
}
}

}

 

If a validation failure occurs all is good as the logic is the same, but if validation is successful my source does not actually update. That is because I am relying on BindingGroup to update the source but I’m not updating anything from calling ValidateWithoutUpdate. I know that seems obvious but it can be a little confusing if you assume iecv.CommitEdit will update the sources.

Cancelling an edit also requires both IECV.CancelEdit and BindingGroup.CanceEdit. The former is necessary to keep currency updated and the latter so the target bindings are updated so revalidation can execute correctly. Here is a possible implementation:           

public void CancelItem()

{

    if (!iecv.IsEditingItem && !iecv.IsAddingNew)

    {
  MessageBox.Show("No item is editable to cancel.");

      return;
}

    ListViewItem lbItem = null;

    if (iecv.IsEditingItem)

    {

    // get the listviewitem
  itemsList.ScrollIntoView(iecv.CurrentEditItem);

  lbItem = (ListViewItem)itemsList.ItemContainerGenerator.ContainerFromItem(iecv.CurrentEditItem);

      // Need to call BindingGroup.CancelEdit in order for revalidate

      // to occur correctly on the item

      lbItem.BindingGroup.CancelEdit();

      // Revalidates so ValidationErrorTemplate is cleared

      lbItem.BindingGroup.ValidateWithoutUpdate();

      iecv.CancelEdit();

    }

// update the template to non-editing state

    Helpers.UpdateContentTemplate(false, lbItem);

}

 

Editing New Items

Adding a new item to the collection hasn’t changed and you still just call IECV.AddNew. Cancelling a new item hasn’t change either. You call IECV.CancelNew and there are no BindingGroup methods involved. What does need to update a bit but is pretty much the same as what I’ve already described above is when you are committing a new item. Here is the updated code:

public void SubmitItem()

{

  if (!iecv.IsEditingItem && !iecv.IsAddingNew)

  {
  MessageBox.Show("No item is editable for submission.");

      return;
}

    ListViewItem lbItem = null;

    if (iecv.IsEditingItem)

    {

        …

    }

    else if (iecv.IsAddingNew)

    {

      // get the listviewitem
  itemsList.ScrollIntoView(iecv.CurrentAddItem);

  lbItem = (ListViewItem)itemsList.ItemContainerGenerator.ContainerFromItem(iecv.CurrentAddItem);

    if (lbItem.BindingGroup.CommitEdit())

    {
  // iecv will do any necessary cleanup on commit

      iecv.CommitNew();

      // update the template to non-editing state

      Helpers.UpdateContentTemplate(/* isEditing */false, lbItem);
}
}

}

 

The only thing you want to be careful about here is currency when using a BindingListCollectionView (DataTable for example). Note that this issue does not occur with a ListCollectionView. When BindingGroup.CommitEdit is called it will call IEditableObject.EndEdit for each item as expected. However, the IEditableObjectEndEdit implementation in DataRowView will actually trigger BindingListCollectionView to update its currency. So iecv.CommitNew doesn’t actually do anything for the BindingListCollectionView case but it is a good idea to keep it there as it is needed for the other IEditableCollectionView cases (well currently just one other case, ListCollectionView).

Putting it all together

I’ve taken what I discussed above and put it in a sample here. I have one version with a ListCollectionView and one with a BindingListCollectionView so you can experiment with both. I also have a version with just IEditableCollectionView. For the BindingListCollectionView sample you will need to install the Northwind DB. Instructions are included in the sample.

As an alternate implementation, if currency wasn’t that important to me or if I were to keep track of state on my own, I could have implemented it such that I use IECV for adding and removing items and BindingGroup for transactional editing. That would eliminate the redundant calls to both IECV and BindingGroup which eliminates the multiple calls to IEditableObject.EndEdit and IEditableObject.CancelEdit. I’ll leave this implementation as an exercise for the interested reader.

Coming next…

Next I will be talking a little on the usage of validation feedback through Validation.ValidationAdornerSiteFor. Read about it here.

BindingGroupSample_IECV.zip