每列的WPF DataGrid CustomSort

tri*_*n86 14 .net c# wpf datagrid collectionviewsource

我有一个绑定到CollectionViewSource的WPF DataGrid,它封装了一个ObservableCollection.此CollectionViewSource有两个主要目标:

1)按T的特定属性对每个项目进行分组.我在GroupDescription中使用ValueConverter来获取我想要的分组行为.

2)通过a)主要对组名称(如上定义)和b)各个组项目对网格进行排序.我通过将自定义IComparer附加到CollectionViewSource的'CustomSort'属性来实现此目的.

这在大多数情况下都很有用,但是只要单击列标题,就会覆盖排序逻辑.我不想禁用排序,但是我想知道是否可以为特定列分配自定义排序顺序?

为了使事情更加清晰,假设用户单击"ColumnA" - 此时,我的CustomSorter封装的排序逻辑被覆盖,DataGrid现在按该属性排序.我宁愿颠倒CustomSorter的逻辑,而不是按所选属性进行排序.

tri*_*n86 27

我创建了几个附加属性来处理这个问题.我希望这对某人派上用场!

首先 - 定向比较器的简单接口.这扩展了IComparer,但又为我们提供了一个属性(SortDirection).您的实现应该使用它来确定元素的正确排序(否则会丢失).

public interface ICustomSorter : IComparer
{
    ListSortDirection SortDirection { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

接下来是附加行为 - 这做了两件事:1)告诉网格使用自定义排序逻辑(AllowCustomSort = true)和b)使我们能够在每列级别设置此逻辑.

public class CustomSortBehaviour
{
    public static readonly DependencyProperty CustomSorterProperty =
        DependencyProperty.RegisterAttached("CustomSorter", typeof(ICustomSorter), typeof(CustomSortBehavior));

    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(CustomSortBehavior), 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

要使用它,请在XAML中实现ICustomComparer(带无参数构造函数):

<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>
Run Code Online (Sandbox Code Playgroud)

  • 好方案!但如果比较函数被赋予SortMemberPath中定义的属性值,它可能会更加棒!目前我们被迫为每个列和每种类型的源项实现不同的ICustomSorter. (4认同)

Kir*_*oll 8

这个答案与 trilson86 的解决方案非常相似——它是基于它的——但它的解释SortMemberPath方式是传递给比较器的值是列的实际值,而不是行的值。这有利于您的分拣机更好地重复使用。此外,它完全消除了对自定义排序界面的需要。

DataGridSortBehavior.cs

using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace YourNamespace
{
    public class DataGridSortBehavior 
    {
        public static IComparer GetSorter(DataGridColumn column)
        {
            return (IComparer)column.GetValue(SorterProperty);
        }

        public static void SetSorter(DataGridColumn column, IComparer value)
        {
            column.SetValue(SorterProperty, value);
        }

        public static bool GetAllowCustomSort(DataGrid grid)
        {
            return (bool)grid.GetValue(AllowCustomSortProperty);
        }

        public static void SetAllowCustomSort(DataGrid grid, bool value)
        {
            grid.SetValue(AllowCustomSortProperty, value);
        }

        public static readonly DependencyProperty SorterProperty = DependencyProperty.RegisterAttached("Sorter", typeof(IComparer), 
            typeof(DataGridSortBehavior));
        public static readonly DependencyProperty AllowCustomSortProperty = DependencyProperty.RegisterAttached("AllowCustomSort", typeof(bool), 
            typeof(DataGridSortBehavior), new UIPropertyMetadata(false, OnAllowCustomSortChanged));

        private static void OnAllowCustomSortChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var grid = (DataGrid)obj;

            bool oldAllow = (bool)e.OldValue;
            bool newAllow = (bool)e.NewValue;

            if (!oldAllow && newAllow)
            {
                grid.Sorting += HandleCustomSorting;
            }
            else
            {
                grid.Sorting -= HandleCustomSorting;
            }
        }

        public static bool ApplySort(DataGrid grid, DataGridColumn column)
        {
            IComparer sorter = GetSorter(column);
            if (sorter == null)
            {
                return false;
            }

            var listCollectionView = CollectionViewSource.GetDefaultView(grid.ItemsSource) as ListCollectionView;
            if (listCollectionView == null)
            {
                throw new Exception("The ICollectionView associated with the DataGrid must be of type, ListCollectionView");
            }

            listCollectionView.CustomSort = new DataGridSortComparer(sorter, column.SortDirection ?? ListSortDirection.Ascending, column.SortMemberPath);
            return true;
        }

        private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)
        {
            IComparer sorter = GetSorter(e.Column);
            if (sorter == null)
            {
                return;
            }

            var grid = (DataGrid)sender;
            e.Column.SortDirection = e.Column.SortDirection == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
            if (ApplySort(grid, e.Column))
            {
                e.Handled = true;
            }
        }

        private class DataGridSortComparer : IComparer
        {
            private IComparer comparer;
            private ListSortDirection sortDirection;
            private string propertyName;
            private PropertyInfo property;

            public DataGridSortComparer(IComparer comparer, ListSortDirection sortDirection, string propertyName)
            {
                this.comparer = comparer;
                this.sortDirection = sortDirection;
                this.propertyName = propertyName;
            }

            public int Compare(object x, object y)
            {
                PropertyInfo property = this.property ?? (this.property = x.GetType().GetProperty(propertyName));
                object value1 = property.GetValue(x);
                object value2 = property.GetValue(y);

                int result = comparer.Compare(value1, value2);
                if (sortDirection == ListSortDirection.Descending)
                {
                    result = -result;
                }
                return result;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你的Xaml

这看起来也应该类似于 trilson86 的解决方案:

<UserControl.Resources>
    <converters:MyComparer x:Key="MyComparer"/>
</UserControl.Resources>
<DataGrid behaviours:DataGridSortBehavior.AllowCustomSort="True" ItemsSource="{Binding MyListCollectionView}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Test" Binding="{Binding MyValue}" behaviours:DataGridSortBehavior.Sorter="{StaticResource MyComparer}" />
    </DataGrid.Columns>
</DataGrid>
Run Code Online (Sandbox Code Playgroud)


pen*_*ave 5

trilson86给出的答案非常好。但是,两个 DependencyProperty 声明中的第三个参数不正确。而不是 DataGrid 和 DataGridColumn,它们应该是 CustomSortBehaviour,如下所示:

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
Run Code Online (Sandbox Code Playgroud)

我一直收到一条警告,指出 AllowCustomSort 属性已经注册。一些研究使我在这里找到了答案。

无论如何,这是一个很好的答案,所以谢谢你。


Joh*_*son 5

这是一种方法:

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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

<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>
Run Code Online (Sandbox Code Playgroud)