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 plain List<T> shows data but ignores later changes.
  • DataPropertyName must 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.SortCompare in SortCompare when 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);
};
SymptomCauseFix
Grid freezes while fillingOne row added per item with layout enabledCall SuspendLayout, add, then ResumeLayout
Slow scroll on 200k rowsBound mode materialises every rowSwitch to virtual mode
Flicker while scrollingDoubleBuffered is false on the gridSubclass and set it in the constructor
Sort resets the selectionSorting rebuilds the row collectionCapture and restore the selected key, not the row index
Blank cells after a refreshProperty names changed, bindings staleReassign 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.

Validation and user input handling Data binding and when not to use WinForms

Last refreshed 2026-09-18.