Як сортувати спостережувану колекцію?


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

З: Як я сортую його за клавішею?


Ви шукаєте реалізацію сортування в класі або буде виконано будь-який тип сортування?
okw

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

Дивіться це для рішення WPF stackoverflow.com/questions/1945461/…
Gayot Fow

Подивіться на відповіді на цій сторінці: дуже чітка вказівка ​​на зламаний API, коли для отримання критичних та основних функціональних можливостей потрібні 22+ відповіді.
Геррі

Відповіді:


19

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

Я оновив свій код для підвищення продуктивності та обробки дублікатів (завдяки 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, останній використовується для мінімізації змін колекції, щоб зменшити кількість порушених повідомлень про зміни

  • EDIT Сортує ту саму колекцію без створення нової копії *

Щоб повернути ObservableCollection, зателефонуйте .ToObservableCollection на * sortedOC *, використовуючи, наприклад, [цю реалізацію] [1].

**** відповідь оригіналу - це створює нову колекцію ****. Ви можете використовувати linq, як показано нижче. Швидкий фрагмент коду: виробляє

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

Виявив це, і це найбільше допоможе. Це LINQ, який складає vartedOC var?
Jason94

9
Не є прихильником цієї відповіді, тому що вона не дає вам відсортованої ObservableCollection.
xr280xr

63
-1 , так як це не сортує , але замість цього створює нову колекцію. ObservableCollection
Кос

2
Оновлений код буде працювати, але має складну часову складність O (n ^ 2). Це можна покращити до O (n * log (n)), використовуючи BinarySearchзамість IndexOf.
Вільям Моррісон

2
Відмінне рішення! Для тих, хто успадковує ObservableCollection <T>, можна використовувати захищений метод MoveItem () замість методів RemoveAt та Insert. Дивіться також: referenceource.microsoft.com/#system/compmod/system/…
Герман Кордес

84

Це просте розширення спрацювало прекрасно для мене. Я просто повинен був переконатися, що це MyObjectбуло IComparable. Коли викликується метод сортування в колекції MyObjects, що спостерігається, викликається CompareToметод on 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
Joe

2
@Joe, мені потрібно було зробити логічне порівняння замість стандартного порівняння рядків, саме тому мені потрібно було написати розширення в першу чергу. Логічний рядок порівняє сортування чисел у рядках належним чином, а не впорядковує їх як рядки (1, 2, 20, 1000 замість 1, 1000, 2, 20 тощо)
NielW

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, після чого знову додає всі об'єкти - тому варто зазначити, що якщо ваш інтерфейс зв’язаний із колекцією, ви не побачили б анімованих змін, наприклад, коли елементи переміщуються
Carlos P

1
Я не знаю, чому ви повинні відображати предмети, що рухаються навколо ... наприклад, ви, як правило, ObservableCollectionпов'язані з ItemSource випадаючих і колекції ви взагалі не бачите. Також ця операція очищення та заповнення є надшвидкою ... "повільна" може бути такою, яка вже оптимізована. нарешті, ви можете змінити цей код, щоб реалізувати свій метод переміщення, мати sortedlistта sourceінше легко.
Джайдер

3
Якщо ви прив'язуєтесь до випадаючого меню, ви не отримаєте користі від перегляду предметів, що рухаються, це правда. Якщо ви прив’язані до ListBox, тоді такі рамки, як WPF або Silverlight або Windows Store Apps, надаватимуть корисні візуальні відгуки, оскільки об’єкти колекції переіндексуються.
Карлос П

Хоча це швидше, ніж підхід Move, це викликає ряд подій Скидання / Додати. Найвища відповідь (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 робить все важке підйом таким чином, щоб він був прозорим для розробника. Нові елементи автоматично розміщуються в правильному порядку сортування. Будь-який клас, що походить від IComparerT, підходить для властивості користувальницького сортування.

Дивіться ListCollectionView для документації та інших функцій.


6
що насправді спрацювало: D це набагато краще рішення, ніж інше "перенастроєне" рішення для такого простого завдання.
MushyPeas

Куди пішов ваш блог?
понеділок

Проблема з "прозорими" речами полягає в тому, що ви не бачите, де шукати, коли це не працює. Документація 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
Дякуємо, що опублікували це - як зазначалося в коментарях до блогу Річі, цей код є вагомим удосконаленням; зокрема, використовуючи метод "Переміщення" джерела. Я думаю, це замінить рядки "Видалити / вставити" на джерело. Перехід (j-1, j);
Карлос П

2
Цей алгоритм сортування не оптимізований en.wikipedia.org/wiki/Sorting_algorithm
Jaider

@Jaider Так, це оптимізовано, просто не для загальної швидкості в режимі сировини.
jv42

Це викликає кількість видалення / додавання подій (для кожного я вважаю). Найвища відповідь, що голосує, мінімізує це і справедливо піднімає Move події, що теж тільки для дійсно переміщених. Ключове значення тут полягає в тому, щоб не робити сортування на місці, а сортувати його зовнішньо, 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
Для додаткового шоколадного файлу cookie ви можете додати логічний параметр "Висхідний" та if(!Ascending) sorted.Reverse();безпосередньо перед for: D (і не потрібно додатково - турбуватися про пам'ять, що метод "Реверс" не створює нових об'єктів, він знаходиться на місці зворотним)
Шаркі

Згідно з моєю колекцією тестів. Move (0,0) призводить до події CollectionChanged. Тому було б краще покращити продуктивність, щоб спочатку перевірити, чи потрібен навіть хід.
sa.he

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>. Інші параметри використовують a IComparer<T>або a 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.
Eduardo Brites

1
Я отримую помилку "Profiler.Profile.ProfileObject" не можна використовувати як параметр типу "T" у загальному типі або методі "ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>)". Немає неявного перетворення посилань із "Profiler.Profile.ProfileObject" в "System.IComparable <Profiler.Profile.ProfileObject>
Нова Бджола

1
@NewBee: Цей метод розширення визначає загальні обмеження на , Tщоб мати можливість сортування елементів в колекції. Сортування включає поняття більшого і меншого, і тільки ви можете визначити, як ProfileObjectвпорядковано. Щоб використовувати метод розширення , що вам потрібно реалізувати IComparable<ProfileObject>на ProfileObject. Інші альтернативи, як зазначено, вказують a IComparer<ProfileObject>або a Func<ProfileObject, ProfileObject, int>і відповідно змінюють код сортування.
Мартін Ліверсанс

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

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

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не буде возитися з замовленням.
НВ

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, а використання видалення, здавалося, пошкоджувало деякі властивості зовнішнього ключа. Можливо, я роблю щось не так.

Незалежно від цього, Move можна використовувати замість Remove / Insert, але це спричиняє деякі проблеми з виправленням продуктивності.

Щоб вирішити проблему з продуктивністю, я створюю словник із відсортованими значеннями 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.