【问题标题】:How to sort a DataGrid using stable sorting?如何使用稳定排序对 DataGrid 进行排序?
【发布时间】:2012-08-12 16:01:36
【问题描述】:

我有一个 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

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

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

但如果我现在按类别排序:

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

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

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

似乎默认情况下它应该像这样工作,或者至少是一个切换。有没有人有什么建议? @Eirik 的 shift-clicking 想法有效,这表明行为是存在的。但是,我真正想要的是在没有任何修饰符的情况下那样工作。这不应该是“按此排序,然后按此排序,然后按此排序”的原因,应该将算法换成不同的算法。

看到这个:http://en.wikipedia.org/wiki/Sorting_algorithm#Stability

【问题讨论】:

  • 您是否正在寻找一种在后面的代码中执行此操作的方法,或者希望有人知道如何使 shift-click-behaviour 成为默认行为?
  • @Grubsnik 我希望有人知道如何更改排序算法,因为我认为 shift-click 方法是一个主要的 hack。但是,它可以完成工作。

标签: c# wpf sorting datagrid


【解决方案1】:

您应该能够通过在单击列时按住 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));
        }
    }
}

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

private void myDataGridSorting(object sender, DataGridSortingEventArgs e)
{
    e.Handled = true;
}

编辑: 阅读 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));
}

到这里:

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

view.SortDescriptions.Insert(0, new SortDescription(columnHeader.Content.ToString(), ListSortDirection.Ascending));

您将获得稳定的排序。单击学生按学生排序,然后单击班级按班级、学生排序。 如果在单击时按住 ctrl,则会在按单击的列排序之前清除先前的排序。

【讨论】:

  • +1。这太好了,谢谢!短期内很方便。但是,我还不想将其标记为答案。理想情况下,默认情况下我会喜欢这种行为(因此,无需按住 shift)。我会澄清这个问题。
  • @TarkaDaal 你将如何“删除”之前的排序?假设您想按班级和学生排序,但您想按班级和年级排序。您需要实现某种方法来设置新列以首先排序,例如在单击时按住 shift... ;)
  • 你不需要。如果您想按班级排序,然后按成绩,只需单击班级,然后按成绩。学生的顺序无关紧要。这不是按多列排序,而是尽可能保留以前的顺序。
  • @TarkaDaal 编辑了答案并提供了在代码中排序的解决方案。
  • 您的代码中仍然缺少什么:为了实现 OP 想要的,您不会清除 SortDescriptions,但首先删除最近单击的排序列的任何 SortDescription(如果有),然后在列表的开头插入一个新的。这样,您将始终将其他排序顺序保留为较低优先级的排序顺序。
【解决方案2】:

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

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

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

    if (view != null)
    {
        view.CustomSort = new StableComparer(collection);
    }
}

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-12
    • 2011-08-12
    • 1970-01-01
    • 2017-04-02
    相关资源
    最近更新 更多