¿Cómo clasifico una colección observable?

97

Tengo una clase siguiente:

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

Que he puesto en un 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"));

P: ¿Cómo lo clasifico por clave?

Maciek
fuente
¿Está buscando una implementación de clasificación dentro de la clase o simplemente servirá cualquier tipo de clasificación?
okw
No estoy seguro de cómo entender eso. Básicamente, solo quiero ordenarlo, la colección no va a ser muy grande (20 elementos como máximo), por lo que cualquier cosa servirá (lo más probable)
Maciek
Vea esto para una solución WPF stackoverflow.com/questions/1945461/…
Gayot Fow
Mire las respuestas en esta página: indicación muy clara de una API rota cuando se necesitan más de 22 respuestas para algunas funciones críticas y básicas.
Gerry
Posible duplicado de Sort ObservableCollection <string> hasta C #
Tim Pohlmann

Respuestas:

19

Se puede ordenar un observable y devolver el mismo objeto ordenado mediante un método de extensión. Para colecciones más grandes, tenga cuidado con la cantidad de notificaciones de cambios de colección.

He actualizado mi código para mejorar el rendimiento y manejar duplicados (gracias a nawfal por resaltar el bajo rendimiento del original a pesar de que funcionó bien en el ejemplo de datos original). El observable se divide en una mitad ordenada a la izquierda y una mitad derecha sin clasificar, donde cada vez que el elemento mínimo (como se encuentra en la lista ordenada) se desplaza al final de la partición ordenada desde la no ordenada. Peor caso O (n). Esencialmente una ordenación por selección (consulte la salida a continuació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
            }

uso: muestra con un observador (usó una clase Person para que sea simple)

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

Detalles del progreso de clasificación que muestran cómo se pivota la colección:

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,

La clase Person implementa IComparable e IEquatable, este último se usa para minimizar los cambios en la colección a fin de reducir la cantidad de notificaciones de cambios generadas

  • EDITAR Ordena la misma colección sin crear una nueva copia *

Para devolver un ObservableCollection, llame a .ToObservableCollection en * sortedOC * usando, por ejemplo, [esta implementación] [1].

**** respuesta orig: esto crea una nueva colección **** 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 propia colección

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;
    }
}
Andrés
fuente
Encontré esto y lo encontré de lo más útil. ¿Es LINQ el que forma la var sortedOC?
Jason94
9
No soy un fanático de esta respuesta porque no le brinda una colección Observable ordenada.
xr280xr
63
-1 ya que no ordena el ObservableCollection , sino que crea una nueva colección.
Kos
2
El código actualizado funcionará, pero tiene una complejidad de tiempo O (n ^ 2). Esto se puede mejorar a O (n * log (n)) usando en BinarySearchlugar de IndexOf.
William Morrison
2
Excelente solucion! Para los que heredan de ObservableCollection <T>, es posible usar el método protegido MoveItem () en lugar de usar los métodos RemoveAt e Insert. Véase también: referenceource.microsoft.com/#system/compmod/system/…
Herman Cordes
84

Esta simple extensión funcionó muy bien para mí. Solo tenía que asegurarme de que así MyObjectfuera IComparable. Cuando se llama al método sort en la colección observable de MyObjects, se llama al CompareTométodo on MyObject, que llama a mi método Logical Sort. Si bien no tiene todas las campanas y silbidos del resto de las respuestas publicadas aquí, es exactamente lo que necesitaba.

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();
NielW
fuente
7
esta debería ser la respuesta
thumbmunkeys
1
Actualicé mi respuesta anterior, ya que era la aceptada y aborda la mejora del rendimiento con respecto a esta respuesta, lo que genera notificaciones de cambio para todo en la colección
Andrew
3
Gran respuesta. ¿Alguna razón por la que usas en return Utils.LogicalStringCompare(a.Title, b.Title);lugar de return string.Compare(a.Title, b.Title);? @NeilW
Joe
2
@Joe, necesitaba hacer una comparación lógica en lugar de una comparación de cadenas estándar, razón por la cual necesitaba escribir la extensión en primer lugar. La comparación de cadenas lógicas ordena correctamente los números en cadenas, en lugar de ordenarlos como cadenas (1, 2, 20, 1000 en lugar de 1, 1000, 2, 20, etc.)
NielW
4
este es absolutamente el camino a seguir. Agregué una respuesta propia extendiendo esto, lo que le permite pasar un keySelector en lugar de usar IComparable, como suele hacer LINQ.
Jonesopolis
39

