¿Cómo ordeno una colección observable?

Tengo una siguiente clase:

[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 he puesto en una 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")); 

P: ¿Cómo lo clasifico por clave?

OP Edit: como muchos han señalado correctamente, la respuesta original no devuelve la misma colección (originalmente se centró más en ordenar la parte del diccionario de la Q). Por favor, vea editar en la parte inferior donde abordo la clasificación de una colección observable. Original dejado aquí como sigue recibiendo votos

Puede usar linq como lo ilustra el método doSort a continuación. Un fragmento de código rápido: produce

3: xey 6: fty 7: aaa

Alternativamente, puede usar un método de extensión en la colección en sí

 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 devolver un ObservableCollection, llame a .ToObservableCollection en sortedOC utilizando, por ejemplo, esta implementación .

OP EDIT Ordenando un observable y devolviendo el mismo objeto ordenado se puede hacer usando un método de extensión. Para colecciones más grandes, ten cuidado con el número de notificaciones cambiadas de colección, por ejemplo

 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: muestra con un observador (se utilizó una clase Person para que sea sencillo)

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

Salida desde arriba:
eliminó a Katy de 51 años en el índice 0
Katy agregó 51 años en el índice 8
eliminó a Mary de 41 años en el índice 3
agregó Mary de 41 años en el índice 7
eliminó a Jane de 20 años en el índice 3
agregó Jane de 20 años en el índice 5
eliminado Jim de 39 años en el índice 3
agregó Jim de 39 años en el índice 6
eliminó a Jane de 20 años en el índice 4
agregó Jane de 20 años en el índice 5

La clase Person implementa tanto IComparable como IEquatable; esta última se utiliza para minimizar los cambios en la colección a fin de reducir el número de notificaciones de cambio

Esta simple extensión funcionó maravillosamente para mí. Solo tenía que asegurarme de que MyObject fuera IComparable . Cuando se MyObjects el método de MyObject en la colección observable de MyObjects , se CompareTo método MyObject en MyObject , que llama a mi método de Clasificación lógica. Si bien no tiene todos los detalles del rest de las respuestas publicadas aquí, es exactamente lo que necesitaba.

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

Sé que esta pregunta es antigua, pero sucedió al cruzarla mientras buscaba en Google y encontré una entrada de blog relevante que proporciona una mejor respuesta que las que están aquí:

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

ACTUALIZAR

El ObservableSortedList que @romkyns señala en los comentarios mantiene automáticamente el orden de clasificación.

Implementa una colección observable que mantiene sus artículos en orden ordenado. En particular, los cambios en las propiedades de los elementos que resultan en cambios de orden se manejan correctamente.

Sin embargo, tenga en cuenta también el comentario

Puede tener errores debido a la complejidad comparativa de la interfaz involucrada y su documentación relativamente pobre (consulte https://stackoverflow.com/a/5883947/33080 ).

Puedes usar este método simple:

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

Puedes ordenar de esta manera:

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

Más detalles: http://jaider.net/2011-05-04/sort-a-observablecollection/

Me gustó el enfoque de método de extensión de burbuja de burbuja en el blog de “Richie” más arriba, pero no necesariamente quiero solo comparar el objeto completo. Más a menudo quiero ordenar en una propiedad específica del objeto. Así que lo modifiqué para aceptar un selector de teclas del mismo modo que lo hace OrderBy para que pueda elegir qué propiedad ordenar:

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

Que llamaría de la misma manera que llamaría OrderBy, excepto que clasificará la instancia existente de su ObservableCollection en lugar de devolver una nueva colección:

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

WPF proporciona clasificación en vivo de ListCollectionView clase 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); } } 

Una vez que se completa esta inicialización, no hay nada más que hacer. La ventaja sobre una clasificación pasiva es que ListCollectionView hace todo el trabajo pesado de una manera transparente para el desarrollador. Los nuevos elementos se colocan automáticamente en su orden de clasificación correcto. Cualquier clase que se derive de IComparer de T es adecuada para la propiedad de clasificación personalizada.

Vea ListCollectionView para la documentación y otras características.

Me gustaría agregar a la respuesta de NeilW . Para incorporar un método que se asemeja al orden por. Agregue este método como una extensión:

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

Y use como:

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

La respuesta de @ NielW es el camino a seguir, para una clasificación real en el lugar. Quería agregar una solución ligeramente alterada que le permita eludir el 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); } } 

ahora puedes llamarlo como cualquier otro método LINQ:

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

Una variación es donde ordena la colección en el lugar usando un algoritmo de ordenación de selección . Los elementos se mueven a su lugar usando el método Move . Cada movimiento activará el evento CollectionChanged con NotifyCollectionChangedAction.Move (y también PropertyChanged con el nombre de propiedad Item[] ).

