Как отсортировать наблюдаемую коллекцию?


97

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

[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"));

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


Вы ищете реализацию сортировки внутри класса или вам подойдет любой тип сортировки?
ок,

Не знаю, как это понять. По сути, я просто хочу, чтобы это было отсортировано, коллекция не будет очень большой (максимум 20 предметов), так что все подойдет (скорее всего)
Maciek

См. Это для решения WPF stackoverflow.com/questions/1945461/…
Gayot Fow

Посмотрите ответы на этой странице: очень четкое указание на неработающий API, когда требуется более 22 ответов для некоторых важных и основных функций.
Джерри

Возможный дубликат Sort ObservableCollection <string> через C #
Тим

Ответы:


21

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

Я обновил свой код, чтобы повысить производительность и обработать дубликаты (спасибо nawfal за то, что он подчеркнул низкую производительность оригинала, хотя он хорошо работал на примере исходных данных). Наблюдаемое разделяется на левую отсортированную половину и правую несортированную половину, где каждый раз минимальный элемент (найденный в отсортированном списке) смещается в конец отсортированного раздела из несортированного. Худший случай O (n). По сути, это сортировка по выбору (вывод см. Ниже).

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

        int ptr = 0;
        while (ptr < sorted.Count - 1)
        {
            if (!collection[ptr].Equals(sorted[ptr]))
            {
                int idx = search(collection, ptr+1, sorted[ptr]);
                collection.Move(idx, ptr);
            }
            
            ptr++;
        }
    }

    public static int search<T>(ObservableCollection<T> collection, int startIndex, T other)
            {
                for (int i = startIndex; i < collection.Count; i++)
                {
                    if (other.Equals(collection[i]))
                        return i;
                }
    
                return -1; // decide how to handle error case
            }

Использование: образец с наблюдателем (для простоты использован класс 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 = "Alice", Age = 39},
                    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 = 5},
                    new Person {Name = "Kim", Age = 19}
                };
    
                //what do observers see?
            
    
observable.CollectionChanged += (sender, e) =>
        {
            Console.WriteLine(
                e.OldItems[0] + " move from " + e.OldStartingIndex + " to " + e.NewStartingIndex);
            int i = 0;
            foreach (var person in sender as ObservableCollection<Person>)
            {
                if (i == e.NewStartingIndex)
                {
                    Console.Write("(" + (person as Person).Age + "),");
                }
                else
                {
                    Console.Write((person as Person).Age + ",");
                }
                
                i++;
            }

            Console.WriteLine();
        };

Подробная информация о ходе сортировки, показывающая, как поворачивается коллекция:

Sue aged 5 move from 8 to 0
(5),51,12,13,39,14,41,20,39,19,
Jack aged 12 move from 2 to 1
5,(12),51,13,39,14,41,20,39,19,
Bob aged 13 move from 3 to 2
5,12,(13),51,39,14,41,20,39,19,
John aged 14 move from 5 to 3
5,12,13,(14),51,39,41,20,39,19,
Kim aged 19 move from 9 to 4
5,12,13,14,(19),51,39,41,20,39,
Jane aged 20 move from 8 to 5
5,12,13,14,19,(20),51,39,41,39,
Alice aged 39 move from 7 to 6
5,12,13,14,19,20,(39),51,41,39,
Jim aged 39 move from 9 to 7
5,12,13,14,19,20,39,(39),51,41,
Mary aged 41 move from 9 to 8
5,12,13,14,19,20,39,39,(41),51,

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

  • ИЗМЕНИТЬ Сортировка той же коллекции без создания новой копии *

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

**** orig answer - это создает новую коллекцию **** Вы можете использовать linq, как показано ниже в методе doSort. Быстрый фрагмент кода: производит

3: xey 6: fty 7: aaa

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

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

Нашел это и нашел очень полезным. Является ли переменная sortedOC var в LINQ?
Jason94

9
Не поклонник этого ответа, потому что он не дает вам отсортированной ObservableCollection.
xr280xr

63
-1 , так как это не сортирует , но вместо этого создает новую коллекцию. ObservableCollection
Кос,

2
Обновленный код будет работать, но имеет временную сложность O (n ^ 2). Это можно улучшить до O (n * log (n)), используя BinarySearchвместо IndexOf.
Уильям Моррисон

2
Отличное решение! Для тех, кто наследует от ObservableCollection <T>, можно использовать защищенный метод MoveItem () вместо использования методов RemoveAt и Insert. См. Также: linksource.microsoft.com/#system/compmod/system/…
Herman Cordes,

85

Это простое расширение прекрасно сработало для меня. Я просто должен был убедиться, что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();

