Como faço para classificar uma coleção observável?

Eu tenho uma aula a seguir:

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

Que eu coloquei em um 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")); 

Q: Como faço para classificá-lo por chave?

OP Edit: como muitos apontaram corretamente a resposta original não retorna a mesma coleção, (originalmente focada mais em classificar a parte do dictionary do Q). Por favor, veja editar na parte inferior, onde eu abordo a sorting de uma coleção observável. Original deixado aqui como ainda recebendo votos

Você pode usar o linq como o método doSort abaixo ilustra. Um trecho de código rápido: produz

3: xey 6: fty 7: aaa

Alternativamente, você poderia usar um método de extensão na própria coleção

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

EDITAR

Para retornar um ObservableCollection, chame .ToObservableCollection no sortedOC usando, por exemplo, esta implementação .

OP EDIT Classificando um observável e retornando o mesmo object classificado pode ser feito usando um método de extensão. Para collections maiores, atente para o número de notifications de coleta alteradas, por exemplo

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

uso: Amostra com um observador (usou uma class Person para simplificar)

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

Saída de cima:
removido Katy com idade de 51 no índice 0
adicionou Katy aos 51 anos no índice 8
Mary removido envelheceu 41 no índice 3
adicionou Mary, de 41 anos, no índice 7
Jane removida com 20 anos no índice 3
adicionou Jane 20 anos no índice 5
Jim removido com 39 anos no índice 3
adicionou Jim com 39 anos no índice 6
Jane removido com 20 anos no índice 4
adicionou Jane 20 anos no índice 5

A class Person implementa IComparable e IEquatable, o último é usado para minimizar as alterações na coleção, de modo a reduzir o número de notifications de alteração levantadas

Esta simples extensão funcionou lindamente para mim. Eu só tinha que ter certeza que MyObject era IComparable . Quando o método de sorting é chamado na coleção observável de MyObjects , o método CompareTo em MyObject é chamado, o que chama meu método Logical Sort. Embora não tenha todos os sinos e assobios do resto das respostas postadas aqui, é exatamente o que eu precisava.

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

Eu sei que esta pergunta é antiga, mas aconteceu apenas através dela enquanto pesquisava e encontrei uma input de blog relevante que fornece uma resposta melhor do que as que estão aqui:

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

ATUALIZAR

O ObservableSortedList que @romkyns aponta nos comentários mantém automaticamente a ordem de sorting.

Implementa uma coleção observável que mantém seus itens na ordem classificada. Em particular, as alterações nas propriedades do item que resultam em alterações de pedidos são tratadas corretamente.

Contudo note também a observação

Pode ter erros devido à complexidade comparativa da interface envolvida e à documentação relativamente pobre (consulte https://stackoverflow.com/a/5883947/33080 ).

Você pode usar este método simples:

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

Você pode classificar assim:

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

Mais detalhes: http://jaider.net/2011-05-04/sort-a-observablecollection/

Gostei da abordagem do método de extensão de sorting de bolhas no blog de “Richie” acima, mas não quero necessariamente classificar apenas a comparação do object inteiro. Eu mais frequentemente quero classificar em uma propriedade específica do object. Então eu modifiquei para aceitar um seletor de chave do jeito que o OrderBy faz para que você possa escolher qual propriedade classificar:

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

Qual você chamaria da mesma maneira que chamaria OrderBy, exceto que ele ordenaria a instância existente de seu ObservableCollection em vez de retornar uma nova coleção:

 ObservableCollection people = new ObservableCollection(); ... people.Sort(p => p.FirstName); 

O WPF fornece ordenação ao vivo ListCollectionView uso usando a 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); } } 

Quando essa boot estiver completa, não há mais nada a fazer. A vantagem de uma sorting passiva é que o ListCollectionView faz todo o trabalho pesado de uma maneira transparente para o desenvolvedor. Novos itens são colocados automaticamente em sua ordem de sorting correta. Qualquer class derivada de IComparer de T é adequada para a propriedade de sorting personalizada.

Veja ListCollectionView para a documentação e outros resources.

Eu gostaria de adicionar à resposta de NeilW . Incorporar um método que se pareça com o orderby. Adicione este método como uma extensão:

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

E usar como:

 myCollection = new ObservableCollection(); //Sorts in place, on a specific Func myCollection.Sort(x => x.ID); 

A resposta do @NielW é o caminho a seguir, para uma verdadeira sorting no local. Eu queria adicionar uma solução ligeiramente alterada que permite ignorar o uso de 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); } } 

agora você pode chamá-lo como a maioria de qualquer método LINQ:

 myObservableCollection.Sort(o => o.MyProperty); 

