How to Implement DataGrid Sorting in WPF

Learn the basics of DataGrid sorting and practical implementation patterns for real-world WPF applications.

Overview

The WPF DataGrid control provides built-in column sorting out of the box.
When CanUserSortColumns is set to true (the default), clicking a column header sorts the rows by that column.
Built-in sorting covers the common cases, but real applications frequently need programmatic sorting, custom comparison rules, or a way to reset the grid to its unsorted state.
This article covers the essentials and explores patterns for each of those requirements.

Prerequisites / Environment

The examples assume the grid is bound to an observable collection of a Product type that exposes Name and Price properties.

Enabling Default Sorting

By default, every column in a DataGrid is sortable as long as its SortMemberPath can be resolved against the bound data source:

<DataGrid ItemsSource="{Binding Products}"
          AutoGenerateColumns="False"
          CanUserSortColumns="True">
  <DataGrid.Columns>
    <DataGridTextColumn Header="Name"  Binding="{Binding Name}"  SortMemberPath="Name" />
    <DataGridTextColumn Header="Price" Binding="{Binding Price}" SortMemberPath="Price" />
  </DataGrid.Columns>
</DataGrid>

Clicking the Name header once sorts ascending, and clicking again reverses to descending.
The default behavior does not return to an unsorted state, so clear logic must be implemented explicitly when needed — see How to Reset DataGrid Sorting in WPF for that pattern.

Sorting via Code-Behind

Sorting can be triggered programmatically by manipulating DataGrid.Items.SortDescriptions:

using System.ComponentModel;

dataGrid.Items.SortDescriptions.Clear();
dataGrid.Items.SortDescriptions.Add(
    new SortDescription(nameof(Product.Price), ListSortDirection.Descending));
dataGrid.Items.Refresh();

Reset the column-header sort glyph too so the UI stays in sync:

foreach (var col in dataGrid.Columns)
    col.SortDirection = null;

var priceCol = dataGrid.Columns.First(c => c.SortMemberPath == nameof(Product.Price));
priceCol.SortDirection = ListSortDirection.Descending;

Without this glyph update the header arrow still points at the previous column, even though the rows are ordered correctly, which makes the sorted state look inconsistent to the user.

Two DataGrid controls side by side. In the left one the rows are ordered by Price descending while an ascending arrow remains on the Name column. In the right one the descending arrow is on the Price column and matches the row order.
Both grids started from a state sorted by Name ascending, and then had their SortDescriptions replaced with Price descending. The left grid updated only SortDescriptions, so the rows follow Price descending while the arrow stays on the Name column. The right grid also updated SortDirection, so the arrow points at Price descending.

Custom Sort Logic with ListCollectionView

For comparison rules that SortDescriptions cannot express — such as a case-insensitive string sort or sorting by an expression not exposed as a public property — use ListCollectionView.CustomSort. (SortDescriptions can still sort by a public property whose getter computes a value; the limit is comparisons not exposed as a property.) Multi-level sorting does not need it: add several SortDescription entries instead.
CollectionViewSource.GetDefaultView returns an ICollectionView, which does not expose CustomSort. For an in-memory collection the concrete type is ListCollectionView, but a view over another source (such as a DataView) is not, so narrow the type with a pattern match rather than an unconditional cast that could throw InvalidCastException:

if (CollectionViewSource.GetDefaultView(dataGrid.ItemsSource) is ListCollectionView view)
{
    view.CustomSort = Comparer<Product>.Create((a, b) =>
        StringComparer.OrdinalIgnoreCase.Compare(a.Name, b.Name));
}

CustomSort takes precedence over SortDescriptions. To switch back to SortDescriptions-based sorting, set view.CustomSort = null first — clearing SortDescriptions alone leaves CustomSort in effect — and then configure SortDescriptions.

Notes

Summary

Scenario Recommended approach
Simple column sorting CanUserSortColumns="True" (default)
Programmatic sort SortDescriptions + update SortDirection
Custom sort logic ListCollectionView.CustomSort

For most line-of-business apps the default mechanism covers the common cases.
Reach for CustomSort only when the data requires special ordering that SortDescription cannot express.