7
это должен быть ответ
thumbmunkeys

1
Обновил свой ответ выше, поскольку он был принят, и касается улучшения производительности по сравнению с этим ответом, который вызывает уведомления об изменениях для всего в коллекции
Эндрю

3
Отличный ответ. По какой причине вы используете return Utils.LogicalStringCompare(a.Title, b.Title);вместо return string.Compare(a.Title, b.Title);? @NeilW
Джо

2
@Joe, мне нужно было провести логическое сравнение вместо стандартного сравнения строк, поэтому мне нужно было в первую очередь написать расширение. Сравнение логических строк правильно сортирует числа в строках, а не упорядочивает их как строки (1, 2, 20, 1000 вместо 1, 1000, 2, 20 и т. Д.)
NielW 08

4
это абсолютно правильный путь. Я добавил свой собственный ответ, расширяющий это, позволяя вам передавать keySelector вместо использования IComparable, как это обычно делает LINQ.
Jonesopolis

39

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

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

ОБНОВИТЬ

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

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

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

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


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

@Steve Ты можешь попробовать это .
Роман Старков

Спасибо за ссылку, я выбрал метод расширения, так как это казалось самым аккуратным решением. Работает шарм: D
pengibot

bw кто-нибудь заметил, что в блоге есть опечатка в имени файла html (obversablecollection)? : P
laishiekai

1
@romkyns ответ заключался в расширении ObservableCollection <T>. Тогда GridView прекрасно это распознает. Тогда просто спрячьте его методы, как и вы. Я выложу полное решение, когда у меня будет время.
Уэстон

25

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

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/


4
Это очищает ObservableCollection, а затем повторно добавляет все объекты - поэтому стоит отметить, что если ваш пользовательский интерфейс привязан к коллекции, вы не увидите анимированных изменений, например, при перемещении элементов
Карлос П.

1
Я не уверен, почему вам нужно отображать перемещающиеся элементы ... например, вы обычно ObservableCollectionпривязаны к ItemSource выпадающих списков, и вы вообще не видите коллекцию. Кроме того, эта операция очистки и заполнения очень быстрая ... "медленная" может быть уже оптимизированной сортировкой. наконец, вы можете изменить этот код, чтобы реализовать свой метод перемещения, sortedlistа sourceвсе остальное просто.
Jaider

3
Если вы привязаны к раскрывающемуся списку, вам не будет выгодно видеть, как предметы перемещаются, это правда. Если же вы привязаны к ListBox, то такие фреймворки, как WPF, Silverlight или Windows Store Apps, обеспечат полезную визуальную обратную связь по мере повторной индексации объектов в коллекции.
Carlos P

Хотя это быстрее, чем подход Move, он вызывает ряд событий Reset / Add. Ответ, получивший наибольшее количество голосов (подход Move), минимизирует это и справедливо вызывает Moveсобытия, причем только для действительно перемещенных.
nawfal

19

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 для документации и других функций.


6
это действительно сработало: D это намного лучшее решение, чем другое "чрезмерно сложное" решение для такой простой задачи.
MushyPeas

Куда делся ваш блог?
phoog

Проблема с «прозрачными» вещами в том, что вы не видите, куда смотреть, когда это не работает. В документации Microsoft есть 100% прозрачный пример, то есть вы его вообще не видите.
Пол Маккарти,

15

Мне понравился метод расширения пузырьковой сортировки в блоге Ричи выше, но я не обязательно хочу просто сортировать, сравнивая весь объект. Я чаще хочу отсортировать по определенному свойству объекта. Поэтому я изменил его, чтобы он принимал селектор ключей, как это делает 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);

1
Спасибо за публикацию - как указано в комментариях к блогу Ричи, в этот код внесены некоторые полезные улучшения; в частности, используя метод «Переместить» источника. Я предполагаю, что это заменит строки Remove / Insert на source.Move (j-1, j);
Carlos P,

2
Этот алгоритм сортировки не оптимизирован en.wikipedia.org/wiki/Sorting_algorithm
Jaider

@Jaider Да, он оптимизирован, но не для общей сырой скорости.
jv42

Это вызывает количество событий удаления / добавления (для каждого N, по моему мнению). Ответ, получивший наибольшее количество голосов, минимизирует это и справедливо вызывает события перемещения, причем только для действительно перемещенных. Ключевым моментом здесь является не выполнять сортировку на месте сразу, а отсортировать ее извне, OrderByа затем провести сравнение, чтобы выяснить фактическое изменение.
nawfal

11

Ответ @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);

