17

我有一个 WPF DataGrid 绑定到一个封装 ObservableCollection 的 CollectionViewSource。这个 CollectionViewSource 有两个主要目标:

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

2) 按 a) 主要是组名(如上定义)和 b) 单个组项对网格进行排序。我通过将自定义 IComparer 附加到 CollectionViewSource 的“CustomSort”属性来实现这一点。

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

为了让事情更清楚一点,假设用户单击“ColumnA” - 目前,由我的 CustomSorter 封装的排序逻辑被覆盖,DataGrid 现在按该属性排序。我不想按所选属性进行排序,而是反转 CustomSorter 的逻辑。

4

10 回答 10

33

我创建了几个附加属性来处理这个问题。我希望这对某人有用!

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

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

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

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;
    }
}

要使用它,请在您的 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>
于 2013-08-13T20:49:23.230 回答
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

我不断收到一条警告,提示 AllowCustomSort 属性已注册。一点研究让我在这里找到了答案。

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

于 2015-08-31T15:34:11.530 回答
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);
            }
        }
    }
}

用法:

<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>
于 2016-01-15T19:56:53.923 回答
3

这个答案与 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;
            }
        }
    }
}

你的 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>
于 2016-04-15T00:07:43.000 回答
3

数据网格自定义排序

<DataGrid attached:DataGridHelpers.UseCustomSort="True" ItemsSource="{Binding Items}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn attached:DataGridHelpers.CustomSorterType="{x:Type comparers:StrLogicalComparer}" Binding="{Binding CodeText}" Header="Code"  />
        <DataGridTextColumn Header="Number" Binding="{Binding Number}" />
    </DataGrid.Columns>
</DataGrid>

支持嵌套属性

于 2018-08-15T20:58:11.407 回答
2

我通过重写 OnSorting 事件并自己实现它来做到这一点。

http://msdn.microsoft.com/en-us/library/system.windows.controls.datagrid.onsorting.aspx

这基本上意味着重新排序 ListCollectionView。

对不起,它的答案不太深入。

于 2013-08-08T10:22:30.650 回答
2

我更改了@trilson86 的答案,因此您只需要一个用于整个 DataGrid 的自定义排序器类。

首先是接口:

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

    string SortMemberPath { get; set; }
}

接下来是 Bevaviour 类,它定义了 CustomSorterProperty,您可以直接在 DataGrid 上使用它,而不是在 DateGridRow 上使用它。在 HandleCustomSorting() 中,CustomSorter 的 SortMemberPath 属性填充了来自单击列的实际值,您可以在 CustomSorter 中使用此值对所需列进行排序。

public class CustomSortBehaviour
{
    #region Fields and Constants

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

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



    #endregion

    #region public Methods

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


    public static ICustomSorter GetCustomSorter(DataGrid grid)
    {
        return (ICustomSorter)grid.GetValue(CustomSorterProperty);
    }

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


    public static void SetCustomSorter(DataGrid grid, ICustomSorter value)
    {
        grid.SetValue(CustomSorterProperty, value);
    }

    #endregion

    #region nonpublic Methods

    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(dataGrid);
        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;
        sorter.SortMemberPath = e.Column.SortMemberPath;

        listColView.CustomSort = sorter;
    }

    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;
        }
    }

    #endregion
}

您可以像这样在 XAML 中使用它:

<Window x:Class="..."
        xmlns:sorter="clr-namespace:...Sorting"
        ...
        >

    <Window.Resources>
        <sorter:CustomSorter x:Key="MySorter"/>
    </Window.Resources>

    <Grid>

        <DataGrid ItemsSource="{Binding ...}"
                  sorter:CustomSortBehaviour.AllowCustomSort="True"
                  sorter:CustomSortBehaviour.CustomSorter="{StaticResource MySorter}" >


            <DataGrid.Columns>
                <DataGridTextColumn Header="Column 1" Binding="{Binding Column1}"/>
                <DataGridTextColumn Header="Column 2" Binding="{Binding Column2}"/>
                <DataGridTextColumn Header="Column 3" Binding="{Binding Column3}"/>
            </DataGrid.Columns>

        </DataGrid>

    </Grid>