Encontré una entrada de blog relevante que proporciona una mejor respuesta que las de aquí:

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

ACTUALIZAR

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

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

Sin embargo, tenga en cuenta también la observación

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

Eric J.
fuente
2
De hecho, este blog es más útil. Sin embargo, todavía tengo que encontrar una respuesta decente a la pregunta de tener una colección observable que mantenga su clasificación a medida que se agregan y eliminan elementos. Voy a escribir el mío, creo.
Stephen Drew
@Steve Puedes probar este .
Roman Starkov
Gracias por el enlace, fui con el método de extensión ya que parecía la solución más ordenada. Funciona de
maravilla
¿Alguien notó que el blog tiene un error tipográfico en el nombre del archivo html (obversablecollection)? : P
laishiekai
1
@romkyns la respuesta fue extender ObservableCollection <T>. GridView lo reconoce muy bien entonces. Luego, simplemente oculta sus métodos, como lo haces tú. Publicaré una solución completa cuando tenga tiempo.
Weston
25

Puede utilizar este sencillo método:

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

Puedes ordenar así:

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

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

Jaider
fuente
4
Esto borra ObservableCollection y luego vuelve a agregar todos los objetos, por lo que vale la pena señalar que si su interfaz de usuario está vinculada a la colección, no verá cambios animados, por ejemplo, cuando los elementos se mueven
Carlos P
1
No estoy seguro de por qué tiene que mostrar elementos en movimiento ... por ejemplo, normalmente se ha ObservableCollectionvinculado a ItemSource de los menús desplegables y no ve la colección en absoluto. Además, esta operación de limpieza y llenado es ultra rápida ... la "lenta" puede ser del tipo que ya está optimizado. finalmente, puede modificar este código para implementar su método de movimiento, tener el sortedlisty sourceel resto es fácil.
Jaider
3
Si está vinculado a un menú desplegable, no se beneficiaría de ver elementos en movimiento, eso es cierto. Sin embargo, si está vinculado a un ListBox, los marcos como WPF o Silverlight o las aplicaciones de la Tienda Windows proporcionarán comentarios visuales útiles a medida que los objetos de la colección se vuelven a indexar.
Carlos P
Si bien esto es más rápido que el enfoque Move, esto genera una serie de eventos Reset / Add. La respuesta más votada (enfoque Move) minimiza esto y con razón plantea Moveeventos, eso también solo para los realmente movidos.
nawfal
19

WPF proporciona clasificación en vivo lista para usar usando la ListCollectionViewclase ...

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

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

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

Gayot Fow
fuente
6
que realmente funcionó: D es una solución mucho mejor que la otra solución "diseñada en exceso" para una tarea tan simple.
MushyPeas
¿A dónde fue tu blog?
phoog
El problema con las cosas "transparentes" es que no puedes ver dónde buscar cuando no funcionan. La documentación de Microsoft tiene un ejemplo 100% transparente, es decir, no se puede ver en absoluto.
Paul McCarthy
15

Me gustó el enfoque del método de extensión de clasificación de burbujas en el blog de "Richie" anterior, pero no necesariamente quiero clasificar comparando todo el objeto. Más a menudo quiero ordenar por una propiedad específica del objeto. Así que lo modifiqué para aceptar un selector de clave de la forma en que lo hace OrderBy para que pueda elegir en qué propiedad ordenar:

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

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

ObservableCollection<Person> people = new ObservableCollection<Person>();
...