Este algoritmo tiene algunas buenas propiedades:

  • El algoritmo se puede implementar como un tipo estable.
  • La cantidad de elementos que se mueven en la colección (por ejemplo CollectionChanged eventos CollectionChanged despedidos) es casi siempre menor que otros algoritmos similares, como sorting de inserción y sort de burbuja.

El algoritmo es bastante simple. La colección se itera para encontrar el elemento más pequeño que luego se mueve al inicio de la colección. El proceso se repite comenzando en el segundo elemento y así sucesivamente hasta que todos los elementos se hayan movido a su lugar. El algoritmo no es terriblemente eficiente, pero para cualquier cosa que va a mostrar en una interfaz de usuario no debería importar. Sin embargo, en términos de cantidad de movimientos, es bastante eficiente.

Aquí hay un método de extensión que por simplicidad requiere que los elementos implementen IComparable . Otras opciones son usar un IComparer o un 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); } } } 

Ordenar una colección es simplemente una cuestión de invocar el método de extensión:

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

Para mejorar un poco el método de extensión en la respuesta xr280xr agregué un parámetro bool opcional para determinar si la clasificación es descendente o no. También incluí la sugerencia hecha por Carlos P en el comentario a esa respuesta. Por favor ver más abajo.

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

¿Necesita mantener su colección ordenada en todo momento? Al recuperar los pares, ¿necesita que estén siempre ordenados, o solo unas pocas veces (quizás solo para presentarlos)? ¿Qué tan grande espera que sea su colección? Hay muchos factores que pueden ayudarlo a decidir qué método usar.

Si necesita que la colección se ordene en todo momento, incluso cuando inserte o elimine elementos y la velocidad de inserción no sea un problema, tal vez debería implementar algún tipo de SortedObservableCollection como @Gerrie Schenck mencionado o verificar esta implementación .

Si necesita que su colección se clasifique solo por unas pocas veces, utilice:

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

Esto llevará algún tiempo para ordenar la colección, pero aún así, podría ser la mejor solución dependiendo de lo que haga con ella.

Mi respuesta actual ya tiene la mayoría de los votos, pero encontré una forma mejor y más moderna de hacerlo.

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

Haz una nueva clase SortedObservableCollection , SortedObservableCollection de ObservableCollection e implementa IComparable> .

Una forma sería convertirlo en una Lista y luego llamar a Sort (), proporcionando un delegado de comparación. Algo como:-

(no probado)

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

¿Qué diablos, voy a arrojar una respuesta rápidamente improvisada también … se parece un poco a algunas otras implementaciones aquí, pero lo agregaré de todos los que:

(apenas probado, con suerte no me estoy avergonzando)

Vamos a mencionar algunos objectives primero (mis suposiciones):

1) Debe ordenar ObservableCollection en su lugar, para mantener notificaciones, etc.

2) No debe ser terriblemente ineficiente (es decir, algo cercano a la eficiencia de clasificación “buena” estándar)

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

Ninguna de estas respuestas funcionó en mi caso. Ya sea porque arruina el encuadernado, o requiere tanta encoding adicional que es una especie de pesadilla, o la respuesta simplemente se rompe. Entonces, aquí hay otra respuesta más simple, pensé. Es mucho menos código y sigue siendo la misma colección observable con un método adicional de este tipo. Avíseme si hay alguna razón por la que no debería hacerlo de esta manera (eficiencia, 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); } } } 

… Donde ScoutItem es mi clase pública. Simplemente parecía mucho más simple. Beneficio adicional: realmente funciona y no estropea las fijaciones ni devuelve una nueva colección, etc.

De acuerdo, ya que estaba teniendo problemas para que ObservableSortedList funcione con XAML, seguí y creé SortingObservableCollection . Hereda de ObservableCollection, por lo que funciona con XAML y la unidad lo ha probado con una cobertura de código del 98%. Lo he usado en mis propias aplicaciones, pero no prometo que está libre de errores. Siéntete libre de contribuir. Aquí está el uso del código de muestra:

 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 

Es una PCL, por lo que debería funcionar con Windows Store, Windows Phone y .NET 4.5.1.

Esto es lo que hago con extensiones 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); } } 

Esto funcionó para mí, lo encontré hace mucho tiempo en alguna parte.

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

Necesitaba poder ordenar por varias cosas, no solo una. Esta respuesta se basa en algunas de las otras respuestas, pero permite una clasificación más compleja.

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

Cuando lo use, pase una serie de llamadas OrderBy / ThenBy. Me gusta esto:

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