71

我有下面ObservableCollection<string>。我需要按字母顺序排序

private ObservableCollection<string> _animals = new ObservableCollection<string>
{
    "Cat", "Dog", "Bear", "Lion", "Mouse",
    "Horse", "Rat", "Elephant", "Kangaroo", "Lizard", 
    "Snake", "Frog", "Fish", "Butterfly", "Human", 
    "Cow", "Bumble Bee"
};

我试过了_animals.OrderByDescending。但我不知道如何正确使用它。

_animals.OrderByDescending(a => a.<what_is_here_?>);

我怎样才能做到这一点?

4

14 回答 14

134

介绍

基本上,如果需要显示已排序的集合,请考虑使用CollectionViewSource类:将其属性分配(“绑定”)Source到源集合——ObservableCollection<T>类的一个实例。

这个想法是CollectionViewSourceclass 提供了 class 的一个CollectionView实例。这是原始(源)集合的一种“投影”,但应用了排序、过滤等。

参考:

现场塑形

WPF 4.5 为CollectionViewSource.

参考:

解决方案

如果仍然需要对类的实例进行排序ObservableCollection<T>,那么可以这样做。该类ObservableCollection<T>本身没有排序方法。但是,可以重新创建集合以对项目进行排序:

// Animals property setter must raise "property changed" event to notify binding clients.
// See INotifyPropertyChanged interface for details.
Animals = new ObservableCollection<string>
    {
        "Cat", "Dog", "Bear", "Lion", "Mouse",
        "Horse", "Rat", "Elephant", "Kangaroo",
        "Lizard", "Snake", "Frog", "Fish",
        "Butterfly", "Human", "Cow", "Bumble Bee"
    };
...
Animals = new ObservableCollection<string>(Animals.OrderBy(i => i));

额外细节

请注意,OrderBy()andOrderByDescending()方法(与其他 LINQ 扩展方法一样)不会修改源集合!相反,它们创建了一个新序列(即实现IEnumerable<T>接口的类的新实例)。因此,有必要重新创建集合。

于 2013-10-01T09:49:35.097 回答
57

我知道这是一个老问题,但这是“排序 observablecollection”的第一个谷歌结果,所以认为值得留下我的两分钱。

道路

我要走的方法是List<>从 开始构建一个ObservableCollection<>,对它进行排序(通过它的Sort()方法,更多关于 msdn),当List<>已经排序后,ObservableCollection<>用该Move()方法重新排序。

编码

public static void Sort<T>(this ObservableCollection<T> collection, Comparison<T> comparison)
{
    var sortableList = new List<T>(collection);
    sortableList.Sort(comparison);

    for (int i = 0; i < sortableList.Count; i++)
    {
        collection.Move(collection.IndexOf(sortableList[i]), i);
    }
}

考试

public void TestObservableCollectionSortExtension()
{
    var observableCollection = new ObservableCollection<int>();
    var maxValue = 10;

    // Populate the list in reverse mode [maxValue, maxValue-1, ..., 1, 0]
    for (int i = maxValue; i >= 0; i--)
    {
        observableCollection.Add(i);
    }

    // Assert the collection is in reverse mode
    for (int i = maxValue; i >= 0; i--)
    {
        Assert.AreEqual(i, observableCollection[maxValue - i]);
    }

    // Sort the observable collection
    observableCollection.Sort((a, b) => { return a.CompareTo(b); });

    // Assert elements have been sorted
    for (int i = 0; i < maxValue; i++)
    {
        Assert.AreEqual(i, observableCollection[i]);
    }
}

笔记

