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 aAnonymous
October 01, 2008
There have been several questions on the WPF CodePlex discussion list relating to styling rows and columnsAnonymous
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, XanAnonymous
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 LeeAnonymous
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ützAnonymous
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 removedAnonymous
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, -TreyAnonymous
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
- DataGridColumn.SortDirection does not actually sort the column. DataGridColumn.SortDirection is used
Anonymous
May 18, 2009
The comment has been removedAnonymous
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 removedAnonymous
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 removedAnonymous
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. -TAnonymous
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.