WPF Standard Control Demo App › DataGrid
DataGrid List
DataGrid shows a collection as a table of rows and columns, with built-in column sorting, in-place editing, and adding and deleting rows.
Which columns are generated
DataGrid derives from MultiSelector, Selector, and ItemsControl. With the default AutoGenerateColumns="True", it creates one column per public property it can bind to. In the measured class, which declares Zeta, Alpha, and Mid in that order, the columns appeared in declaration order, not alphabetically; the order is what was observed, not something the documentation guarantees. A property without a setter became a read-only column.
In the measured item types, every public property became a column, while the indexer this[string columnName] did not. The demo app’s items implement IDataErrorInfo, so the generated columns were Name, Value, and a read-only Error column taken from the interface. When the item type has members that should not be shown, set AutoGenerateColumns to False and declare the columns as DataGridTextColumn, DataGridCheckBoxColumn, DataGridComboBoxColumn, and so on. In edit mode their cells hold a TextBox, a CheckBox, and a ComboBox.
Adding, deleting, and read-only grids
CanUserAddRows and CanUserDeleteRows decide whether the user can add a row through the empty row at the bottom and delete selected rows with the Delete key. Both default to True. The new-item row depends on the source: it appeared for a List<T> and an ObservableCollection<T>. It did not appear for an array or for a type without a parameterless constructor. In those cases CanUserAddRows became False, without an exception, so if users cannot add rows, check the source. With the second of three rows selected, the Delete key removed it from the source collection.
IsReadOnly turns off editing for the whole grid. With IsReadOnly="True", CanUserAddRows became False. A column cannot opt back in: a column with IsReadOnly="False" set explicitly still reported False, but BeginEdit() on its cell returned False and no editing started.
| case | measured |
|---|---|
| auto columns, declared Zeta, Alpha (get-only), Mid | Zeta (editable), Alpha (read-only), Mid (editable) |
| auto columns, demo SampleItem (IDataErrorInfo) | Name (editable), Value (editable), Error (read-only) |
| 2 columns, FrozenColumnCount = 3 (demo slider max) | no exception, shown; value 2 |
| demo data, row 3 Error, RowValidationRules empty: HasError start / edited | False / False |
| demo data, row 3 Error, RowValidationRules + DataErrorValidationRule: HasError start / edited | True / True |
| DataGrid IsReadOnly=True: CanUserAddRows | False (Default) |
| column IsReadOnly=False set: IsReadOnly / BeginEdit() | False / False |
| new-item row / CanUserAddRows, ItemsSource List<Person> | shown / True |
| new-item row / CanUserAddRows, ItemsSource ObservableCollection<Person> | shown / True |
| new-item row / CanUserAddRows, ItemsSource Person[] (array) | not shown / False |
| new-item row / CanUserAddRows, ItemsSource List<T> without parameterless ctor | not shown / False |
| row 2 selected, Delete key: items left | 2 (Name 1, Name 3) |
Columns, read-only settings, validation, and adding and deleting rows. Measured on .NET 10 / Windows 11.
Row validation needs a rule
RowValidationErrorTemplate is what a row shows when its validation fails. A row only fails validation when a rule checks it, and RowValidationRules is empty by default. In the demo app’s data, the third row returns an IDataErrorInfo.Error, but that row never had Validation.HasError set, even after it was edited, so with this data and no rule the template did not appear. After adding a DataErrorValidationRule to RowValidationRules, the third row had a validation error. Add a validation rule if rows should show errors.
Editing, and sorting while a cell is being edited
Keyboard editing was measured on a focused Name cell. F2 started editing, with a TextBox in the cell. Typing “Changed” and pressing Esc ended editing and left the source unchanged. Pressing F2 again, typing “Committed”, and pressing Enter wrote “Committed” to the source.
Sorting is built in: clicking a column header sorted ascending, then descending, then ascending again. A third click does not remove the sort. How to Reset DataGrid Sorting in WPF shows how to clear it from code.
While a cell was being edited, adding a SortDescription to the items threw InvalidOperationException. CommitEdit() and CancelEdit() without arguments did not prevent it: sorting still threw after either. After CommitEdit(DataGridEditingUnit.Row, true) or CancelEdit(DataGridEditingUnit.Row), sorting worked. End the row edit this way before sorting, filtering, or grouping from code.
Grouping comes from the collection view, and the DataGrid shows groups only when it has a GroupStyle. Without one, a grouped view produced no group headers.
Rows on screen: virtualization, frozen columns, and alternating rows
EnableRowVirtualization decides whether DataGridRow containers outside the view are created. EnableColumnVirtualization does not affect the DataGridColumn definitions, which are always kept; it decides whether the cells and column headers of columns outside the view are created (column virtualization was not measured here). The defaults are True for rows and False for columns. With 1,000 rows in a DataGrid 360 wide and 200 high, 11 DataGridRows existed. By default, grouping turns row virtualization off, because VirtualizingPanel.IsVirtualizingWhenGrouping is False: grouped with a GroupStyle, all 1,000 rows were created. With it set to True, 18 rows were created in the same DataGrid. Set it on grouped grids.
FrozenColumnCount is the number of leftmost columns that stay in place during horizontal scrolling; the default is 0. With four 100-wide columns in a DataGrid 200 wide, scrolling 60 to the right moved the first column from x = 7 to -53. With FrozenColumnCount="1", it stayed at 7 while the second column moved. A value of 3 with two columns raised no exception and was reduced to 2.
AlternatingRowBackground sets the background of every other row. Setting only this property was enough: AlternationCount became 2, and the four rows alternated between white and the alternating brush.
| case | measured |
|---|---|
| Name cell, F2: IsEditing / editor | True / TextBox |
| typed "Changed", Esc: IsEditing / source | False / "Name 1" |
| F2, typed "Committed", Enter: source | "Committed" |
| editor by column type | Text: TextBox, CheckBox: CheckBox, ComboBox: ComboBox |
| Name header clicked 3 times: SortDirection | Ascending -> Descending -> Ascending |
| editing a cell, Items.SortDescriptions.Add(...) | InvalidOperationException |
| after CommitEdit() | InvalidOperationException |
| after CancelEdit() | InvalidOperationException |
| after CommitEdit(DataGridEditingUnit.Row, true) | no exception |
| after CancelEdit(DataGridEditingUnit.Row) | no exception |
| only AlternatingRowBackground set: AlternationCount / rows | 2 / #FFFFFFFF, #FFD3D3D3, #FFFFFFFF, #FFD3D3D3 |
| 1,000 rows: DataGridRows / GroupItems | 11 / 0 |
| 1,000 rows grouped, with GroupStyle: DataGridRows / GroupItems | 1000 / 10 |
| 1,000 rows grouped, no GroupStyle: DataGridRows / GroupItems | 11 / 0 |
| 1,000 rows grouped, GroupStyle, IsVirtualizingWhenGrouping=True: DataGridRows / GroupItems | 18 / 1 |
| FrozenColumnCount=0, scrolled 60: x of columns 1, 2 before / after | 7, 107 / -53, 47 |
| FrozenColumnCount=1, scrolled 60: x of columns 1, 2 before / after | 7, 107 / 7, 47 |
Editing, sorting, alternating rows, virtualization, and frozen columns. Measured on .NET 10 / Windows 11.
The defaults of the other switches
SelectionModeandSelectionUnitdefault toExtendedandFullRow.ColumnWidth,MaxColumnWidth, andMinColumnWidth, the default width of columns that do not set their own and its limits, wereSizeToHeader,Infinity, and 20.CanUserReorderColumns,CanUserResizeColumns,CanUserResizeRows, andCanUserSortColumnsall default toTrue.RowDetailsVisibilityMode, which decides when theRowDetailsTemplateis shown under a row, defaults toVisibleWhenSelected, andAreRowDetailsFrozen, which keeps the details in place during horizontal scrolling, toFalse.GridLinesVisibilitydefaults toAll, andHeadersVisibilitytoAll.
Trying it in the demo app