这只是一个概念证明,展示了如何在ObservableCollection<>不破坏项目绑定的情况下对 an 进行排序。排序算法有改进和验证的空间(如此处指出的索引检查

于 2016-04-15T09:07:08.703 回答
18

我看着这些,我正在整理它,然后它打破了绑定,如上所述。想出了这个解决方案,虽然比你的大多数解决方案都简单,但它似乎可以做我想做的事情,,,

public static ObservableCollection<string> OrderThoseGroups( ObservableCollection<string> orderThoseGroups)
    {
        ObservableCollection<string> temp;
        temp =  new ObservableCollection<string>(orderThoseGroups.OrderBy(p => p));
        orderThoseGroups.Clear();
        foreach (string j in temp) orderThoseGroups.Add(j);
        return orderThoseGroups;



    }
于 2016-08-19T16:24:25.447 回答
13

这是一个ObservableCollection<T>,它会在更改时自动排序,仅在必要时触发排序,并且仅触发单个移动集合更改操作。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;

namespace ConsoleApp4
{
  using static Console;

  public class SortableObservableCollection<T> : ObservableCollection<T>
  {
    public Func<T, object> SortingSelector { get; set; }
    public bool Descending { get; set; }
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
      base.OnCollectionChanged(e);
      if (SortingSelector == null 
          || e.Action == NotifyCollectionChangedAction.Remove
          || e.Action == NotifyCollectionChangedAction.Reset)
        return;

      var query = this
        .Select((item, index) => (Item: item, Index: index));
      query = Descending
        ? query.OrderBy(tuple => SortingSelector(tuple.Item))
        : query.OrderByDescending(tuple => SortingSelector(tuple.Item));

      var map = query.Select((tuple, index) => (OldIndex:tuple.Index, NewIndex:index))
       .Where(o => o.OldIndex != o.NewIndex);

      using (var enumerator = map.GetEnumerator())
       if (enumerator.MoveNext())
          Move(enumerator.Current.OldIndex, enumerator.Current.NewIndex);


    }
  }


  //USAGE
  class Program
  {
    static void Main(string[] args)
    {
      var xx = new SortableObservableCollection<int>() { SortingSelector = i => i };
      xx.CollectionChanged += (sender, e) =>
       WriteLine($"action: {e.Action}, oldIndex:{e.OldStartingIndex},"
         + " newIndex:{e.NewStartingIndex}, newValue: {xx[e.NewStartingIndex]}");

      xx.Add(10);
      xx.Add(8);
      xx.Add(45);
      xx.Add(0);
      xx.Add(100);
      xx.Add(-800);
      xx.Add(4857);
      xx.Add(-1);

      foreach (var item in xx)
        Write($"{item}, ");
    }
  }
}

输出:

action: Add, oldIndex:-1, newIndex:0, newValue: 10
action: Add, oldIndex:-1, newIndex:1, newValue: 8
action: Move, oldIndex:1, newIndex:0, newValue: 8
action: Add, oldIndex:-1, newIndex:2, newValue: 45
action: Add, oldIndex:-1, newIndex:3, newValue: 0
action: Move, oldIndex:3, newIndex:0, newValue: 0
action: Add, oldIndex:-1, newIndex:4, newValue: 100
action: Add, oldIndex:-1, newIndex:5, newValue: -800
action: Move, oldIndex:5, newIndex:0, newValue: -800
action: Add, oldIndex:-1, newIndex:6, newValue: 4857
action: Add, oldIndex:-1, newIndex:7, newValue: -1
action: Move, oldIndex:7, newIndex:1, newValue: -1
-800, -1, 0, 8, 10, 45, 100, 4857,
于 2017-06-07T00:54:55.390 回答
13

我为 ObservableCollection 创建了一个扩展方法

public static void MySort<TSource,TKey>(this ObservableCollection<TSource> observableCollection, Func<TSource, TKey> keySelector)
    {
        var a = observableCollection.OrderBy(keySelector).ToList();
        observableCollection.Clear();
        foreach(var b in a)
        {
            observableCollection.Add(b);
        }
    }

它似乎有效,您不需要实现 IComparable

于 2017-04-25T13:57:37.637 回答
12

这种扩展方法消除了对整个列表进行排序的需要。

相反,它将每个新项目插入到位。所以列表始终保持排序。

事实证明,当集合更改时由于缺少通知而导致许多其他方法失败时,此方法才有效。而且速度相当快。