people.Sort(p => p.FirstName);
xr280xr
fuente
1
Gracias por publicar esto. Como se señaló en los comentarios del blog de Richie, hay algunas mejoras valiosas en este código; en particular, utilizando el método 'Move' de la fuente. Supongo que esto reemplazaría las líneas Eliminar / Insertar con source.Move (j-1, j);
Carlos P
2
Este algoritmo de clasificación no está optimizado en.wikipedia.org/wiki/Sorting_algorithm
Jaider
@Jaider Sí, está optimizado, pero no para la velocidad bruta general.
jv42
Esto genera una cantidad de eventos Eliminar / Agregar (por cada N, creo). La respuesta más votada minimiza esto y, con razón, genera eventos Move, eso también solo para los realmente movidos. La clave aquí es no hacer una ordenación en el lugar de inmediato, sino ordenarla externamente usando OrderByy luego hacer una comparación para descubrir el cambio real.
nawfal
11

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 evitar tener que usar 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);
    }
}

ahora puede llamarlo como la mayoría de los métodos LINQ:

myObservableCollection.Sort(o => o.MyProperty);
Jonesopolis
fuente
2
Para obtener una galleta de chocolate adicional, puede agregar un parámetro booleano "Ascendente" y if(!Ascending) sorted.Reverse();justo antes de for: D (y no hay necesidad de preocuparse más por la memoria, ese método Reverse no crea ningún objeto nuevo, está en el lugar inverso)
Sharky
Según mi colección de pruebas, Move (0,0) conduce a un evento CollectionChanged. Por lo tanto, sería una mejora del rendimiento comprobar primero si se requiere un movimiento.
sa.he
10

Me gustaría agregar a la respuesta de NeilW . Incorporar un método que se asemeje al orderby. Agregue este método como una extensión:

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

Y usa como:

myCollection = new ObservableCollection<MyObject>();

//Sorts in place, on a specific Func<T,T>
myCollection.Sort(x => x.ID);
DR.
fuente
8

Una variación es donde clasifica la colección en su lugar usando un algoritmo de clasificación de selección . Los elementos se mueven a su lugar utilizando el Movemétodo. Cada movimiento disparará el CollectionChangedevento con NotifyCollectionChangedAction.Move(y también PropertyChangedcon el nombre de la propiedad Item[]).

Este algoritmo tiene algunas propiedades interesantes:

  • El algoritmo se puede implementar como un tipo estable.
  • El número de elementos movidos en la colección (por ejemplo, CollectionChangedeventos activados) es casi siempre menor que otros algoritmos similares como el ordenamiento por inserción y el ordenamiento por burbujas.

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 colocado en su lugar. El algoritmo no es muy eficiente, pero para cualquier cosa que vaya a mostrar en una interfaz de usuario, no debería importar. Sin embargo, en términos de número de operaciones de movimiento, es bastante eficiente.

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

}

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

var collection = new ObservableCollection<String>(...);
collection.Sort();
Martin Liversage
fuente
1
Esta es mi forma de clasificación preferida de todas las descritas aquí, desafortunadamente el método Move no está disponible en Silverlight 5.
Eduardo Brites
1
Recibo el error 'Profiler.Profile.ProfileObject' no se puede usar como parámetro de tipo 'T' en el tipo o método genérico 'ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>)'. No hay conversión de referencia implícita de 'Profiler.Profile.ProfileObject' a 'System.IComparable <Profiler.Profile.ProfileObject>
New Bee
1
@NewBee: Este método de extensión especifica una limitación genérica de Tser capaz de ordenar los elementos de la colección. La ordenación implica el concepto de mayor y menor que y solo usted puede definir cómo ProfileObjectse ordena. Para usar el método de extensión, debe implementar IComparable<ProfileObject>en ProfileObject. Otras alternativas son como se indica especificando un IComparer<ProfileObject>o un Func<ProfileObject, ProfileObject, int>y cambiar el código de clasificación en consecuencia.
Martin Liversage
4

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 de Carlos P en el comentario a esa respuesta. Por favor ver más abajo.

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);
            }
        }
    }
Jonathan Morales Vélez
fuente
2

¿Necesitas mantener tu colección ordenada en todo momento? Al recuperar los pares, ¿necesita que estén siempre ordenados o solo unas pocas veces (tal vez solo para presentar)? ¿Qué tan grande espera que sea su colección? Hay muchos factores que pueden ayudarlo a decidir qué método utilizar.

Si necesita que la colección esté ordenada en todo momento, incluso cuando inserta o elimina elementos y la velocidad de inserción no es un problema, tal vez debería implementar algún tipo de lo SortedObservableCollectionque mencionó @Gerrie Schenck o ver esta implementación .

