5 More Random Gotchas with the WPF DataGrid
1. DataGridColumn.SortDirection does not actually sort the column.
DataGridColumn.SortDirection is used to queue the visual arrow in the DataGridColumnHeader to point up, down, or to not show. To actually sort the columns other than clicking on the DataGridColumnHeader, you can set the DataGrid.Items.SortDescriptions programmatically.
Here is an example of an ICommand used to trigger grouping of a particular column in the DataGrid. In addition to grouping it, I also sort it on the same column name that I grouped it with. Note that this code will programmatically sort the column but will not update the sorting arrow since I did not update SortDirection.
public ICommand GroupColumn
{
get
{
if (_groupColumn == null)
{
_groupColumn = new RelayCommand<object>(
(param) =>
{
// the delegate is passed in the column header
// name which matches the Binding on the column
string header = param as string;
//
// SearchResults is the ItemsSource that DataGrid
// is bound to. Below I programmatically group and
// sort the collection based on the header name
//
SearchResults.GroupDescriptions.Add(
new PropertyGroupDescription(header));
SearchResults.SortDescriptions.Add(
new SortDescription(header, ListSortDirection.Ascending));
});
}
return _groupColumn;
}
}
2. DataGridColumns cannot be styled as they are not FrameworkElements.
DataGridColumn derives from DependencyObject, not FrameworkElement, and therefore cannot be styled like many of the other elements on DataGrid. You can think of a DataGridColumn as data storage for what each cell’s content will be template with and a couple other properties like the width of each cell and how it is sorted. They are not meant to be visual elements like DataGridCell or DataGridRow.
3. While editing a row, the row is committed by pressing Enter, pressing tab on the last cell of the row, losing focus, clicking the column header to sort the column, programmatically calling DataGrid.CommitEdit, updating DataGrid.CurrentCell to a cell outside the current row, or updating DataGrid.CurrentItem.
The actions described above are the default actions to commit a row. One action that deserves a little more clarification is the commit on losing focus. Each DataGridCell listens for changes to its IsKeyboardFocusWithin property and when it changes it updates the CurrentCell accordingly (which causes the row to commit).
4. Disable commit behavior through the DataGrid.RowEditEnding event
In the previous gotcha I explain all the ways that a row can be committed. If you want to customize that behavior, the best way to do it is through DataGrid.RowEditEnding. This event is fired at the start of any and all row commits and you have the ability to cancel the commit behavior in this event through DataGridRowEditEndingEventArgs.Cancel. See more about editing behavior here.
5. Customize focus behavior after a row commit through the DataGrid.RowEditEnding event
While there isn’t a DataGrid.RowEditEnded at this time (March 2009 Release), you can use the Dispatcher trick to get custom behavior after a row commit has occurred. In DataGrid.RowEditEnding, you can specify a Dispatcher.BeginInvoke so that the code in the delegate is executed after the row commit has completed. Here is some example code which sets focus to the second cell of the next row and puts it in edit mode but only when I press tab on the last cell of the row before the NewItemPlaceholder.
private void OnRowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (e.EditAction == DataGridEditAction.Commit)
{
//
// custom commit action:
// tab specific behavior for editing workflow.
// moves to the next row and opens the second cell for edit
// if the next row is the NewItemPlaceholder
//
if (_isTabPressed &&
e.Row.Item == dataGrid.Items[dataGrid.Items.Count - 2])
{
// set the new cell to be the last row and the second column
int colIndex = 1;
var rowToSelect = dataGrid.Items[dataGrid.Items.Count - 1];
var colToSelect = dataGrid.Columns[colIndex];
int rowIndex = dataGrid.Items.IndexOf(rowToSelect);
// select the new cell
dataGrid.SelectedCells.Clear();
dataGrid.SelectedCells.Add(
new DataGridCellInfo(rowToSelect, colToSelect));
this.Dispatcher.BeginInvoke(new DispatcherOperationCallback((param) =>
{
// get the new cell, set focus, then open for edit
var cell = Helper.GetCell(dataGrid, rowIndex, colIndex);
cell.Focus();
dataGrid.BeginEdit();
return null;
}), DispatcherPriority.Background, new object[] { null });
}
}
}
See more WPF DataGrid gotchas.
Comments
Anonymous
April 14, 2009
Good tidbits - can you post your: var cell = Helper.GetCell(DataGrid, rowIndex, colIndex); Helper? Thanks.Anonymous
April 14, 2009
Jason, See this thread, http://wpf.codeplex.com/Thread/View.aspx?ThreadId=34065.Anonymous
April 15, 2009
Thank you for submitting this cool story - Trackback from DotNetShoutoutAnonymous
April 16, 2009
How do I use your code in #1 on a DataGrid to sort from codebehind?Anonymous
April 17, 2009
JP Chow, I bind this command to a menu item on a context menu of the DataGridColumnHeader. The source of the binding (which defineds GroupColumn) comes from the ViewModel of SearchOptionsView. <ContextMenu x:Key="cm_rowHeaderMenu"> <MenuItem Name="mi_group" Header="Group Clause" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SearchOptionsView}}, Path=DataContext.GroupClause}"/> </ContextMenu>Anonymous
April 21, 2009
I am using MVVM pattern. Datagrid is bound to a ListCollectionView property on a ViewModel. I would like grid to retain sorting when item is added. Any ideas why this code not work? public ListViewCollection Items { get { return this.items; } private set { if (this.items == value) { return; } System.ComponentModel.SortDescriptionCollection sortedColumns = null; if (this.items != null) { this.items.Filter = null; //save sortingDescriptions sortedColumns = this.items.SortDescriptions; } this.items = value; this.items.SortDescriptions.Clear(); //Reapply sorting if (sortedColumns != null) { for (int i = 0; i <= sortedColumns.Count - 1; i++) { this.items.SortDescriptions.Add(new System.ComponentModel.SortDescription(sortedColumns[i].PropertyName, sortedColumns[i].Direction)); } } } }Anonymous
April 23, 2009
sachinkendale, Have you tried applying Items.Refresh()?Anonymous
April 27, 2009
this is how i got it working. public ListCollectionView Items { get { return this.items; } set { if (this.items == value) { return; } Dictionary<string, ListSortDirection> sortOrder = new Dictionary<string,ListSortDirection>(); if (this.items != null) { for (int i = 0; i <= this.items.SortDescriptions.Count - 1; i++) { sortOrder.Add(this.items.SortDescriptions[i].PropertyName, this.items.SortDescriptions[i].Direction); } } this.items = value; this.OnPropertyChanged(new PropertyChangedEventArgs("Items")); if (sortOrder.Count > 0) { foreach(string key in sortOrder.Keys) { this.items.SortDescriptions.Add(new System.ComponentModel.SortDescription(key, sortOrder[key])); } } this.items.Refresh(); } }Anonymous
April 30, 2009
I've run into a caveat with the ElementStyle. If I use a binding to set the value of the tooltip as below: <code> <dg:DataGridTextColumn.ElementStyle > <Style TargetType="{x:Type TextBlock}"> <Setter Property="ToolTip" Value="{Binding Path=Week01, StringFormat='Workload: {0:D2}'}" /> </Style> </dg:DataGridTextColumn.ElementStyle> </code> This code displays the tooltip for each cell as expected, but for some reason, the string format is ignored. It doesn't produce an error; it's just ignored.Anonymous
May 26, 2009
Vincent, Could you give an example(or code snippet) of how to sync two datagrids' vertical scrollbars? Many thanks!Anonymous
May 26, 2009
Daniel, Take a look at the solution here, http://news.infragistics.com/forums/p/6743/29223.aspx#29223.Anonymous
May 27, 2009
Hi, this seemed like a good place to post a problem I have regarding the commit behaviour on a WPF DataGrid. My grid is calling RowEditEnding and BeginningEdit every time I press Tab to move to the next field (not just the last field on the row, but every field). Can anybody think why it might be behaving like this? Thanks, Graham.Anonymous
July 09, 2009
Hi Vincibal I have binded the wpf datagrid. then modify the data and rebind the data, but wpf datagrid does not reflect. It display blank Im using datacontext(observablecollection) Thanks in advanceAnonymous
August 18, 2009
Stupid question but how do I get a value (ie check for) _isTabPressed?Anonymous
September 08, 2009
Jason, You can listen for the KeyDown event on the DataGrid and check for the TabKey being the key that is pressed.Anonymous
March 17, 2010
Hi Vincent i am facing issues of rendering performance of data grid. i have around 30 coulmns to display and half of it are of type DataGridComboboxColumn that are bound to different ItemsSources. Columns are generated dynamically. Even it displays only 100 records, takes more than 5sec to render data on screen. While debug, i could see data fetch and column generation happens quickly but screen shows blank rows for around 5/10secs(rendering) and then displays data. During render process, CPU utilization is 100% and app gets into not responding stage. Any solution to this? Thanks ShailendraAnonymous
March 18, 2010
I recently had to switch from a ListView to the DataGrid because I wanted to persist the column order along with the column widths. And the ListView didn't support this. So one nice feature that the ListView had was that it has three SelectionModes (Extended, Multiple, Single). The 'Multiple' mode allows the user to select multiple rows without having to hold the <Control> key or <Shift> key. You can simply click other rows without the other items being un-selected. The DataGrid on the other hand only has two Selection Modes (Extended, Single) so I have to hold the <Control> <Shift> key to select multiple rows. Am I missing something? Is there a way to get this to behave like the ListView's 'Multiple' mode? Thanks!