Как отсортировать наблюдаемую коллекцию? - PullRequest
93 голосов
/ 22 декабря 2009

У меня есть следующий класс:

[DataContract]
public class Pair<TKey, TValue> : INotifyPropertyChanged, IDisposable
{
    public Pair(TKey key, TValue value)
    {
        Key = key;
        Value = value;
    }

    #region Properties
    [DataMember]
    public TKey Key
    {
        get
        { return m_key; }
        set
        {
            m_key = value;
            OnPropertyChanged("Key");
        }
    }
    [DataMember]
    public TValue Value
    {
        get { return m_value; }
        set
        {
            m_value = value;
            OnPropertyChanged("Value");
        }
    }
    #endregion

    #region Fields
    private TKey m_key;
    private TValue m_value;
    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    { }

    #endregion
}

Что я положил в коллекцию ObservableCollection:

ObservableCollection<Pair<ushort, string>> my_collection = 
    new ObservableCollection<Pair<ushort, string>>();

my_collection.Add(new Pair(7, "aaa"));
my_collection.Add(new Pair(3, "xey"));
my_collection.Add(new Pair(6, "fty"));

В: Как мне отсортировать его по ключу?

Ответы [ 22 ]

77 голосов
/ 02 мая 2013

Это простое расширение прекрасно сработало для меня. Я просто должен был убедиться, что MyObject было IComparable. Когда метод сортировки вызывается для наблюдаемой коллекции MyObjects, вызывается метод CompareTo для MyObject, который вызывает мой метод логической сортировки. Хотя в нем нет всех наворотов остальных ответов, опубликованных здесь, это именно то, что мне нужно.