Si necesita ordenar su colección solo unas pocas veces, use:

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

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

bruno conde
fuente
1
El enlace en esta respuesta es al código con licencia LGPL, por lo que si es Silverlight (no puede vincular dinámicamente) o no es de código abierto, tenga cuidado con ese código.
yzorg
2

Mi respuesta actual ya tiene la mayor cantidad de votos, pero encontré una forma mejor y más moderna de hacer esto.

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
fuente
¿No sería mejor actualizar la respuesta original?
Nathan Hughes
No. Ya ha recibido más votos que cualquier otra respuesta. No voy a suponer que la gente preferiría hacerlo de esta manera. Solo pensé en ofrecer otra forma de hacerlo, especialmente porque había una recompensa por las nuevas respuestas.
NielW
1

Cree una nueva clase SortedObservableCollection, derívela ObservableCollectione impleméntela IComparable<Pair<ushort, string>>.

Gerrie Schenck
fuente
1

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));
Adam Ralph
fuente
1

Qué diablos, también arrojaré una respuesta improvisada rápidamente ... se parece un poco a algunas otras implementaciones aquí, pero la agregaré en cualquier persona:

(apenas probado, ojalá no me esté avergonzando)

Primero establezcamos algunos objetivos (mis suposiciones):

1) Debe ordenar ObservableCollection<T>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<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
    }
}
JerKimball
fuente
1

Ninguna de estas respuestas funcionó en mi caso. Ya sea porque arruina la encuadernación o requiere tanta codificación adicional que es una especie de pesadilla, o la respuesta simplemente está rota. Entonces, aquí hay otra respuesta más simple que pensé. Es mucho menos código y sigue siendo la misma colección observable con un tipo de método this.sort adicional. Avíseme si hay alguna razón por la que no debería hacerlo de esta manera (eficiencia, etc.).

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

... Donde ScoutItem es mi clase pública. Parecía mucho más simple. Beneficio agregado: realmente funciona y no se mete con las encuadernaciones ni devuelve una nueva colección, etc.

arce
fuente
1

Muy bien, dado que tenía problemas para que ObservableSortedList funcionara con XAML, seguí adelante y creé SortingObservableCollection . Hereda de ObservableCollection, por lo que funciona con XAML y lo he 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 de código de muestra:

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

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

Weston
fuente
1
Probablemente no debería usar newen todos esos métodos, si alguien tiene una instancia de tipo más genérico, esos métodos no serán llamados. En su lugar, overridecada método reemplazable y cámbielos según sea necesario o recurra base.Method(...). Usted, por ejemplo, ni siquiera necesita preocuparse .Addporque eso se usa internamente .InsertItem, por lo que si .InsertItemse anula y ajusta, .Addno interferirá con el pedido.
HB
1

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

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

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

Uso:

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

Necesitaba poder ordenar por varias cosas, no solo por 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<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);
    }
}

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));
J H
fuente
0

Aprendí mucho de las otras soluciones, pero encontré un par de problemas. Primero, algunos dependen de IndexOf, que tiende a ser bastante lento para listas grandes. En segundo lugar, mi ObservableCollection tenía entidades EF y el uso de Eliminar parecía corromper algunas de las propiedades de la clave externa. Quizás estoy haciendo algo mal.

Independientemente, se puede usar A Move en su lugar Eliminar / Insertar, pero eso causa algunos problemas con la corrección de rendimiento.

Para solucionar el problema de rendimiento, creo un diccionario con los valores ordenados IndexOf. Para mantener el diccionario actualizado y preservar las propiedades de la entidad, use un intercambio implementado con dos movimientos en lugar de uno como se implementa en otras soluciones.

Un solo movimiento cambia los índices de los elementos entre las ubicaciones, lo que invalidaría el diccionario IndexOf. Agregar un segundo movimiento para implementar un intercambio restaura las ubicaciones.

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++;
        }
}
jlear
fuente
-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));
Rex
fuente
Oh, lo entiendo ... Gayot quería dar la recompensa por la respuesta más negativa jajaja
NielW
Nunca visto otorgar recompensas con sarcasmo :)
nawfal