如何使用稳定排序对DataGrid进行排序?

Tar*_*aal 8 c# sorting wpf datagrid

我有一个WPF DataGrid,我已经得到它,以便您可以通过单击列标题对其进行排序.它有效,但它不稳定.如何让它做稳定的排序?

我的意思是,如果我有这张桌子:

Class    | Student    | Grade
-----------------------------
Art      | James      |  A
Art      | Amy        |  B
Art      | Charlie    |  A
Science  | James      |  D
Science  | Amy        |  A
Science  | Charlie    |  C
History  | James      |  B
History  | Amy        |  A
History  | Charlie    |  C
Run Code Online (Sandbox Code Playgroud)

如果我按学生排序,它会像你期望的那样工作:

Class    | Student    | Grade
-----------------------------
Art      | Amy        |  B
Science  | Amy        |  A
History  | Amy        |  A
Art      | Charlie    |  A
Science  | Charlie    |  C
History  | Charlie    |  C
Art      | James      |  A
Science  | James      |  D
History  | James      |  B
Run Code Online (Sandbox Code Playgroud)

但是,如果我现在按类排序:

Class    | Student    | Grade
-----------------------------
Art      | James      |  A
Art      | Amy        |  B
Art      | Charlie    |  A
History  | James      |  B
History  | Amy        |  A
History  | Charlie    |  C
Science  | James      |  D
Science  | Amy        |  A
Science  | Charlie    |  C
Run Code Online (Sandbox Code Playgroud)

它破坏了学生的排序(不稳定排序).我想要的是稳定排序,它保留了订单:

Class    | Student    | Grade
-----------------------------
Art      | Amy        |  B
Art      | Charlie    |  A
Art      | James      |  A
History  | Amy        |  A
History  | Charlie    |  C
History  | James      |  B
Science  | Amy        |  A
Science  | Charlie    |  C
Science  | James      |  D
Run Code Online (Sandbox Code Playgroud)

似乎默认情况下应该像这样工作,或者至少是切换.有没有人有什么建议?@ Eirik关于shift-click工作的想法,这表明行为存在.但是,我真正喜欢的是在没有任何修改器的情况下工作.它不应该是"按此排序,然后是这个,然后是这个"的原因,它应该是将算法交换为另一个算法的情况.

请参阅:http://en.wikipedia.org/wiki/Sorting_algorithm#Stability

Eir*_*rik 8

您可以通过在单击列时按住shift来按多列排序.尝试单击类列,然后按住shift并单击学生列.

这是一个在代码中添加排序的解决方案:

private void myDataGridPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    DependencyObject dep = (DependencyObject)e.OriginalSource;

    while ((dep != null) && !(dep is DataGridColumnHeader))
    {
        dep = VisualTreeHelper.GetParent(dep);
    }

    if (dep == null)
        return;

    if (dep is DataGridColumnHeader)
    {
        DataGridColumnHeader columnHeader = dep as DataGridColumnHeader;

        ICollectionView view = CollectionViewSource.GetDefaultView((sender as DataGrid).ItemsSource);

        if (columnHeader.Content.Equals("Class") || columnHeader.Content.Equals("Student"))
        {
            view.SortDescriptions.Clear();
            view.SortDescriptions.Add(new SortDescription("Class", ListSortDirection.Ascending));
            view.SortDescriptions.Add(new SortDescription("Student", ListSortDirection.Ascending));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

为此,您必须禁用标准排序.一种方法是停止Sorting事件,如下所示:

private void myDataGridSorting(object sender, DataGridSortingEventArgs e)
{
    e.Handled = true;
}
Run Code Online (Sandbox Code Playgroud)

编辑:在阅读hbarck的评论后,我再次阅读了你的问题,似乎我错过了一些部分.如果您更改此代码:

if (columnHeader.Content.Equals("Class") || columnHeader.Content.Equals("Student"))
{
    view.SortDescriptions.Clear();
    view.SortDescriptions.Add(new SortDescription("Class", ListSortDirection.Ascending));
    view.SortDescriptions.Add(new SortDescription("Student", ListSortDirection.Ascending));
}
Run Code Online (Sandbox Code Playgroud)

对此:

if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
    view.SortDescriptions.Clear();
}

view.SortDescriptions.Insert(0, new SortDescription(columnHeader.Content.ToString(), ListSortDirection.Ascending));
Run Code Online (Sandbox Code Playgroud)

你将有稳定的排序.单击学生按学生排序,然后单击班级按班级排序,学生.如果在单击时按住ctrl,则在按照单击的列排序之前清除先前的排序.


alm*_*ulo 2

我已经设法使用自定义比较器获得稳定的排序,但这有点像一个大黑客......

我使用 ListCollectionView 的 CustomSort 属性来设置我的自定义比较器,这需要我在实例化它时将集合传递给它。

private void Sorting(IEnumerable collection)
{
    var view = CollectionViewSource.GetDefaultView(collection) as ListCollectionView;

    if (view != null)
    {
        view.CustomSort = new StableComparer(collection);
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的自定义比较器中,我在 Compare 方法期间使用集合,只是为了在常规比较返回零(它们相同或具有相同值)时回退到项目索引。

public class StableComparer : IComparer
{
    public IEnumerable Collection { get; set; }

    public StableComparer(IEnumerable collection)
    {
        Collection = collection;
    }

    public int Compare(object x, object y)
    {
        IComparable x_Comparable = x as IComparable;
        IComparable y_Comparable = y as IComparable;

        if (x_Comparable != null && y_Comparable != null)
        {
            var comparison = x_Comparable.CompareTo(y_Comparable);

            // A zero value means x and y are equivalent for sorting, and they could
            //  be rearranged by an unstable sorting algorithm
            if (comparison == 0 && Collection != null)
            {
                // IndexOf is an extension method for IEnumerable (not included)
                var x_Index = Collection.IndexOf(x);
                var y_Index = Collection.IndexOf(y);

                // By comparing their indexes in the original collection, we get to
                //  preserve their relative order
                if (x_Index != -1 && y_Index != -1)
                    comparison = x_Index.CompareTo(y_Index);
            }

            return comparison;
        }

        return 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

我仍在测试这一点,所以我不能保证这会一直有效...例如,一个问题是保持比较器内的 Collection 属性更新。或者支持两种排序方向(现在就开始工作,应该不难)。或者检查它是如何工作的,性能方面的。

但我认为这个想法很明确;虽然很老套,就像我说的。