下面的代码应该是防弹的;它已经在大规模生产环境中进行了广泛的测试。

要使用:

// Call on dispatcher.
ObservableCollection<MyClass> collectionView = new ObservableCollection<MyClass>();
var p1 = new MyClass() { Key = "A" }
var p2 = new MyClass() { Key = "Z" }
var p3 = new MyClass() { Key = "D" }
collectionView.InsertInPlace(p1, o => o.Key);
collectionView.InsertInPlace(p2, o => o.Key);
collectionView.InsertInPlace(p3, o => o.Key);
// The list will always remain ordered on the screen, e.g. "A, D, Z" .
// Insertion speed is Log(N) as it uses a binary search.

以及扩展方法:

/// <summary>
/// Inserts an item into a list in the correct place, based on the provided key and key comparer. Use like OrderBy(o => o.PropertyWithKey).
/// </summary>
public static void InsertInPlace<TItem, TKey>(this ObservableCollection<TItem> collection, TItem itemToAdd, Func<TItem, TKey> keyGetter)
{
    int index = collection.ToList().BinarySearch(keyGetter(itemToAdd), Comparer<TKey>.Default, keyGetter);
    collection.Insert(index, itemToAdd);
}

以及二分查找扩展方法:

/// <summary>
/// Binary search.
/// </summary>
/// <returns>Index of item in collection.</returns> 
/// <notes>This version tops out at approximately 25% faster than the equivalent recursive version. This 25% speedup is for list
/// lengths more of than 1000 items, with less performance advantage for smaller lists.</notes>
public static int BinarySearch<TItem, TKey>(this IList<TItem> collection, TKey keyToFind, IComparer<TKey> comparer, Func<TItem, TKey> keyGetter)
{
    if (collection == null)
    {
        throw new ArgumentNullException(nameof(collection));
    }

    int lower = 0;
    int upper = collection.Count - 1;

    while (lower <= upper)
    {
        int middle = lower + (upper - lower) / 2;
        int comparisonResult = comparer.Compare(keyToFind, keyGetter.Invoke(collection[middle]));
        if (comparisonResult == 0)
        {
            return middle;
        }
        else if (comparisonResult < 0)
        {
            upper = middle - 1;
        }
        else
        {
            lower = middle + 1;
        }
    }

    // If we cannot find the item, return the item below it, so the new item will be inserted next.
    return lower;
}
于 2019-06-06T15:12:57.860 回答
4
myObservableCollection.ToList().Sort((x, y) => x.Property.CompareTo(y.Property));
于 2017-06-04T13:21:22.667 回答
3

to 的参数OrderByDescending是一个返回要排序的键的函数。在您的情况下,关键是字符串本身:

var result = _animals.OrderByDescending(a => a);

例如,如果你想按长度排序,你会写:

var result = _animals.OrderByDescending(a => a.Length);
于 2013-10-01T09:47:38.490 回答
3
_animals.OrderByDescending(a => a.<what_is_here_?>);

如果动物是对象动物的列表,则可以使用属性对列表进行排序。

public class Animal
{
    public int ID {get; set;}
    public string Name {get; set;}
    ...
}

ObservableCollection<Animal> animals = ...
animals = animals.OrderByDescending(a => a.Name);
于 2013-10-01T09:54:14.027 回答
1

我也想分享我的想法,因为我遇到了同样的问题。

好吧,只要回答这个问题就是:

1 - 向 observable 集合类添加扩展,如下所示:

namespace YourNameSpace
{
    public static class ObservableCollectionExtension
    {
        public static void OrderByReference<T>(this ObservableCollection<T> collection, List<T> comparison)
        {
            for (int i = 0; i < comparison.Count; i++)
            {
                if (!comparison.ElementAt(i).Equals(collection.ElementAt(i)))
                    collection.Move(collection.IndexOf(comparison[i]), i);
            }
        }
        