static class Extensions
{
    public static void Sort<T>(this ObservableCollection<T> collection) where T : IComparable
    {
        List<T> sorted = collection.OrderBy(x => x).ToList();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

public class MyObject: IComparable
{
    public int CompareTo(object o)
    {
        MyObject a = this;
        MyObject b = (MyObject)o;
        return Utils.LogicalStringCompare(a.Title, b.Title);
    }

    public string Title;

}
  .
  .
  .
myCollection = new ObservableCollection<MyObject>();
//add stuff to collection
myCollection.Sort();
38 голосов
/ 23 марта 2011

Я знаю, что этот вопрос старый, но только что натолкнулся на него во время поиска в Google и нашел соответствующую запись в блоге, которая дает лучший ответ, чем здесь:

http://kiwigis.blogspot.com/2010/03/how-to-sort-obversablecollection.html

UPDATE

ObservableSortedList , на который @romkyns указывает в комментариях, автоматически поддерживает порядок сортировки.

Реализует наблюдаемую коллекцию, которая поддерживает свои элементы в отсортированном порядке. В частности, изменения свойств элемента, которые приводят к изменениям заказа, обрабатываются правильно.

Однако обратите внимание и на замечание

Может быть с ошибкой из-за сравнительной сложности используемого интерфейса и его относительно плохой документации (см. https://stackoverflow.com/a/5883947/33080).

24 голосов
/ 05 мая 2011

Вы можете использовать этот простой метод:

public static void Sort<TSource, TKey>(this Collection<TSource> source, Func<TSource, TKey> keySelector)
{
    List<TSource> sortedList = source.OrderBy(keySelector).ToList();
    source.Clear();
    foreach (var sortedItem in sortedList)
        source.Add(sortedItem);
}

Вы можете сортировать так:

_collection.Sort(i => i.Key);

Подробнее: http://jaider.net/2011-05-04/sort-a-observablecollection/

20 голосов
/ 22 декабря 2009

OP Edit: как многие правильно указали, исходный ответ не возвращает ту же коллекцию (изначально больше внимания уделялось сортировке словарной части Q). Пожалуйста, смотрите редактирование внизу, где я рассматриваю сортировку наблюдаемой коллекции. Оригинал оставлен здесь, так как все еще получает голоса

Вы можете использовать linq, как показано на рисунке ниже. Быстрый фрагмент кода: производит

3: Xey 6: FTY 7: ааа

В качестве альтернативы вы можете использовать метод расширения для самой коллекции

var sortedOC = _collection.OrderBy(i => i.Key);

private void doSort()
{
    ObservableCollection<Pair<ushort, string>> _collection = 
        new ObservableCollection<Pair<ushort, string>>();

    _collection.Add(new Pair<ushort,string>(7,"aaa"));
    _collection.Add(new Pair<ushort, string>(3, "xey"));
    _collection.Add(new Pair<ushort, string>(6, "fty"));

    var sortedOC = from item in _collection
                   orderby item.Key
                   select item;

    foreach (var i in sortedOC)
    {
        Debug.WriteLine(i);
    }

}

public class Pair<TKey, TValue>
{
    private TKey _key;

    public TKey Key
    {
        get { return _key; }
        set { _key = value; }
    }
    private TValue _value;

    public TValue Value
    {
        get { return _value; }
        set { _value = value; }
    }

    public Pair(TKey key, TValue value)
    {
        _key = key;
        _value = value;

    }

    public override string ToString()
    {
        return this.Key + ":" + this.Value;
    }
}

EDIT

Чтобы вернуть ObservableCollection, вызовите .ToObservableCollection для sortedOC , используя, например, это реализация .

OP EDIT Сортировка наблюдаемого и возвращение того же отсортированного объекта может быть сделано с использованием метода расширения. Для больших коллекций следите за количеством уведомлений об изменениях коллекции, например

public static void Sort<T>(this ObservableCollection<T> observable) where T : IComparable<T>, IEquatable<T>
    {
        List<T> sorted = observable.OrderBy(x => x).ToList();

        int ptr = 0;
        while (ptr < sorted.Count)
        {
            if (!observable[ptr].Equals(sorted[ptr]))
            {
                T t = observable[ptr];
                observable.RemoveAt(ptr);
                observable.Insert(sorted.IndexOf(t), t);
            }
            else
            {
                ptr++;
            }
        }
    }

использование: Образец с наблюдателем (для простоты использовал класс Person)

public class Person:IComparable<Person>,IEquatable<Person>
    { 
        public string Name { get; set; }
        public int Age { get; set; }

        public int CompareTo(Person other)
        {
            if (this.Age == other.Age) return 0;
            return this.Age.CompareTo(other.Age);
        }

        public override string ToString()
        {
            return Name + " aged " + Age;
        }

        public bool Equals(Person other)
        {
            if (this.Name.Equals(other.Name) && this.Age.Equals(other.Age)) return true;
            return false;
        }
    }

  static void Main(string[] args)
    {
        Console.WriteLine("adding items...");
        var observable = new ObservableCollection<Person>()
        {
            new Person { Name = "Katy", Age = 51 },
            new Person { Name = "Jack", Age = 12 },
            new Person { Name = "Bob",  Age = 13 },
            new Person { Name = "John", Age = 14 },
            new Person { Name = "Mary", Age = 41 },
            new Person { Name = "Jane", Age = 20 },
            new Person { Name = "Jim",  Age = 39 },
            new Person { Name = "Sue",  Age = 15 },
            new Person { Name = "Kim",  Age = 19 }
        };

        //what do observers see?
        observable.CollectionChanged += (o, e) => {

            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    Console.WriteLine("removed {0} at index {1}", item, e.OldStartingIndex);
                }
            }

            if (e.NewItems != null)
            {
                foreach (var item in e.NewItems)
                {
                    Console.WriteLine("added {0} at index {1}", item, e.NewStartingIndex);
                }
            }};            

        Console.WriteLine("\nsorting items...");
        observable.Sort();
    };

Вывод сверху:
удалила Кэти в возрасте 51 года с индексом 0
добавила Кэти в возрасте 51 года в индексе 8
удалила Мэри в возрасте 41 года по индексу 3
добавила Мэри в возрасте 41 в индексе 7
убрала Джейн в возрасте 20 лет по индексу 3
добавил Джейн в возрасте 20 лет в индекс 5
удалил Джима в возрасте 39 лет по индексу 3
добавлен Джим в возрасте 39 лет по индексу 6
убрал Джейн в возрасте 20 лет по индексу 4
добавлена ​​Джейн в возрасте 20 лет по индексу 5

Класс Person реализует как IComparable, так и IEquatable, последний используется для минимизации изменений в коллекции, чтобы уменьшить количество создаваемых уведомлений об изменениях

15 голосов
/ 11 июня 2012

Мне понравился метод расширения пузырьковой сортировки в блоге "Ричи" выше, но я не обязательно хочу просто сортировать, сравнивая весь объект. Я чаще хочу отсортировать по определенному свойству объекта. Поэтому я изменил его так, чтобы он принимал ключевой селектор так, как это делает OrderBy, чтобы вы могли выбирать, какое свойство сортировать:

    public static void Sort<TSource, TKey>(this ObservableCollection<TSource> source, Func<TSource, TKey> keySelector)
    {
        if (source == null) return;

        Comparer<TKey> comparer = Comparer<TKey>.Default;

        for (int i = source.Count - 1; i >= 0; i--)
        {
            for (int j = 1; j <= i; j++)
            {
                TSource o1 = source[j - 1];
                TSource o2 = source[j];
                if (comparer.Compare(keySelector(o1), keySelector(o2)) > 0)
                {
                    source.Remove(o1);
                    source.Insert(j, o1);
                }
            }
        }
    }

Который вы бы назвали так же, как и OrderBy, за исключением того, что он отсортирует существующий экземпляр ObservableCollection вместо возврата новой коллекции:

ObservableCollection<Person> people = new ObservableCollection<Person>();
...

people.Sort(p => p.FirstName);
14 голосов
/ 04 июня 2016

WPF обеспечивает оперативную сортировку "из коробки" с использованием класса ListCollectionView ...

public ObservableCollection<string> MyStrings { get; set; }
private ListCollectionView _listCollectionView;
private void InitializeCollection()
{
    MyStrings = new ObservableCollection<string>();
    _listCollectionView = CollectionViewSource.GetDefaultView(MyStrings) 
              as ListCollectionView;
    if (_listCollectionView != null)
    {
        _listCollectionView.IsLiveSorting = true;
        _listCollectionView.CustomSort = new 
                CaseInsensitiveComparer(CultureInfo.InvariantCulture);
    }
}

После завершения инициализации больше ничего не нужно делать. Преимущество перед пассивной сортировкой состоит в том, что ListCollectionView выполняет всю тяжелую работу таким образом, чтобы это было прозрачно для разработчика. Новые элементы автоматически размещаются в правильном порядке сортировки. Любой класс, производный от IComparer из T, подходит для пользовательского свойства сортировки.

См. ListCollectionView для документации и других функций.

9 голосов
/ 27 июля 2015

@ Ответ NielW - путь к реальной сортировке на месте. Я хотел добавить слегка измененное решение, которое позволяет вам обходиться без использования IComparable:

static class Extensions
{
    public static void Sort<TSource, TKey>(this ObservableCollection<TSource> collection, Func<TSource, TKey> keySelector)
    {
        List<TSource> sorted = collection.OrderBy(keySelector).ToList();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

теперь вы можете вызывать его, как и любой другой метод LINQ:

myObservableCollection.Sort(o => o.MyProperty);
8 голосов
/ 17 января 2013

Вариант - это место, где вы сортируете коллекцию на месте с использованием алгоритма selection sort . Элементы перемещаются на место с помощью метода Move. Каждый ход будет запускать событие CollectionChanged с NotifyCollectionChangedAction.Move (а также PropertyChanged с именем свойства Item[]).

Этот алгоритм обладает некоторыми приятными свойствами:

  • Алгоритм может быть реализован как устойчивая сортировка.
  • Количество элементов, перемещаемых в коллекции (например, CollectionChanged сгенерированных событий), почти всегда меньше, чем другие подобные алгоритмы, такие как сортировка вставкой и сортировка пузырьками.

Алгоритм довольно прост. Коллекция повторяется, чтобы найти наименьший элемент, который затем перемещается в начало коллекции. Процесс повторяется, начиная со второго элемента и так далее, пока все элементы не будут перемещены на место. Алгоритм не очень эффективен, но для всего, что вы собираетесь отображать в пользовательском интерфейсе, это не должно иметь значения. Тем не менее, с точки зрения количества операций перемещения это довольно эффективно.

Вот метод расширения, который для простоты требует, чтобы элементы реализовали IComparable<T>. Другие варианты используют IComparer<T> или Func<T, T, Int32>.

public static class ObservableCollectionExtensions {

  public static void Sort<T>(this ObservableCollection<T> collection) where T : IComparable<T> {
    if (collection == null)
      throw new ArgumentNullException("collection");

    for (var startIndex = 0; startIndex < collection.Count - 1; startIndex += 1) {
      var indexOfSmallestItem = startIndex;
      for (var i = startIndex + 1; i < collection.Count; i += 1)
        if (collection[i].CompareTo(collection[indexOfSmallestItem]) < 0)
          indexOfSmallestItem = i;
      if (indexOfSmallestItem != startIndex)
        collection.Move(indexOfSmallestItem, startIndex);
    }
  }

}

Сортировка коллекции - это просто вызов метода расширения:

var collection = new ObservableCollection<String>(...);
collection.Sort();
8 голосов
/ 21 марта 2015

Я бы хотел Добавить к ответу NeilW . Чтобы включить метод, который напоминает порядок. Добавьте этот метод как расширение:

public static void Sort<T>(this ObservableCollection<T> collection, Func<T,T> keySelector) where T : IComparable
{
    List<T> sorted = collection.OrderBy(keySelector).ToList();
    for (int i = 0; i < sorted.Count(); i++)
        collection.Move(collection.IndexOf(sorted[i]), i);
}

И использовать как:

myCollection = new ObservableCollection<MyObject>();

//Sorts in place, on a specific Func<T,T>
myCollection.Sort(x => x.ID);
4 голосов
/ 22 сентября 2012

Чтобы немного улучшить метод расширения ответа xr280xr, я добавил необязательный параметр bool, чтобы определить, идет ли сортировка по убыванию или нет. Я также включил предложение, сделанное Карлосом П. в комментарии к этому ответу. Пожалуйста, смотрите ниже.

public static void Sort<TSource, TKey>(this ObservableCollection<TSource> source, Func<TSource, TKey> keySelector, bool desc = false)
    {
        if (source == null) return;

        Comparer<TKey> comparer = Comparer<TKey>.Default;

        for (int i = source.Count - 1; i >= 0; i--)
        {
            for (int j = 1; j <= i; j++)
            {
                TSource o1 = source[j - 1];
                TSource o2 = source[j];
                int comparison = comparer.Compare(keySelector(o1), keySelector(o2));
                if (desc && comparison < 0)
                    source.Move(j, j - 1);
                else if (!desc && comparison > 0)
                    source.Move(j - 1, j);
            }
        }
    }
...