Uma variação é quando você classifica a coleção usando um algoritmo de sorting de seleção . Elementos são movidos para o lugar usando o método Move . Cada movimento triggersrá o evento CollectionChanged com NotifyCollectionChangedAction.Move (e também PropertyChanged com o nome da propriedade Item[] ).

Este algoritmo tem algumas boas propriedades:

  • O algoritmo pode ser implementado como uma sorting estável.
  • O número de itens movidos na coleção (por exemplo, events CollectionChanged triggersdos) é quase sempre menor do que outros algoritmos semelhantes, como inserção de sorting e sorting de bolhas.

O algoritmo é bem simples. A coleção é iterada para encontrar o menor elemento que é movido para o início da coleção. O processo é repetido a partir do segundo elemento e assim por diante, até que todos os elementos tenham sido movidos para o lugar. O algoritmo não é muito eficiente, mas para qualquer coisa que você vai exibir em uma interface de usuário, isso não importa. No entanto, em termos do número de operações de movimentação, é bastante eficiente.

Aqui está um método de extensão que, por simplicidade, requer que os elementos implementem IComparable . Outras opções estão usando um IComparer ou um 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); } } } 

Classificar uma coleção é simplesmente uma questão de invocar o método de extensão:

 var collection = new ObservableCollection(...); collection.Sort(); 

Para melhorar um pouco o método de extensão na resposta xr280xr, adicionei um parâmetro bool opcional para determinar se a sorting está descendente ou não. Incluí também a sugestão feita por Carlos P no comentário a essa resposta. Por favor veja abaixo.

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

Você precisa manter sua coleção ordenada em todos os momentos? Ao recuperar os pares, você precisa que eles sejam sempre classificados, ou é apenas por algumas vezes (talvez apenas por apresentar)? Quão grande você espera que sua coleção seja? Existem muitos fatores que podem ajudá-lo a decidir qual método utilizar.

Se você precisar que a coleção seja ordenada em todos os momentos, mesmo quando você inserir ou excluir elementos e a velocidade de inserção não for um problema, talvez você deva implementar algum tipo de SortedObservableCollection como @Gerrie Schenck mencionado ou confira esta implementação .

Se você precisa que sua coleção seja classificada apenas por algumas vezes, use:

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

Isso levará algum tempo para classificar a coleção, mas, mesmo assim, pode ser a melhor solução, dependendo do que você está fazendo com ela.

Minha resposta atual já tem mais votos, mas encontrei uma maneira melhor e mais moderna de fazer isso.

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

Crie uma nova class SortedObservableCollection , obtenha de ObservableCollection e implemente IComparable> .

Uma maneira seria convertê-lo em uma lista e, em seguida, chamar Sort (), fornecendo um delegado de comparação. Algo como:-

(não testado)

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

O que diabos, eu vou jogar em uma resposta rapidamente-juntos também … parece um pouco como algumas outras implementações aqui, mas vou adicioná-lo anywho:

(mal testado, espero não me envergonhar)

Vamos declarar alguns objectives primeiro (minhas suposições):

1) Deve ordenar ObservableCollection no lugar, para manter notifications, etc.

2) Não deve ser terrivelmente ineficiente (isto é, algo próximo da eficiência de sorting padrão “boa”)

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

Nenhuma dessas respostas funcionou no meu caso. Ou porque isso atrapalha, ou requer tanto código adicional que é um pesadelo, ou a resposta está apenas quebrada. Então, aqui está outra resposta mais simples que eu pensei. É muito menos código e continua sendo a mesma coleção observável com um método adicional. Deixe-me saber se há algum motivo para eu não estar fazendo isso dessa maneira (eficiência, etc.)?

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

… Onde ScoutItem é minha class pública. Parecia muito mais simples. Benefício adicional: ele realmente funciona e não atrapalha com ligações ou retorna uma nova coleção etc.

Tudo bem, desde que eu estava tendo problemas para obter o ObservableSortedList para trabalhar com o XAML, fui em frente e criei o SortingObservableCollection . Ele herda de ObservableCollection, por isso funciona com XAML e eu testei a 98% de cobertura de código. Eu usei isso em meus próprios aplicativos, mas não prometo que ele esteja livre de bugs. Sinta-se livre para contribuir. Aqui está o uso do código de amostra:

 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 

É um PCL, portanto deve funcionar com o Windows Store, Windows Phone e .NET 4.5.1.

Isto é o que eu faço com extensões 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); } } 

Isso funcionou para mim, achei há muito tempo atrás em algum lugar.

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

Uso:

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

Eu precisava ser capaz de classificar por várias coisas, não apenas uma. Esta resposta é baseada em algumas das outras respostas, mas permite uma sorting mais complexa.

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

Quando você usá-lo, passe em uma série de chamadas OrderBy / ThenBy. Como isso:

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