2
Для дополнительного шоколадного печенья вы можете добавить логический параметр «По возрастанию» и if(!Ascending) sorted.Reverse();непосредственно перед for: D (и не нужно беспокоиться о памяти, этот метод Reverse не создает никаких новых объектов, он на месте наоборот)
Sharky

Согласно моим тестам collection.Move (0,0) приводит к событию CollectionChanged. Поэтому было бы улучшением производительности сначала проверить, требуется ли вообще ход.
сб.,

10

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

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

8

Вариант - это когда вы сортируете коллекцию на месте, используя алгоритм сортировки выбора . Элементы перемещаются на место с помощью 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();

1
Это мой предпочтительный способ сортировки из всего описанного здесь, к сожалению, метод Move недоступен в Silverlight 5.
Эдуардо Бритес

1
Я получаю ошибку «Profiler.Profile.ProfileObject» не может использоваться в качестве параметра типа «T» в универсальном типе или методе ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>) ». Нет неявного преобразования ссылки из Profiler.Profile.ProfileObject в System.IComparable <Profiler.Profile.ProfileObject>
New Bee

1
@NewBee: Этот метод расширения определяет общие ограничения на , Tчтобы иметь возможность сортировки элементов в коллекции. Сортировка включает в себя концепцию большего и меньшего, и только вы можете определить, как ProfileObjectона упорядочена. Чтобы использовать метод расширения, вам необходимо реализовать IComparable<ProfileObject>на ProfileObject. Другие альтернативы, как указано, указывают IComparer<ProfileObject>или, соответственно, Func<ProfileObject, ProfileObject, int>изменяют код сортировки.
Martin Liversage

4

Чтобы немного улучшить метод расширения в ответе 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);
            }
        }
    }

2

Вам нужно постоянно сортировать вашу коллекцию? При извлечении пар нужно, чтобы они всегда сортировались или только несколько раз (может быть, просто для представления)? Насколько большой, по вашему мнению, будет ваша коллекция? Есть много факторов, которые могут помочь вам выбрать метод ведьмы.

Если вам нужно, чтобы коллекция постоянно сортировалась, даже когда вы вставляете или удаляете элементы, и скорость вставки не является проблемой, возможно, вам следует реализовать что-то SortedObservableCollectionвроде упомянутого @Gerrie Schenck или проверить эту реализацию .

Если вам нужно, чтобы ваша коллекция была отсортирована всего на несколько раз, используйте:

my_collection.OrderBy(p => p.Key);

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


1
Ссылка в этом ответе относится к лицензированному коду LGPL, поэтому, если вы Silverlight (не можете динамически связывать) или не имеете открытого исходного кода, будьте осторожны с этим кодом.
yzorg 02

2

Мой текущий ответ уже набрал наибольшее количество голосов, но я нашел лучший и более современный способ сделать это.

class MyObject 
{
      public int id { get; set; }
      public string title { get; set; }
}

ObservableCollection<MyObject> myCollection = new ObservableCollection<MyObject>();

//add stuff to collection
// .
// .
// .

myCollection = new ObservableCollection<MyObject>(
    myCollection.OrderBy(n => n.title, Comparer<string>.Create(
    (x, y) => (Utils.Utils.LogicalStringCompare(x, y)))));

не лучше ли обновить исходный ответ?
Натан Хьюз,

Нет. Он уже получил больше голосов, чем любой другой ответ. Я не собираюсь предполагать, что люди предпочли бы поступать так. Просто подумал, что предлагаю другой способ сделать это, тем более что за новые ответы была назначена награда.
NielW 07

1

Создайте новый класс SortedObservableCollection, создайте его ObservableCollectionи реализуйте IComparable<Pair<ushort, string>>.


1

Один из способов - преобразовать его в список, а затем вызвать Sort (), предоставив делегат сравнения. Что-то вроде:-

(непроверено)

my_collection.ToList().Sort((left, right) => left == right ? 0 : (left > right ? -1 : 1));


1

Какого черта, я также добавлю быстро скомбинированный ответ ... он немного похож на некоторые другие реализации здесь, но я добавлю его в любом случае:

(почти не проверял, надеюсь, я себя не смущаю)

Сформулируем сначала несколько целей (мои предположения):

1) Необходимо сортировать ObservableCollection<T> на месте, поддерживать уведомления и т. Д.

2) Не должно быть ужасно неэффективным (т.е. что-то близкое к стандартной "хорошей" эффективности сортировки)

