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!

Comments

  • Anonymous
    October 01, 2008
    PingBack from http://www.easycoded.com/overview-of-the-editing-features-in-the-wpf-datagrid/

  • Anonymous
    October 01, 2008
    As you might have heard, .NET Framework 3.5 SP1 and Visual Studio 2008 SP1 are out today! There are a

  • Anonymous
    October 01, 2008
    There have been several questions on the WPF CodePlex discussion list relating to styling rows and columns

  • Anonymous
    November 10, 2008
    If you derive from ObservableCollection you should make the derived type generic: public class SomeCollection<T> :  ObservableCollection<T> otherwise IEditableCollectionView.CanAddNew will return false if the collection is empty.

  • Anonymous
    November 10, 2008
    orsino, That is correct.

  • Anonymous
    November 21, 2008
    Can you post a source code and demo application for implementing editting feature in wpf datagrid using Entity Framework? Thanks, Xan

  • Anonymous
    November 30, 2008
    Is there a way to get the CanAddNew property to return true when ObservableCollection contains an interface object? Some magic factory attribute pointer or something? Regards Lee

  • Anonymous
    December 03, 2008
    Lee, Unfortunately there isn't a direct way to get an Interface object to create itself with IECV.  

  • Anonymous
    December 15, 2008
    Hi There, I use latest WPF Toolkit DataGrid. But setting up CanUserAddRows to true doesnt work. CanUserDeleteRows and IsReadOnly works though (to whatver value is set for them). My grid hosts a text column, a combobox column and a template column. I have checked that IsReadonly is False and IsEnabled is True. What am I missing here? Thx in Advance, Vinit.

  • Anonymous
    December 16, 2008
    Vinit, Is IECV.CanAddNew true?  Can you describe your data source a bit more.

  • Anonymous
    February 27, 2009
    I try to use the Datagrid with an ObservableCollection. I try to persist my data in RowEndEdit event but if I get persist errors a cannot handle them. I also cannot see any CommittingEdit event as described above. How can catch persist errors correctly? Thx Anschütz

  • Anonymous
    March 01, 2009
    Here again, my actual Problem is: When I try to persist data in RowEditEnding and Errors occure I want the user to be able to react with a Dialog. A MessageBox or Window whatever. But any call like MessageBox.Show() or Window win = new Window(); win.Show() causes the DataGrid to call the RowEditEnding() Method again. Where can I put such I Dialog call? Thx for answer.

  • Anonymous
    March 03, 2009
    Anschutz, Maybe you can make a special case that when an error occurs you cancel the commit in RowEditEnding.  Then when the user fixes it in the dialog you programmatically commit the data.  Also, CommittingEdit has been replaced by RowEditEnding.  I need to update two of the APIs in this post.

  • Anonymous
    March 26, 2009
    The comment has been removed

  • Anonymous
    March 27, 2009
    wpf.wanna.be, I don't know about all scenarios but some have been fixed in dev10.  As far as a workaround, there aren't any easy and straightforward ones.  Maybe if you could provide some code we can take a look.

  • Anonymous
    March 27, 2009
    Vincent, thanks for a really quick response. As for the code, consider the following. class MyCollection<T> : ObservableCollection<T> {...} This works correctly in DataGrid with respect to ListCollectionView.CanAddRow. But serialization support is close to none for XamlReader/XamlWriter when it comes to generic classes. I have been trying all day to get a MarkupExtension to work but I have had limited success in reading a generic class from XAML. Writing still does not work. On the other hand, the following non-generic class: class MyCollection : ObservableCollection<T> {...} ...works perfectly with both XamlReader/XamlWriter but there is a MAJOR problem in that ListCollectionView.CanAddRow (wrongly) returns FALSE if a collection is empty. As soon as I programmatically add at least one item to this collection, CanAddRow starts returning TRUE. So, there you go. If you have any ideas, I would be grateful.

  • Anonymous
    March 27, 2009
    Vincent- Sorry if this is a dumb question, but I am having a little trouble committing changes to my db with the DataGrid. I have a simple WPF application with a LINQ to Entities data model pulling from my database. In the DataGrid I have explicitly set the columns' binding modes to TwoWay, and its ItemSource = myEntities.Customers. From here I can edit on the grid, add new items, etc. and all the changes are showing in the grid, but none of the changes are shoveled back to the db. What am I missing here to make the magic happen? It was a little clearer to me using a ListView, but the grid is definitely the tool I want to use for CRUD and less programming. twhite @ fire . ca . gov thank you, -Trey

  • Anonymous
    March 31, 2009
    Trey, Are you doing the save changes part with your entities, myEntities.SaveChanges?

  • Anonymous
    March 31, 2009
    wpf.wanna.be, The scenario that you are describing is fixed in dev10.  Unfortunately, there are no magic workarounds for it that haven't already been described.  

  • Anonymous
    April 02, 2009
    Hi , Im having requirement like this, Im having n row and user will have save and reset button out side of datagrid.when i click on reset button ,is there any possiblity to reset all rows in datagrid which r modified.

  • Anonymous
    April 08, 2009
    Mahender, Currently you can only reset one row at a time.  You will have to create this custom editing functionality yourself.  

  • Anonymous
    April 12, 2009
    wpf.wanna.be, Vinsibal, I found that if you access the CanAddRow of ListCollectionView once before you use the collection, magically the CanUserAddRows of the DataGrid becomes true. Strange! IEditableCollectionView ecv = new ListCollectionView(myRecordCache); bool b = ecv.CanAddNew;   // dummy access MyGrid.DataContext = ecv;

  • Anonymous
    April 14, 2009

  1. DataGridColumn.SortDirection does not actually sort the column. DataGridColumn.SortDirection is used
  • Anonymous
    May 18, 2009
    The comment has been removed

  • Anonymous
    May 18, 2009
    Chris, You could try keeping track of the state like this: <toolkit:DataGrid Grid.Row="1"                          x:Name="MyDataGrid"                          ItemsSource="{Binding Items}"                                                    AutoGenerateColumns="False"                          Validation.Error="MyDataGrid_Error">            <toolkit:DataGrid.Columns>                <toolkit:DataGridTextColumn Header="First Name" Binding="{Binding Path=Item.FirstName, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" />                                            </toolkit:DataGrid.Columns>        </toolkit:DataGrid> private bool _isInErrorState = false;        private void OnDataGridValidationError(object sender, ValidationErrorEventArgs e)        {            if (e.Action == ValidationErrorEventAction.Added && !_isInErrorState)            {                _isInErrorState = true;                MessageBox.Show(e.Error.ErrorContent.ToString());                _isInErrorState = false;            }        }

  • Anonymous
    May 18, 2009
    Chris, MyDataGrid_Error and OnDataGridValidationError are the same thing.  Forgot to update it when I was copy/pasting.

  • Anonymous
    May 18, 2009
    The comment has been removed

  • Anonymous
    July 06, 2009
    Vincent- I have a WPF application using the DataGrid from the toolkit. I have written it using an MVVM pattern architecture. Behind it all sits a DAL that persists changes to an Oracle DB. In particular I have a window that allows users to edit a form we process for business. It has two parts - a OTM relationship between the header material and totals (the parent record), and specific incident material (child records). This window is bound to a ViewModel of the parent record, who's child records are exposed as an ObservableCollection<ChildRecordViewModels>. The DataGrid is then bound to this observable collection of children. When a user updates the record, persistence is happening for the entire form, and works flawless - it iterates through the collection and does well. However, I am stuck trying to figure out how to ADD and DELETE records from this grid bound to an OC within a view model. Any suggestions?

  • Anonymous
    July 07, 2009
    Trey, This particular article has some good info on inserts and deletes, http://www.codeproject.com/KB/WPF/WPFDataGridExamples.aspx.  Let me know if that helps.

  • Anonymous
    July 22, 2009
    The comment has been removed

  • Anonymous
    July 22, 2009
    Vincent- More on the last post... I am trying simply to ensure that the "edit state" of both the parent and child view models is for the life of the window. My current implementation allows for the parent to function so... but the inherent functionality of the DataGrid calling End/CancelEdit() methods on a lost focus means their initial state is lost then. Is there a way to prevent a DataGrid from calling EndEdit() or CancelEdit() ? If so, I would simply modify my Ok and Cancel button commands to not only handle the parent's state, but loop through the OC and an either End or Cancel each of them. -T

  • Anonymous
    July 23, 2009
    Trey, There is a way to prevent the DataGrid from calling CancelEdit().  Listen to DataGrid.RowEditEnding and you can cancel it there based on criteria you are looking for.  Hope that helps.

  • Anonymous
    September 18, 2009
    Vincent: I have a datagrid bound to an ObservableCollection of class X, and has CanUserAddRows set to True.  Is it possible to modify a property of class X when the user adds a new row (before it is displayed to the user)? Thank you.

  • Anonymous
    November 01, 2009
    This tripped me up for hours today, but the when using a collection of T, T must have a parameterless constructor or you won't get the blank row.

  • Anonymous
    April 11, 2010
    Problem: after I removed a row from the ObservableCollection, my datagrid became uneditable. All settings above were correct. Answer found: Before removing row, validation was performed and I got an error (which I didn't see since the row is removed). Since row was removed, I had no indication that I had a validation error.