        public static void InsertInPlace<T>(this ObservableCollection<T> collection, List<T> comparison, T item)
        {
            int index = comparison.IndexOf(item);
            comparison.RemoveAt(index);
            collection.OrderByReference(comparison);
            collection.Insert(index, item);
        }
    }
}

2 - 然后像这样使用它:

_animals.OrderByReference(_animals.OrderBy(x => x).ToList());

这会更改您的 ObservableCollection,您可以使用 linq 并且不会更改绑定!

额外的:

我已经根据自己的喜好扩展了@Marco 和@Contango 的答案。首先,我想直接使用列表作为比较,所以你会这样:

public static void OrderByReference<T>(this ObservableCollection<T> collection, List<T> comparison)
{
    for (int i = 0; i < comparison.Count; i++)
    {
        collection.Move(collection.IndexOf(comparison[i]), i);
    }
}

并像这样使用:

YourObservableCollection.OrderByReference(YourObservableCollection.DoYourLinqOrdering().ToList());

然后我想,既然这总是移动所有东西并触发 ObservableCollection 中的移动,为什么不比较对象是否已经在那里,这带来了我在 Equals 比较器开始时提出的内容。

将对象添加到正确的位置听起来也不错,但我想要一种简单的方法来做到这一点。所以我想出了这个:

public static void InsertInPlace<T>(this ObservableCollection<T> collection, List<T> comparison, T item)
{
    collection.Insert(comparison.IndexOf(item), item);
}

您发送一个列表,其中包含您想要的新对象以及这个新对象,因此您需要创建一个列表,然后添加这个新对象,如下所示:

var YourList = YourObservableCollection.ToList();
var YourObject = new YourClass { ..... };
YourList.Add(YourObject);
YourObservableCollection.InsertInPlace(YourList.DoYourLinqOrdering().ToList(), YourObject);

但是由于 ObservableCollection 的顺序可能与列表的顺序不同,因为在“DoYourLinqOrdering()”中进行了选择(如果集合之前没有订购过,就会发生这种情况),我在插入中添加了第一个扩展 (OrderByReference)正如您在答案的开头所看到的。如果不需要移动它不会花费很长时间,所以我没有看到使用它的问题。

随着性能的发展,我通过检查每个方法完成所需的时间来比较这些方法,所以并不理想,但无论如何,我已经测试了一个具有 20000 个元素的可观察集合。对于 OrderByReference,通过添加 Equal 对象检查器,我没有看到性能上有很大差异,但如果不是所有的元素都需要移动,它会更快,并且不会在 collecitonChanged 上触发不必要的 Move 事件,所以就是这样。对于InsertInPlace也是一样的,如果ObservableCollection已经排好序了,仅仅检查对象是否在正确的位置比移动所有的itens要快,所以如果只是通过Equals 语句,您将受益于确保一切都在它应该在的地方。

请注意,如果您将此扩展与不匹配的对象或具有或多或少对象的列表一起使用,您将获得 ArgumentOutOfRangeException 或其他一些意外行为。

希望这对某人有帮助!

于 2020-06-22T05:09:21.967 回答
1
/// <summary>
/// Sorts the collection.
/// </summary>
/// <typeparam name="T">The type of the elements of the collection.</typeparam>
/// <param name="collection">The collection to sort.</param>
/// <param name="comparison">The comparison used for sorting.</param>
public static void Sort<T>(this ObservableCollection<T> collection, Comparison<T> comparison = null)
{
    var sortableList = new List<T>(collection);
    if (comparison == null)
        sortableList.Sort();
    else
        sortableList.Sort(comparison);

    for (var i = 0; i < sortableList.Count; i++)
    {
        var oldIndex = collection.IndexOf(sortableList[i]);
        var newIndex = i;
        if (oldIndex != newIndex)
            collection.Move(oldIndex, newIndex);
    }
}

该解决方案基于Marco 的回答。我对他的解决方案有一些问题,因此仅Move在索引实际更改时才调用来改进它。这应该可以提高性能并解决相关问题。

于 2017-02-13T15:42:04.100 回答
0