</Window>
于 2015-12-22T13:56:47.433 回答
1

我从这个问题中学到了很多东西,在这里我分享一种方法。
此方法在符合 mvvm 时不会修改 xaml。
我需要将文件路径加载到 DataGrid,可以像 Windows Explorer 一样进行排序。
有一些文件:

E:\Test\Item_1.txt
E:\Test\Item_04.txt
E:\Test\Item_5.txt
E:\Test\Item_10.txt

请注意,我已通过 Windows 资源管理器按文件名排序。
我想您已经发现 Explorer 使用的文件排序不是简单的字符串排序。
它使用 shlwapi.dll 中的 win32 api StrCmpLogicalW我们需要 为排序的属性实现IComparable (Non-generic) 接口。 为了减少代码,我使用了 Prism.Mvvm.BindableBase,一个 INotifyPropertyChanged 实现。 像这样的代码:


    /// <summary>
    /// Data Model
    /// </summary>
    public class ListItemModel : BindableBase
    {

        private FilePath filePath;
        public FilePath FilePath
        {
            get { return filePath; }
            set { SetProperty(ref filePath, value); }
        }
        
        /// Other properties.
        /// ....
    }

    /// <summary>
    /// wrapper of filepath
    /// </summary>
    public class FilePath : IComparable
    {
        private string filePath;

        [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
        private static extern int StrCmpLogicalW(string psz1, string psz2);

        public FilePath(string filePath)
        {
            this.filePath = filePath;
        }

        /// <summary>
        /// Implicit type conversion.
        /// </summary>
        /// <param name="x"></param>
        public static implicit operator string(FilePath x)
        {
            return x.filePath;
        }

        public static implicit operator FilePath(string x)
        {
            return new FilePath(x);
        }

        /// <summary>
        /// override for datagrid display.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return filePath;
        }

        /// <summary>
        /// Implement the interface IComparable for Custom sorting.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public int CompareTo(object obj)
        {
            if (obj is FilePath other)
                return StrCmpLogicalW(filePath, other.filePath);
            return 1;
        }
    }

XAML 代码:

        <!-- Items is ObservableCollection<ListItemModel> -->        
        <DataGrid  ItemsSource="{Binding Items}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="File" Binding="{Binding FilePath}" />
            </DataGrid.Columns>
        </DataGrid>

综上所述,我封装了原有的字符串属性,
你也可以封装你的自定义属性进行排序,
只需要重写ToString进行显示,实现CompareTo进行排序。

于 2020-08-18T10:36:51.580 回答
0

这是@trilson86 ICustomeSorter的一些扩展

使分拣机更通用

基于此资源的 NumericComparer

http://www.codeproject.com/Articles/11016/Numeric-String-Sort-in-C

public class GenericNumericComparer : ICustomSorter
{
    private PropertyInfo _propertyInfo;
    private Type _objectType;

    public string SortMemberPath { get; set; }

    private readonly NumericComparer _comparer = new NumericComparer();

    public Type ObjectType
    {
        get { return _objectType; }
        set
        {
            _objectType = value;

            if (_objectType != null) _propertyInfo = ObjectType.GetProperty(SortMemberPath);
        }
    }

    private int CompareHelper(object x, object y)
    {
        if (_propertyInfo != null)
        {
            var value1 = _propertyInfo.GetValue(x);
            var value2 = _propertyInfo.GetValue(y);

            return _comparer.Compare(value1, value2);
        }

        return 0;
    }

    public int Compare(object x, object y)
    {
        var i = CompareHelper(x, y);

        if (SortDirection == ListSortDirection.Ascending)
            return i;

        return i*-1;
    }

    public ListSortDirection SortDirection { get; set; }
}
于 2015-06-14T08:59:43.053 回答
-2

如果您以编程方式添加列,则可以使用它。

dg_show.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("val1", System.ComponentModel.ListSortDirection.Descending));

这里的“val1”是我们添加的列的绑定路径,您也可以使用另一行作为第二次排序。像这个 。

        dg_show.Items.SortDescriptions.Add(new System.ComponentModel.SortDescription("val2", System.ComponentModel.ListSortDirection.Ascending));
于 2015-04-15T01:38:23.797 回答