public static class Ext
{
    public static void Sort<T>(this ObservableCollection<T> src)
        where T : IComparable<T>
    {
        // Some preliminary safety checks
        if(src == null) throw new ArgumentNullException("src");
        if(!src.Any()) return;

        // N for the select,
        // + ~ N log N, assuming "smart" sort implementation on the OrderBy
        // Total: N log N + N (est)
        var indexedPairs = src
            .Select((item,i) => Tuple.Create(i, item))
            .OrderBy(tup => tup.Item2);
        // N for another select
        var postIndexedPairs = indexedPairs
            .Select((item,i) => Tuple.Create(i, item.Item1, item.Item2));
        // N for a loop over every element
        var pairEnum = postIndexedPairs.GetEnumerator();
        pairEnum.MoveNext();
        for(int idx = 0; idx < src.Count; idx++, pairEnum.MoveNext())
        {
            src.RemoveAt(pairEnum.Current.Item1);
            src.Insert(idx, pairEnum.Current.Item3);            
        }
        // (very roughly) Estimated Complexity: 
        // N log N + N + N + N
        // == N log N + 3N
    }
}

1

В моем случае ни один из этих ответов не сработал. Либо потому, что он портит привязку, либо требует так много дополнительного кода, что это своего рода кошмар, либо ответ просто не работает. Итак, я подумал, что вот еще один более простой ответ. Это намного меньше кода и остается той же наблюдаемой коллекцией с дополнительным методом типа this.sort. Дайте мне знать, есть ли причина, по которой я не должен делать это таким образом (эффективность и т. Д.)?

public class ScoutItems : ObservableCollection<ScoutItem>
{
    public void Sort(SortDirection _sDir, string _sItem)
    {
             //TODO: Add logic to look at _sItem and decide what property to sort on
            IEnumerable<ScoutItem> si_enum = this.AsEnumerable();

            if (_sDir == SortDirection.Ascending)
            {
                si_enum = si_enum.OrderBy(p => p.UPC).AsEnumerable();
            } else
            {
                si_enum = si_enum.OrderByDescending(p => p.UPC).AsEnumerable();
            }

            foreach (ScoutItem si in si_enum)
            {
                int _OldIndex = this.IndexOf(si);
                int _NewIndex = si_enum.ToList().IndexOf(si);
                this.MoveItem(_OldIndex, _NewIndex);
            }
      }
}

... Где ScoutItem - мой публичный класс. Просто казалось намного проще. Дополнительное преимущество: он действительно работает и не портит привязки, не возвращает новую коллекцию и т. Д.


1

Хорошо, поскольку у меня возникли проблемы с получением ObservableSortedList для работы с XAML, я пошел дальше и создал SortingObservableCollection . Он унаследован от ObservableCollection, поэтому работает с XAML, и я тестировал его на 98% охват кода. Я использовал его в своих приложениях, но не обещаю, что в нем нет ошибок. Не стесняйтесь вносить свой вклад. Вот пример использования кода:

var collection = new SortingObservableCollection<MyViewModel, int>(Comparer<int>.Default, model => model.IntPropertyToSortOn);

collection.Add(new MyViewModel(3));
collection.Add(new MyViewModel(1));
collection.Add(new MyViewModel(2));
// At this point, the order is 1, 2, 3
collection[0].IntPropertyToSortOn = 4; // As long as IntPropertyToSortOn uses INotifyPropertyChanged, this will cause the collection to resort correctly

Это PCL, поэтому он должен работать с Windows Store, Windows Phone и .NET 4.5.1.


1
Вероятно, вам не следует использовать newвсе эти методы, если у кого-то есть более типизированный экземпляр, эти методы не будут вызываться. Вместо этого overrideкаждый переопределяемый метод и измените их по мере необходимости или используйте запасной вариант base.Method(...). Вам, например, даже не нужно беспокоиться, .Addпотому что это внутреннее использование .InsertItem, поэтому, если .InsertItemоно переопределено и настроено, .Addне повлияет на порядок.
HB

1

Вот что я делаю с расширениями OC:

    /// <summary>
    /// Synches the collection items to the target collection items.
    /// This does not observe sort order.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source">The items.</param>
    /// <param name="updatedCollection">The updated collection.</param>
    public static void SynchCollection<T>(this IList<T> source, IEnumerable<T> updatedCollection)
    {
        // Evaluate
        if (updatedCollection == null) return;

        // Make a list
        var collectionArray = updatedCollection.ToArray();

        // Remove items from FilteredViewItems not in list
        source.RemoveRange(source.Except(collectionArray));

        // Add items not in FilteredViewItems that are in list
        source.AddRange(collectionArray.Except(source));
    }

    /// <summary>
    /// Synches the collection items to the target collection items.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source">The source.</param>
    /// <param name="updatedCollection">The updated collection.</param>
    /// <param name="canSort">if set to <c>true</c> [can sort].</param>
    public static void SynchCollection<T>(this ObservableCollection<T> source,
        IList<T> updatedCollection, bool canSort = false)
    {
        // Synch collection
        SynchCollection(source, updatedCollection.AsEnumerable());

        // Sort collection
        if (!canSort) return;

        // Update indexes as needed
        for (var i = 0; i < updatedCollection.Count; i++)
        {
            // Index of new location
            var index = source.IndexOf(updatedCollection[i]);
            if (index == i) continue;

            // Move item to new index if it has changed.
            source.Move(index, i);
        }
    }

