Introduction
The DataGridView control in Windows Forms supports column sorting when users click column headers. If the data source implements IBindingList with sorting support (like DataTable), sorting works automatically. For custom collections like List<T> or BindingList<T>, you need to either switch to a sortable data source, set the SortMode property on columns, or handle sorting manually via the ColumnHeaderMouseClick event. This article covers all three approaches.
Automatic Sorting with DataTable
1// DataTable supports sorting automatically
2var table = new DataTable();
3table.Columns.Add("Name", typeof(string));
4table.Columns.Add("Age", typeof(int));
5table.Columns.Add("City", typeof(string));
6
7table.Rows.Add("Alice", 30, "New York");
8table.Rows.Add("Bob", 25, "Chicago");
9table.Rows.Add("Charlie", 35, "Boston");
10
11dataGridView1.DataSource = table;
12// Clicking column headers sorts automatically — no extra code needed
DataTable implements IBindingListView, which includes full sorting support. The DataGridView detects this and enables header-click sorting with ascending/descending toggle.
Setting Column SortMode
1// Set SortMode to Automatic for each column
2foreach (DataGridViewColumn column in dataGridView1.Columns)
3{
4 column.SortMode = DataGridViewColumnSortMode.Automatic;
5}
6
7// Or set individually
8dataGridView1.Columns["Name"].SortMode = DataGridViewColumnSortMode.Automatic;
9dataGridView1.Columns["Age"].SortMode = DataGridViewColumnSortMode.Automatic;
10
11// Disable sorting on a specific column
12dataGridView1.Columns["Actions"].SortMode = DataGridViewColumnSortMode.NotSortable;
DataGridViewColumnSortMode.Automatic shows the sort glyph (arrow) on the header and handles click events. Programmatic mode shows the glyph but requires you to call Sort() manually. NotSortable disables sorting entirely for that column.
Manual Sorting with List of Objects
1public class Employee
2{
3 public string Name { get; set; }
4 public int Age { get; set; }
5 public decimal Salary { get; set; }
6}
7
8private List<Employee> _employees;
9private bool _sortAscending = true;
10
11private void Form1_Load(object sender, EventArgs e)
12{
13 _employees = new List<Employee>
14 {
15 new Employee { Name = "Alice", Age = 30, Salary = 75000 },
16 new Employee { Name = "Bob", Age = 25, Salary = 65000 },
17 new Employee { Name = "Charlie", Age = 35, Salary = 85000 },
18 };
19
20 dataGridView1.DataSource = new BindingList<Employee>(_employees);
21
22 // Set all columns to Programmatic sort mode
23 foreach (DataGridViewColumn col in dataGridView1.Columns)
24 col.SortMode = DataGridViewColumnSortMode.Programmatic;
25}
26
27private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
28{
29 var columnName = dataGridView1.Columns[e.ColumnIndex].DataPropertyName;
30
31 if (_sortAscending)
32 _employees = _employees.OrderBy(x => x.GetType().GetProperty(columnName).GetValue(x)).ToList();
33 else
34 _employees = _employees.OrderByDescending(x => x.GetType().GetProperty(columnName).GetValue(x)).ToList();
35
36 _sortAscending = !_sortAscending;
37 dataGridView1.DataSource = new BindingList<Employee>(_employees);
38}
When using List<T>, sorting must be handled manually because List<T> does not implement IBindingList. The ColumnHeaderMouseClick event provides the column index to determine which property to sort by.
Using SortableBindingList
1// A reusable sortable binding list
2public class SortableBindingList<T> : BindingList<T>
3{
4 private bool _isSorted;
5 private PropertyDescriptor _sortProperty;
6 private ListSortDirection _sortDirection;
7
8 protected override bool SupportsSortingCore => true;
9 protected override bool IsSortedCore => _isSorted;
10 protected override PropertyDescriptor SortPropertyCore => _sortProperty;
11 protected override ListSortDirection SortDirectionCore => _sortDirection;
12
13 protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
14 {
15 var items = Items as List<T>;
16 if (items == null) return;
17
18 items.Sort((x, y) =>
19 {
20 var xValue = prop.GetValue(x);
21 var yValue = prop.GetValue(y);
22 int result = Comparer<object>.Default.Compare(xValue, yValue);
23 return direction == ListSortDirection.Descending ? -result : result;
24 });
25
26 _isSorted = true;
27 _sortProperty = prop;
28 _sortDirection = direction;
29 OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
30 }
31
32 protected override void RemoveSortCore()
33 {
34 _isSorted = false;
35 }
36}
37
38// Usage — automatic sorting now works with custom objects
39var list = new SortableBindingList<Employee>();
40list.Add(new Employee { Name = "Alice", Age = 30, Salary = 75000 });
41list.Add(new Employee { Name = "Bob", Age = 25, Salary = 65000 });
42
43dataGridView1.DataSource = list;
44// Column headers are now clickable for sorting
SortableBindingList<T> overrides the sorting methods of BindingList<T>, making it compatible with DataGridView's built-in sorting mechanism.
Programmatic Sort
1// Sort programmatically without user interaction
2dataGridView1.Sort(dataGridView1.Columns["Age"], ListSortDirection.Ascending);
3
4// Sort with a custom comparer
5dataGridView1.Sort(new EmployeeComparer());
6
7public class EmployeeComparer : IComparer
8{
9 public int Compare(object x, object y)
10 {
11 var row1 = (DataGridViewRow)x;
12 var row2 = (DataGridViewRow)y;
13 return string.Compare(
14 row1.Cells["Name"].Value?.ToString(),
15 row2.Cells["Name"].Value?.ToString(),
16 StringComparison.OrdinalIgnoreCase);
17 }
18}
Common Pitfalls
Using List<T> as DataSource: List<T> does not support IBindingList sorting. Clicking column headers does nothing. Use DataTable, SortableBindingList<T>, or handle ColumnHeaderMouseClick manually.
SortMode set to NotSortable: The default SortMode for auto-generated columns depends on the data source. If sorting does not work, check that the column's SortMode is Automatic or Programmatic, not NotSortable.
Reassigning DataSource clears selection: Setting DataSource to a new list after sorting resets scroll position and selection. Use SortableBindingList<T> to sort in-place without reassigning.
Virtual mode conflicts: When VirtualMode is true, the DataGridView does not manage data directly. Sorting must be handled entirely in the CellValueNeeded event and your backing data store.
Sort glyph not showing: The sort arrow only appears when SortMode is Automatic or Programmatic. With manual ColumnHeaderMouseClick handling, set SortGlyphDirection on the column explicitly after sorting.
Summary
DataTable and DataView as data sources enable automatic column sorting with no extra code
Set column.SortMode = DataGridViewColumnSortMode.Automatic for built-in sort behavior
For List<T>, implement a SortableBindingList<T> or handle ColumnHeaderMouseClick manually
Use dataGridView1.Sort(column, direction) for programmatic sorting
Always check that SortMode is not NotSortable when sorting does not respond to header clicks