Как сортировать наблюдаемую коллекцию?
У меня есть следующий class:
[DataContract] public class Pair : 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> my_collection = new ObservableCollection<Pair>(); my_collection.Add(new Pair(7, "aaa")); my_collection.Add(new Pair(3, "xey")); my_collection.Add(new Pair(6, "fty"));
В: Как отсортировать его по ключу?
- Как создать DataTemplate в коде c #?
- Передача данных между формами WPF
- Проблема с привязкой DependencyProperty
- В чем разница между свойством Property и Dependency Property
- Разница между SelectedItem, SelectedValue и SelectedValuePath
- WPF / MVVM - как обрабатывать двойной щелчок на TreeViewItems в ViewModel?
- В WPF можно фильтровать CollectionViewSource без кода?
- Создание BitmapImage из массива байтов
- Можно ли выбрать текстовый блок WPF?
- Как вы можете клонировать объект WPF?
- Цвет переднего плана Progressbar
- Поиск конкретных цветов пикселей BitmapImage
- как захватить символ «#» на разных языковых клавиатурах в WPF / C #?
OP Edit: многие из них правильно указали, что исходный ответ не возвращает одну и ту же коллекцию (первоначально сфокусированный на сортировке словарной части Q). См. Редактирование внизу, где я обращаюсь к сортировке наблюдаемой коллекции. Первоначально осталось здесь, все еще получая голоса
Вы можете использовать linq, как показано ниже в примере метода doSort. Быстрый fragment кода: производит
3: xey 6: fty 7: aaa
В качестве альтернативы вы можете использовать метод расширения в самой коллекции
var sortedOC = _collection.OrderBy(i => i.Key); private void doSort() { ObservableCollection> _collection = new ObservableCollection>(); _collection.Add(new Pair(7,"aaa")); _collection.Add(new Pair(3, "xey")); _collection.Add(new Pair(6, "fty")); var sortedOC = from item in _collection orderby item.Key select item; foreach (var i in sortedOC) { Debug.WriteLine(i); } } public class Pair { 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; } }
РЕДАКТИРОВАТЬ
Чтобы вернуть ObservableCollection, вызовите .ToObservableCollection на sortedOC, используя, например, эту реализацию .
OP EDIT. Сортировка наблюдаемого и возвращающего один и тот же отсортированный объект может быть выполнена с использованием метода расширения. Для больших коллекций следите за количеством измененных уведомлений об коллекции, например
public static void Sort(this ObservableCollection observable) where T : IComparable , IEquatable { List 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++; } } }
использование: образец с наблюдателем (используется class Person, чтобы он был простым)
public class Person:IComparable,IEquatable { 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 () { 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, последний используется для минимизации изменений в коллекции, чтобы уменьшить количество уведомлений об изменениях
Это простое расширение прекрасно работало для меня. Я просто должен был убедиться, что MyObject
был IComparable
. Когда метод sort вызывается в наблюдаемой коллекции MyObjects
, вызывается метод CompareTo
в MyObject
, который вызывает мой метод Logical Sort. Хотя у него нет всех колоколов и свистков остальных ответов, размещенных здесь, это именно то, что мне нужно.
static class Extensions { public static void Sort(this ObservableCollection collection) where T : IComparable { List 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(); //add stuff to collection myCollection.Sort();
Я знаю, что этот вопрос старый, но только что произошло во время поиска в Интернете и нашел соответствующую запись в блоге, которая дает лучший ответ, чем те, что здесь:
http://kiwigis.blogspot.com/2010/03/how-to-sort-obversablecollection.html
ОБНОВИТЬ
ObservableSortedList, который указывает @romkyns в комментариях, автоматически поддерживает порядок сортировки.
Реализует наблюдаемую коллекцию, которая сохраняет свои позиции в отсортированном порядке. В частности, обрабатываются изменения свойств элемента, которые приводят к изменениям порядка.
Однако обратите внимание также на замечание
Может быть ошибкой из-за сравнительной сложности интерфейса и его относительно плохой документации (см. https://stackoverflow.com/a/5883947/33080 ).
Вы можете использовать этот простой метод:
public static void Sort(this Collection source, Func keySelector) { List 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/
Мне понравился подход метода расширения пузырьков в блоге «Ричи» выше, но я не хочу просто сортировать сравнение всего объекта. Я чаще всего хочу сортировать по определенному свойству объекта. Поэтому я изменил его, чтобы принять ключевой селектор, как делает OrderBy, чтобы вы могли выбрать, какое свойство сортировать:
public static void Sort(this ObservableCollection source, Func keySelector) { if (source == null) return; Comparer comparer = Comparer .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 people = new ObservableCollection (); ... people.Sort(p => p.FirstName);
WPF обеспечивает прямую сортировку из коробки с ListCollectionView
classа ListCollectionView
…
public ObservableCollection MyStrings { get; set; } private ListCollectionView _listCollectionView; private void InitializeCollection() { MyStrings = new ObservableCollection (); _listCollectionView = CollectionViewSource.GetDefaultView(MyStrings) as ListCollectionView; if (_listCollectionView != null) { _listCollectionView.IsLiveSorting = true; _listCollectionView.CustomSort = new CaseInsensitiveComparer(CultureInfo.InvariantCulture); } }
Как только эта инициализация завершена, вам больше нечего делать. Преимущество над пассивной сортировкой заключается в том, что ListCollectionView делает весь тяжелый подъем способом, прозрачным для разработчика. Новые элементы автоматически помещаются в их правильный порядок сортировки. Любой class, который происходит от IComparer
of T, подходит для пользовательского свойства сортировки.
См. ListCollectionView для документации и других функций.
Я хотел бы добавить в ответ NeilW . Включить метод, похожий на порядок. Добавьте этот метод в качестве расширения:
public static void Sort(this ObservableCollection collection, Func keySelector) where T : IComparable { List sorted = collection.OrderBy(keySelector).ToList(); for (int i = 0; i < sorted.Count(); i++) collection.Move(collection.IndexOf(sorted[i]), i); }
И используйте как:
myCollection = new ObservableCollection(); //Sorts in place, on a specific Func myCollection.Sort(x => x.ID);
@ Ответ NielW – это путь для реальной сортировки на месте. Я хотел добавить немного измененное решение, которое позволяет обойти использование IComparable
:
static class Extensions { public static void Sort(this ObservableCollection collection, Func keySelector) { List 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);
Вариант – это то, где вы сортируете коллекцию на месте с помощью алгоритма сортировки выбора . Элементы перемещаются на место с помощью метода Move
. Каждое перемещение NotifyCollectionChangedAction.Move
событие CollectionChanged
с NotifyCollectionChangedAction.Move
(а также PropertyChanged
с именем свойства Item[]
).
Этот алгоритм обладает некоторыми хорошими свойствами:
- Алгоритм может быть реализован как стабильный вид.
- Количество элементов, перемещенных в коллекции (например, события
CollectionChanged
), почти всегда меньше, чем другие аналогичные алгоритмы, такие как сортировка вставки и сортировка пузырьков.
Алгоритм довольно прост. Коллекция итерируется, чтобы найти наименьший элемент, который затем перемещается в начало коллекции. Процесс повторяется, начиная со второго элемента и так далее, пока все элементы не будут перемещены на место. Алгоритм не очень эффективен, но для всего, что вы собираетесь отображать в пользовательском интерфейсе, это не имеет значения. Однако с точки зрения количества операций перемещения это довольно эффективно.
Вот метод расширения, который для простоты требует, чтобы элементы реализовали IComparable
. Другие опции используют IComparer
или Func
.
public static class ObservableCollectionExtensions { public static void Sort(this ObservableCollection collection) where T : IComparable { 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(...); collection.Sort();
Чтобы немного улучшить метод расширения на xr280xr, я добавил необязательный параметр bool, чтобы определить, уменьшается ли сортировка. Я также включил предложение, сделанное Карлосом П. в комментарии к этому ответу. См. Ниже.
public static void Sort(this ObservableCollection source, Func keySelector, bool desc = false) { if (source == null) return; Comparer comparer = Comparer .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); } } }
Вам нужно постоянно сортировать свою коллекцию? При извлечении пар вам нужно, чтобы они всегда сортировались, или это всего лишь несколько раз (может быть, просто для представления)? Насколько вы ожидаете от своей коллекции? Есть много факторов, которые могут помочь вам выбрать метод ведьмы.
Если вам нужна коллекция, которую нужно сортировать в любое время, даже если вы вставляете или удаляете элементы, а скорость вставки не является проблемой, возможно, вам следует реализовать какой-то SortedObservableCollection
например, @Gerrie Schenck, упомянутый или проверяющий эту реализацию .
Если вам нужна ваша коллекция, отсортированная всего в несколько раз, используйте:
my_collection.OrderBy(p => p.Key);
Это займет некоторое время, чтобы отсортировать коллекцию, но даже в этом случае это может быть лучшим решением в зависимости от того, что вы делаете с ним.
Мой текущий ответ уже имеет больше всего голосов, но я нашел лучший и более современный способ сделать это.
class MyObject { public int id { get; set; } public string title { get; set; } } ObservableCollection myCollection = new ObservableCollection (); //add stuff to collection // . // . // . myCollection = new ObservableCollection ( myCollection.OrderBy(n => n.title, Comparer.Create( (x, y) => (Utils.Utils.LogicalStringCompare(x, y)))));
Создайте новый class SortedObservableCollection
, SortedObservableCollection
его из ObservableCollection
и выполните IComparable
.
Один из способов – преобразовать его в список, а затем вызвать Sort (), предоставив делегата сравнения. Что-то вроде:-
(Непроверенные)
my_collection.ToList().Sort((left, right) => left == right ? 0 : (left > right ? -1 : 1));
Я думаю, что это самое элегантное решение:
Какая чертовщина, я тоже выложу ответ на быстрый булыжник … он немного похож на некоторые другие реализации здесь, но я добавлю его anywho:
(едва испытал, надеюсь, я не смущаюсь)
Давайте сначала сформулируем некоторые цели (мои предположения):
1) Должен сортировать ObservableCollection
на месте, поддерживать уведомления и т. Д.
2) Не должно быть ужасно неэффективным (т. Е. Что-то близкое к стандартной «хорошей» эффективности сортировки)
public static class Ext { public static void Sort(this ObservableCollection src) where T : IComparable { // 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 } }
Ни один из этих ответов не работал в моем случае. Либо потому, что он затягивает привязку, либо требует слишком много дополнительного кодирования, что это своего рода кошмар, или ответ просто сломан. Итак, вот еще один более простой ответ, который я подумал. Это намного меньше кода, и он остается той же наблюдаемой коллекцией с дополнительным типом этого типа. Дайте мне знать, если есть какая-то причина, я не должен делать это так (эффективность и т. Д.)?
public class ScoutItems : ObservableCollection { public void Sort(SortDirection _sDir, string _sItem) { //TODO: Add logic to look at _sItem and decide what property to sort on IEnumerable 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 – мой публичный class. Просто казалось намного проще. Добавленная выгода: она действительно работает и не путается с привязками или возвращает новую коллекцию и т. Д.
Хорошо, так как у меня возникли проблемы с ObservableSortedList для работы с XAML, я пошел вперед и создал SortingObservableCollection . Он наследуется от ObservableCollection, поэтому он работает с XAML, и я тестировал его на 98% охвата кода. Я использовал его в своих приложениях, но я не буду обещать, что это ошибка. Не стесняйтесь вносить свой вклад. Вот пример использования кода:
var collection = new SortingObservableCollection(Comparer.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.
Это то, что я делаю с расширениями OC:
/// /// Synches the collection items to the target collection items. /// This does not observe sort order. /// /// /// The items. /// The updated collection. public static void SynchCollection(this IList source, IEnumerable 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)); } /// /// Synches the collection items to the target collection items. /// /// /// The source. /// The updated collection. /// if set to true [can sort]. public static void SynchCollection (this ObservableCollection source, IList 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); } }
Это сработало для меня, это было где-то давно.
// SortableObservableCollection public class SortableObservableCollection : ObservableCollection { public SortableObservableCollection(List list) : base(list) { } public SortableObservableCollection() { } public void Sort(Func 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 (Func keySelector, IComparer comparer) { ApplySort(Items.OrderBy(keySelector, comparer)); } private void ApplySort(IEnumerable sortedItems) { var sortedItemsList = sortedItems.ToList(); foreach (var item in sortedItemsList) { Move(IndexOf(item), sortedItemsList.IndexOf(item)); } } }
Применение:
MySortableCollection.Sort(x => x, System.ComponentModel.ListSortDirection.Ascending);
Мне нужно было иметь возможность сортировать по нескольким вещам, а не по одному. Этот ответ основан на некоторых других ответах, но он позволяет более сложную сортировку.
static class Extensions { public static void Sort(this ObservableCollection collection, Func, TKey> sort) { var sorted = (sort.Invoke(collection) as IOrderedEnumerable ).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));
var collection = new ObservableCollection(); collection.Add(7); collection.Add(4); collection.Add(12); collection.Add(1); collection.Add(20); // ascending collection = new ObservableCollection (collection.OrderBy(a => a)); // descending collection = new ObservableCollection (collection.OrderByDescending(a => a));