1

Это сработало для меня, где-то давно нашел.

// SortableObservableCollection
public class SortableObservableCollection<T> : ObservableCollection<T>
    {
        public SortableObservableCollection(List<T> list)
            : base(list)
        {
        }

        public SortableObservableCollection()
        {
        }

        public void Sort<TKey>(Func<T, TKey> keySelector, System.ComponentModel.ListSortDirection direction)
        {
            switch (direction)
            {
                case System.ComponentModel.ListSortDirection.Ascending:
                    {
                        ApplySort(Items.OrderBy(keySelector));
                        break;
                    }
                case System.ComponentModel.ListSortDirection.Descending:
                    {
                        ApplySort(Items.OrderByDescending(keySelector));
                        break;
                    }
            }
        }

        public void Sort<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer)
        {
            ApplySort(Items.OrderBy(keySelector, comparer));
        }

        private void ApplySort(IEnumerable<T> sortedItems)
        {
            var sortedItemsList = sortedItems.ToList();

            foreach (var item in sortedItemsList)
            {
                Move(IndexOf(item), sortedItemsList.IndexOf(item));
            }
        }
    }

Использование:

MySortableCollection.Sort(x => x, System.ComponentModel.ListSortDirection.Ascending);

0

Мне нужно было сортировать по нескольким вещам, а не по одному. Этот ответ основан на некоторых других ответах, но допускает более сложную сортировку.

static class Extensions
{
    public static void Sort<T, TKey>(this ObservableCollection<T> collection, Func<ObservableCollection<T>, TKey> sort)
    {
        var sorted = (sort.Invoke(collection) as IOrderedEnumerable<T>).ToArray();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

Когда вы его используете, передайте серию вызовов OrderBy / ThenBy. Как это:

Children.Sort(col => col.OrderByDescending(xx => xx.ItemType == "drive")
                    .ThenByDescending(xx => xx.ItemType == "folder")
                    .ThenBy(xx => xx.Path));

0

Я многому научился из других решений, но обнаружил пару проблем. Во-первых, некоторые зависят от IndexOf, который, как правило, работает довольно медленно для больших списков. Во-вторых, в моей ObservableCollection были объекты EF, и использование Remove, похоже, повредило некоторые свойства внешнего ключа. Может я что-то не так делаю.

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

Чтобы решить проблему с производительностью, я создаю словарь с отсортированными по IndexOf значениями. Чтобы поддерживать словарь в актуальном состоянии и сохранить свойства сущности, используйте подкачку, реализованную с двумя перемещениями, вместо одного, как это реализовано в других решениях.

Одно движение сдвигает индексы элементов между местоположениями, что делает словарь IndexOf недействительным. Добавление второго хода для реализации свопа восстанавливает местоположения.

public static void Sort<TSource, TKey>(this ObservableCollection<TSource> collection, Func<TSource, TKey> keySelector)
{
    List<TSource> sorted = collection.OrderBy(keySelector).ToList();
    Dictionary<TSource, int> indexOf = new Dictionary<TSource, int>();

    for (int i = 0; i < sorted.Count; i++)
        indexOf[sorted[i]] = i;

    int idx = 0;
    while (idx < sorted.Count)
        if (!collection[idx].Equals(sorted[idx])) {
            int newIdx = indexOf[collection[idx]]; // where should current item go?
            collection.Move(newIdx, idx); // move whatever's there to current location
            collection.Move(idx + 1, newIdx); // move current item to proper location
        }
        else {
            idx++;
        }
}

-3
var collection = new ObservableCollection<int>();

collection.Add(7);
collection.Add(4);
collection.Add(12);
collection.Add(1);
collection.Add(20);

// ascending
collection = new ObservableCollection<int>(collection.OrderBy(a => a));

// descending
collection = new ObservableCollection<int>(collection.OrderByDescending(a => a));

О, я понял ... Гайот хотел назначить награду за наиболее отвергнутый ответ lol
NielW

Никогда не видел сарказма присуждения награды :)
nawfal
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.