我对某个类字段(距离)进行了排序。

public class RateInfo 
{
    public string begin { get; set; }
    public string end { get; set; }
    public string price { get; set; }
    public string comment { get; set; }
    public string phone { get; set; }
    public string ImagePath { get; set; }
    public string what { get; set; }
    public string distance { get; set; }
}    

public ObservableCollection<RateInfo> Phones { get; set; }

public List<RateInfo> LRate { get; set; }

public ObservableCollection<RateInfo> Phones { get; set; }

public List<RateInfo> LRate { get; set; }

......

foreach (var item in ph)
        {

            LRate.Add(new RateInfo { begin = item["begin"].ToString(), end = item["end"].ToString(), price = item["price"].ToString(), distance=kilom, ImagePath = "chel.png" });
        }

       LRate.Sort((x, y) => x.distance.CompareTo(y.distance));

        foreach (var item in LRate)
        {
            Phones.Add(item);
        }
于 2018-12-04T05:17:57.810 回答
0

如果性能是您主要关心的问题并且您不介意听不同的事件,那么这是实现稳定排序的方法:

public static void Sort<T>(this ObservableCollection<T> list) where T : IComparable<T>
{
    int i = 0;
    foreach (var item in list.OrderBy(x => x))
    {
        if (!item.Equals(list[i]))
        {
            list[i] = item;
        }

        i++;
    }
}

我不确定是否有任何更简单和更快(至少在理论上)的东西,就稳定排序而言。在有序列表上执行 ToArray 可能会使枚举更快,但空间复杂度更差。您也可以取消Equals检查以更快地进行,但我想减少更改通知是一件受欢迎的事情。

这也不会破坏任何绑定。

请注意,这会引发一堆Replace事件而不是 Move(这对于 Sort 操作来说更令人期待),并且与该线程中的其他 Move 方法相比,引发的事件数量很可能会更多,但它不太可能对性能,我认为.. 大多数 UI 元素必须已经实现IList,并且进行替换ILists应该比移动更快。但更多更改的事件意味着更多的屏幕刷新。您将不得不对其进行测试以了解其含义。


如需Move答案,请参阅此。即使您在集合中有重复项,也没有看到更正确的实现。

于 2020-08-23T19:02:04.463 回答
0

这是 Shimmy 的一个细微变化,用于收集已经实现了众所周知的IComparable<T>接口的类。在这种情况下,“order by”选择器是隐式的。

public class SortedObservableCollection<T> : ObservableCollection<T> where T : IComparable<T>
{
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);
        if (e.Action != NotifyCollectionChangedAction.Reset &&
            e.Action != NotifyCollectionChangedAction.Move &&
            e.Action != NotifyCollectionChangedAction.Remove)
        {
            var query = this.Select((item, index) => (Item: item, Index: index)).OrderBy(tuple => tuple.Item, Comparer.Default);
            var map = query.Select((tuple, index) => (OldIndex: tuple.Index, NewIndex: index)).Where(o => o.OldIndex != o.NewIndex);
            using (var enumerator = map.GetEnumerator())
            {
                if (enumerator.MoveNext())
                {
                    base.MoveItem(enumerator.Current.OldIndex, enumerator.Current.NewIndex);
                }
            }
        }
    }

    // (optional) user is not allowed to move items in a sorted collection
    protected override void MoveItem(int oldIndex, int newIndex) => throw new InvalidOperationException();
    protected override void SetItem(int index, T item) => throw new InvalidOperationException();

    private class Comparer : IComparer<T>
    {
        public static readonly Comparer Default = new Comparer();

        public int Compare(T x, T y) => x.CompareTo(y);
    }

    // explicit sort; sometimes needed.
    public virtual void Sort()
    {
        if (Items.Count <= 1)
            return;

        var items = Items.ToList();
        Items.Clear();
        items.Sort();
        foreach (var item in items)
        {
            Items.Add(item);
        }
        OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}
于 2019-07-31T14:13:33.070 回答