WPF DataGrid CustomSort for each Column WPF DataGrid CustomSort for each Column wpf wpf

WPF DataGrid CustomSort for each Column


I created a couple of attached properties which handle this issue. I hope this comes in handy for someone!

First - a simple interface for your directionalised comparer. This extends IComparer but gives us one more property (SortDirection). Your implementation should use this to determine the correct ordering of elements (which would otherwise have been lost).

public interface ICustomSorter : IComparer{    ListSortDirection SortDirection { get; set; }}

Next is the attached behavior - this does two things: 1) tells the grid to use custom sort logic (AllowCustomSort=true) and b) gives us the ability to set this logic at a per-column level.

public class CustomSortBehaviour{    public static readonly DependencyProperty CustomSorterProperty =        DependencyProperty.RegisterAttached("CustomSorter", typeof(ICustomSorter), typeof(CustomSortBehaviour));    public static ICustomSorter GetCustomSorter(DataGridColumn gridColumn)    {        return (ICustomSorter)gridColumn.GetValue(CustomSorterProperty);    }    public static void SetCustomSorter(DataGridColumn gridColumn, ICustomSorter value)    {        gridColumn.SetValue(CustomSorterProperty, value);    }    public static readonly DependencyProperty AllowCustomSortProperty =        DependencyProperty.RegisterAttached("AllowCustomSort", typeof(bool),        typeof(CustomSortBehaviour), new UIPropertyMetadata(false, OnAllowCustomSortChanged));    public static bool GetAllowCustomSort(DataGrid grid)    {        return (bool)grid.GetValue(AllowCustomSortProperty);    }    public static void SetAllowCustomSort(DataGrid grid, bool value)    {        grid.SetValue(AllowCustomSortProperty, value);    }    private static void OnAllowCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)    {        var existing = d as DataGrid;        if (existing == null) return;        var oldAllow = (bool)e.OldValue;        var newAllow = (bool)e.NewValue;        if (!oldAllow && newAllow)        {            existing.Sorting += HandleCustomSorting;        }        else        {            existing.Sorting -= HandleCustomSorting;        }    }    private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)    {        var dataGrid = sender as DataGrid;        if (dataGrid == null || !GetAllowCustomSort(dataGrid)) return;        var listColView = dataGrid.ItemsSource as ListCollectionView;        if (listColView == null)            throw new Exception("The DataGrid's ItemsSource property must be of type, ListCollectionView");        // Sanity check        var sorter = GetCustomSorter(e.Column);        if (sorter == null) return;        // The guts.        e.Handled = true;        var direction = (e.Column.SortDirection != ListSortDirection.Ascending)                            ? ListSortDirection.Ascending                            : ListSortDirection.Descending;        e.Column.SortDirection = sorter.SortDirection = direction;        listColView.CustomSort = sorter;    }}

To use it, implement an ICustomComparer (with a parameterless constructor) and in your XAML:

<UserControl.Resources>    <converters:MyComparer x:Key="MyComparer"/>    <!-- add more if you need them --></UserControl.Resources><DataGrid behaviours:CustomSortBehaviour.AllowCustomSort="True" ItemsSource="{Binding MyListCollectionView}">    <DataGrid.Columns>        <DataGridTextColumn Header="Test" Binding="{Binding MyValue}" behaviours:CustomSortBehaviour.CustomSorter="{StaticResource MyComparer}" />    </DataGrid.Columns></DataGrid>


The answer given by trilson86 is excellent. However, the third parameter in the two DependencyProperty declarations is incorrect. Instead of DataGrid and DataGridColumn, they should be CustomSortBehaviour, as such:

public static readonly DependencyProperty AllowCustomSortProperty =        DependencyProperty.RegisterAttached("AllowCustomSort",         typeof(bool),        typeof(CustomSortBehaviour), // <- Here        new UIPropertyMetadata(false, OnAllowCustomSortChanged));    public static readonly DependencyProperty CustomSorterProperty =        DependencyProperty.RegisterAttached("CustomSorter",         typeof(ICustomSorter),         typeof(CustomSortBehaviour));  // <- Here

I kept getting a warning that the AllowCustomSort property was already registered. A little research led me to the answer here.

At any rate, it's an excellent answer, so thank you.


Here is one way:

using System;using System.Collections;using System.ComponentModel;using System.Windows;using System.Windows.Controls;using System.Windows.Data;public static class DataGridSort{    public static readonly DependencyProperty ComparerProperty = DependencyProperty.RegisterAttached(        "Comparer",        typeof(IComparer),        typeof(DataGridSort),        new PropertyMetadata(            default(IComparer),            OnComparerChanged));    private static readonly DependencyProperty ColumnComparerProperty = DependencyProperty.RegisterAttached(        "ColumnComparer",        typeof(ColumnComparer),        typeof(DataGridSort),        new PropertyMetadata(default(ColumnComparer)));    private static readonly DependencyProperty PreviousComparerProperty = DependencyProperty.RegisterAttached(        "PreviousComparer",        typeof(IComparer),        typeof(DataGridSort),        new PropertyMetadata(default(IComparer)));    public static readonly DependencyProperty UseCustomSortProperty = DependencyProperty.RegisterAttached(        "UseCustomSort",        typeof(bool),        typeof(DataGridSort),        new PropertyMetadata(default(bool), OnUseCustomSortChanged));    public static void SetComparer(DataGridColumn element, IComparer value)    {        element.SetValue(ComparerProperty, value);    }    public static IComparer GetComparer(DataGridColumn element)    {        return (IComparer)element.GetValue(ComparerProperty);    }    public static void SetUseCustomSort(DependencyObject element, bool value)    {        element.SetValue(UseCustomSortProperty, value);    }    public static bool GetUseCustomSort(DependencyObject element)    {        return (bool)element.GetValue(UseCustomSortProperty);    }    private static void OnComparerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)    {        var column = (DataGridColumn)d;        var columnComparer = new ColumnComparer((IComparer)e.NewValue, column);        column.SetValue(ColumnComparerProperty, columnComparer);    }    private static void OnUseCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)    {        var dataGrid = (DataGrid)d;        if ((bool)e.NewValue)        {            WeakEventManager<DataGrid, DataGridSortingEventArgs>.AddHandler(dataGrid, nameof(dataGrid.Sorting), OnDataGridSorting);        }        else        {            WeakEventManager<DataGrid, DataGridSortingEventArgs>.RemoveHandler(dataGrid, nameof(dataGrid.Sorting), OnDataGridSorting);        }    }    private static void OnDataGridSorting(object sender, DataGridSortingEventArgs e)    {        var column = e.Column;        var columnComparer = (ColumnComparer)column.GetValue(ColumnComparerProperty);        var dataGrid = (DataGrid)sender;        var view = CollectionViewSource.GetDefaultView(dataGrid.ItemsSource) as ListCollectionView;        if (view == null)        {            return;        }        if (columnComparer == null)        {            view.CustomSort = (IComparer)dataGrid.GetValue(PreviousComparerProperty);        }        else        {            if (!(view.CustomSort is ColumnComparer))            {                dataGrid.SetValue(PreviousComparerProperty, view.CustomSort);            }            switch (column.SortDirection)            {                case ListSortDirection.Ascending:                    column.SortDirection = ListSortDirection.Descending;                    view.CustomSort = columnComparer.Descending;                    break;                case null:                case ListSortDirection.Descending:                    column.SortDirection = ListSortDirection.Ascending;                    view.CustomSort = columnComparer.Ascending;                    break;                default:                    throw new ArgumentOutOfRangeException();            }            e.Handled = true;        }    }    private class ColumnComparer : IComparer    {        private readonly IComparer valueComparer;        private readonly DataGridColumn column;        private readonly InvertedComparer inverted;        public ColumnComparer(IComparer valueComparer, DataGridColumn column)        {            this.valueComparer = valueComparer;            this.column = column;            inverted = new InvertedComparer(this);        }        public IComparer Ascending => this;        public IComparer Descending => inverted;        int IComparer.Compare(object x, object y)        {            if (x == y)            {                return 0;            }            if (x == null)            {                return -1;            }            if (y == null)            {                return 1;            }            // this can perhaps be a bit slow            // Not adding caching yet.            var xProp = x.GetType().GetProperty(column.SortMemberPath);            var xValue = xProp.GetValue(x);            var yProp = x.GetType().GetProperty(column.SortMemberPath);            var yValue = yProp.GetValue(y);            return valueComparer.Compare(xValue, yValue);        }        private class InvertedComparer : IComparer        {            private readonly IComparer comparer;            public InvertedComparer(IComparer comparer)            {                this.comparer = comparer;            }            public int Compare(object x, object y)            {                return comparer.Compare(y, x);            }        }    }}

Usage:

<DataGrid AutoGenerateColumns="False"            ItemsSource="{Binding DataItems}"            local:DataGridSort.UseCustomSort="True">    <DataGrid.Columns>        <DataGridTextColumn Binding="{Binding Key}"                            Header="Key"                            local:DataGridSort.Comparer="{x:Static local:StringLengthComparer.Default}" />        <DataGridTextColumn Binding="{Binding Value}" Header="Value" />    </DataGrid.Columns></DataGrid>