The DataGrid page of the demo app has a section for each of its twenty property groups, including all the properties above. The data holds items with Name and Value that implement IDataErrorInfo, and the FrozenColumnCount slider goes up to 3 for two columns. The “Show Code” link under each section displays its XAML. The following XAML is the FrozenColumnCount section (DataGridUsageControl.xaml), with the styles and the surrounding GroupBoxes left out and the namespace declarations added. Items holds items with Name and Value. The snippet relies on the DataContext it inherits: in the demo app, DataGridUsageControl sets its view model, which has Items, as the DataContext, so set a matching DataContext when you use the snippet by itself:
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Slider x:Name="PropertyFrozenColumnCount"
IsSnapToTickEnabled="True" Maximum="3" Minimum="0" />
<DataGrid x:Name="ResultFrozen" Height="150"
AutoGenerateColumns="False"
FrozenColumnCount="{Binding Value, ElementName=PropertyFrozenColumnCount}"
ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridTextColumn Width="150" Binding="{Binding Name}" Header="Frozen" />
<DataGridTextColumn Width="600" Binding="{Binding Value}" Header="Scrollable" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
Related controls and articles
- How to Implement DataGrid Sorting in WPF — which columns can be sorted and how.
- How to Reset DataGrid Sorting in WPF — clearing a sort from code.
- Switching Controls Between Display and Edit Modes in WPF DataGrid Cells — cell templates for display and editing.
- ListView — columns through
GridView, without editing.
Source code and how it was measured
Every behavior on this page was measured by running it on .NET 10 / Windows 11, using DataGridDemoScene in this site’s screenshot tool. Header clicks were reproduced by calling the column header’s click handling (OnClick), and keys by sending key events to a displayed window. Positions are the values on the measuring machine.