Overview of the editing features in the WPF DataGrid

Introduction

I’m going to talk a little on the editing features of the DataGrid. I will dive deep into a couple things but overall I just want to give you a good idea of the APIs you can work with and how to tweak a couple things. So basically I will be introducing a bit of the internal implementation that may be beneficial for you to understand, the editing commands and how to customize, and the editing events that are available.

Background on the data source and DataGrid working together

Some major updates were done in 3.5 SP1 to enable this editing scenario for the DataGrid. In particular, the IEditableCollectionView interface was added and ListCollectionView and BindingListCollectionView were updated to support this interface. Read more on IEditableCollectionView here. As a refresher, ListCollectionView is the view created for an ItemsControl when your data source implements IList such as ObservableCollection<T> . BindingListCollectionView is the view created for an ItemsControl when your data source implements IBindingList such as an ADO.net DataView.

The DataGrid uses IEditableCollectionView underneath the covers to support transactional editing as well as adding and removing data items. In the implementations of IEditableCollectionView in ListCollectionView and BindingListCollectionView, they both follow the IEditableObject pattern where the calls to IEditableCollectionView.EditItem, IEditableCollectionView.CancelEdit, and IEditableCollectionView.CommitEdit end up calling IEditableObject.BeginEdit, IEditableObject.CancelEdit, and IEditableObject.CommitEdit respectively. It is in the implementation of IEditableObject where you can provide the functionality to commit or rollback changes to the data source. For an ADO.net DataTable, you get this functionality for free as DataRowView already implements IEditableObject. When using an ObservableCollection<T> you will have to provide your own implementation for IEditableObject for T. See the MSDN documentation of IEditableObject for an example of how to implement it for a data item.

Things to keep in mind:

· DataGrid checks for IEditableCollectionView’s CanAddNew, CanCancelEdit, and CanRemove properties before executing the EditItem, CancelEdit, or CommitEdit methods. So if editing appears to not work for some reason, be sure to check that it is able to edit.

 

ListCollectionView

BindingListCollectionView

CanAddNew

True if there is not an edit transaction occurring, if the collection is not a fixed size, and if it can create an object of the correct type.

True if there is not an edit transaction occurring, if the collection is not a fixed size, and if the collection is not read-only.

CanCancelEdit

True if the edited item implements the IEditableObject interface.

True if the edited item implements the IEditableObject interface.

CanRemove

True if the collection is not a fixed size and if an add or edit transaction is not occurring.

True if the collection is not a fixed size and if an add or edit transaction is not occurring and if the collection is not read-only.

 

For information on how the data binding is hooked to the UI, see this post on Stock and Template Columns and Dissecting the Visual Layout.

Note about DataGrid properties related to editing

There are three properties on DataGrid to control editing/adding/deleting. These properties are:

· CanUserAddRows

· CanUserDeleteRows

· IsReadOnly (not in CTP)

They are basically self-documenting but beware of CanUserAddRows and CanUserDeleteRows as they can appear a little magical. Their values are coerced based on other properties such as DataGrid.IsReadOnly, DataGrid.IsEnabled, IEditableCollectionView.CanAddNew, and IEditableCollectionView.CanRemove. So this is another thing to watch out for when editing. If you run into a situation where you set CanUserAddRows or CanUserDeleteRows to true but it is changed to false automatically, check that the conditions below are met.

CanUserAddRows

True if the DataGrid is not ReadOnly and IsEnabled, CanUserAddRows is set to true and IEditableCollectionView.CanAddNew is true

CanUserDeleteRows

True if the DataGid is not ReadOnly and IsEnabled, CanUserDeleteRows is set to true and IEditableCollectionView.CanRemove is true

 

Working with editing commands

Default commands have been added to the DataGrid to support editing. These commands and their default input bindings are:

· BeginEditCommand (F2)

· CancelEditCommand (Esc)

· CommitEditCommand (Enter)

· DeleteCommand (Delete)

When each command is executed it will do some internal housekeeping and at some point it will call into its IEditableCollectionView counterpart. For example, BeginEditCommand calls into IEditableCollectionView.EditItem and CancelEditCommand calls into IEditableCollectionView.CancelItem.

DataGrid also has APIs where you can call editing commands programmatically. Not surprisingly, the APIs are BeginEdit, CancelEdit, and CommitEdit.

Adding new input gestures

Adding new input gestures is similar to any other control in WPF. The DataGrid commands are added through the CommandManager so one possible solution would be to register a new InputBinding with the CommandManager:               

CommandManager.RegisterClassInputBinding(

    typeof(DataGrid),

                new InputBinding(DataGrid.BeginEditCommand, new KeyGesture(Key.<new key>)));

Disabling commands

You can disable any of the editing commands by attaching to the CommandManager.CanExecuteEvent, looking for the command to disable, then setting e.CanExecute accordingly. Here is an example:

_handler = new CanExecuteRoutedEventHandler(OnCanExecuteRoutedEventHandler);

EventManager.RegisterClassHandler(typeof(DataGrid), CommandManager.CanExecuteEvent, _handler);

void OnCanExecuteRoutedEventHandler(object sender, CanExecuteRoutedEventArgs e)

{

  RoutedCommand routedCommand = (e.Command as RoutedCommand);

  if (routedCommand != null)

  {

    if (routedCommand.Name == "<command name>")

    {

      e.CanExecute = <some condition>;

      if(!e.CanExecute)

        e.Handled = true;
}
}

}

 

This is a relatively cumbersome way of disabling an editing command from being executed. Fortunately events were added to the DataGrid so that they can be canceled in a more direct fashion (although no direct event exists for the DeleteCommand).

Editing events on the DataGrid

These are the editing events that you can listen to and cancel the operation or modify data:

· RowEditEnding

· CellEditEnding

· BeginningEdit

· PreparingCellForEdit

· InitializingNewItem

In the first three events; RowEditEnding, CellEditEnding, and BeginningEdit, you have access to the DataGridRow and DataGridColumn that is being committed, cancelled, or edited. These events are called right before the actual operation will occur. You have the ability to cancel the operation completely by setting e.Cancel to true. RowEditEnding and CellEditEnding both have a parameter EditAction which lets you know if it is a commit or cancel action. From CellEditEnding, you also have access to the editing FrameworkElement. This gives you the ability to set or get properties on the visual itself before a cell commit or cancel.

PreparingCellForEdit is fired right after the cell has changed from a non-editing state to an editing state. In this event you have the ability to modify the contents of the cell. InitializingNewItem is called when a new item is added and in this event you have the option to set any properties on the newly created item. This event is good when you want to set initial default values on a new item.

Summary

So hopefully this will give you an idea on what APIs you have available for editing scenarios as well as some gotchas on how particular editing features work. If there is other editing questions/topics that you would like to read about please let me know. Another item that I plan to discuss in the future is how row validation will be tied into the DataGrid, so stay tuned!