DataGridView in depth
Column types and formatting, sorting and filtering, master-detail layouts, virtual mode for large data, and cell-level validation and errors.
Columns, formatting and sorting
grid.AutoGenerateColumns = false; // define columns explicitly, in order
grid.ReadOnly = true;
grid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
grid.MultiSelect = false;
grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
grid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = "OrderId",
HeaderText = "Order",
FillWeight = 20,
DefaultCellStyle = { Alignment = DataGridViewContentAlignment.MiddleRight }
});
grid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = "Total",
HeaderText = "Total",
DefaultCellStyle = { Format = "C2", Alignment = DataGridViewContentAlignment.MiddleRight }
});
grid.Columns.Add(new DataGridViewCheckBoxColumn
{
DataPropertyName = "Shipped",
HeaderText = "Shipped"
});
grid.DataSource = new BindingSource(bindingList, null);- Bind to a
BindingList<T>so adds and removes flow through; a plainList<T>shows data but ignores later changes. DataPropertyNamemust match the property name exactly - a mismatch renders an empty column with no error.- Sorting is automatic for bound columns; click a column header and the grid sorts the underlying list, so anything holding a row index breaks.
- Use
e.SortCompareinSortComparewhen a column holds numbers stored as text.
Master-detail without extra work
var customers = new BindingSource { DataSource = customerList };
var orders = new BindingSource { DataSource = customers, DataMember = "Orders" };
customerGrid.DataSource = customers;
orderGrid.DataSource = orders; // re-queries whenever the master selection moves
// and a live count in the header
customers.CurrentChanged += (s, e) =>
orderGrid.Columns["Total"].HeaderText =
"Total (" + orderGrid.Rows.Count + ")";Chaining BindingSource objects gives you master-detail for free: the child list is re-evaluated from the current master item. It only works when the master object exposes a collection property, which is one more reason to model the data before wiring the grid.
Virtual mode and large data
Above roughly 100,000 rows, populating rows individually becomes the bottleneck. Virtual mode hands control of row content to you: the grid asks for a row only when it needs to paint it.
grid.VirtualMode = true;
grid.RowCount = rows.Count; // the grid no longer owns the data
grid.CellValueNeeded += (s, e) =>
{
var row = rows[e.RowIndex];
e.Value = e.ColumnIndex switch
{
0 => row.Id,
1 => row.Customer,
2 => row.Total,
_ => null
};
};
grid.CellValuePushed += (s, e) =>
{
var row = rows[e.RowIndex];
if (e.ColumnIndex == 1) row.Customer = (string)e.Value;
edits.Add(row.Id);
};| Symptom | Cause | Fix |
|---|---|---|
| Grid freezes while filling | One row added per item with layout enabled | Call SuspendLayout, add, then ResumeLayout |
| Slow scroll on 200k rows | Bound mode materialises every row | Switch to virtual mode |
| Flicker while scrolling | DoubleBuffered is false on the grid | Subclass and set it in the constructor |
| Sort resets the selection | Sorting rebuilds the row collection | Capture and restore the selected key, not the row index |
| Blank cells after a refresh | Property names changed, bindings stale | Reassign DataSource, then check DataPropertyName |
💡
In virtual mode you own sorting, filtering and selection. Keep a stable row key, do the work on a background thread, and only touch the grid through
BeginInvoke - the grid raises its events on the UI thread and expects answers quickly.FAQ
DataGridView or ListView?
Use
DataGridView when cells are edited, sorted or bound to objects. ListView in details view is lighter for read-only lists with icons and grouping.How do I show a per-row error?
Handle
CellValidating, call e.Cancel when the value is wrong, and set rows[e.RowIndex].ErrorText. Handle RowValidating the same way for rules that span several cells.Related
Validation and user input handling Data binding and when not to use WinForms
Last refreshed 2026-09-18.