Come ordino una raccolta osservabile?


97

Ho una classe seguente:

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

Che ho inserito in una 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"));

D: Come lo ordino per chiave?


Stai cercando un'implementazione di ordinamento all'interno della classe o andrà bene qualsiasi tipo di ordinamento?
okw

Non sono sicuro di come capirlo. Fondamentalmente voglio solo che sia ordinato, la collezione non sarà molto grande (20 articoli max) quindi qualsiasi cosa andrà bene (molto probabilmente)
Maciek

Vedere questo per una soluzione WPF stackoverflow.com/questions/1945461/...
Gayot Fow

Guarda le risposte in questa pagina: indicazione molto chiara di un'API non funzionante quando sono necessarie oltre 22 risposte per alcune funzionalità critiche e di base.
Gerry

Risposte:


21

Ordinare un osservabile e restituire lo stesso oggetto ordinato può essere eseguito utilizzando un metodo di estensione. Per le raccolte più grandi, prestare attenzione al numero di notifiche di modifica della raccolta.

Ho aggiornato il mio codice per migliorare le prestazioni e per gestire i duplicati (grazie a nawfal per aver evidenziato le scarse prestazioni dell'originale anche se ha funzionato bene sull'esempio di dati originali). L'osservabile è partizionato in una metà ordinata a sinistra e una metà non ordinata a destra, dove ogni volta che l'elemento minimo (come si trova nell'elenco ordinato) viene spostato alla fine della partizione ordinata da quello non ordinato. Peggiore caso O (n). Essenzialmente un ordinamento di selezione (vedi sotto per l'output).

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
            }

utilizzo: campione con un osservatore (utilizzato una classe Person per mantenerlo semplice)

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

Dettagli sull'avanzamento dell'ordinamento che mostrano come viene ruotata la raccolta:

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 classe Person implementa sia IComparable che IEquatable, quest'ultimo viene utilizzato per minimizzare le modifiche alla raccolta in modo da ridurre il numero di notifiche di modifica sollevate

  • MODIFICA Ordina la stessa raccolta senza creare una nuova copia *

Per restituire un ObservableCollection, chiama .ToObservableCollection su * SortOC * usando ad esempio [questa implementazione] [1].

**** risposta orig - questo crea una nuova raccolta **** È possibile utilizzare linq come illustra il metodo doSort di seguito. Un rapido snippet di codice: produce

3: xey 6: fty 7: aaa

In alternativa è possibile utilizzare un metodo di estensione sulla raccolta stessa

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

Ho trovato questo e dillo molto utile. È LINQ che compone la var SortOC?
Jason94

9
Non sono un fan di questa risposta perché non ti dà un ObservableCollection ordinato.
xr280xr

63
-1 poiché non ordina il ObservableCollection , ma crea invece una nuova raccolta.
Kos

2
Il codice aggiornato funzionerà, ma ha una complessità temporale O (n ^ 2). Questo può essere migliorato a O (n * log (n)) usando BinarySearchinvece di IndexOf.
William Morrison

2
Ottima soluzione! Per quelli che ereditano da ObservableCollection <T>, è possibile utilizzare il metodo MoveItem () protetto invece di utilizzare i metodi RemoveAt e Insert. Vedi anche: referencesource.microsoft.com/#system/compmod/system/…
Herman Cordes

84

Questa semplice estensione ha funzionato magnificamente per me. Dovevo solo assicurarmi che lo MyObjectfosse IComparable. Quando il metodo di ordinamento viene chiamato sulla raccolta osservabile di MyObjects, viene chiamato il CompareTometodo su MyObject, che chiama il mio metodo di ordinamento logico. Anche se non ha tutti i campanelli e fischietti del resto delle risposte pubblicate qui, è esattamente ciò di cui avevo bisogno.

static class Extensions
{
    public static void Sort<T>(this ObservableCollection<T> collection) where T : IComparable
    {
        List<T> sorted = collection.OrderBy(x => x).ToList();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

public class MyObject: IComparable
{
    public int CompareTo(object o)
    {
        MyObject a = this;
        MyObject b = (MyObject)o;
        return Utils.LogicalStringCompare(a.Title, b.Title);
    }

    public string Title;

}
  .
  .
  .
myCollection = new ObservableCollection<MyObject>();
//add stuff to collection
myCollection.Sort();

7
questa dovrebbe essere la risposta
thumbmunkeys

1
Ho aggiornato la mia risposta sopra poiché era quella accettata e affronta il miglioramento delle prestazioni rispetto a questa risposta che solleva notifiche di modifica per tutto nella raccolta
Andrew

3
Bella risposta. Qualche motivo per cui usi return Utils.LogicalStringCompare(a.Title, b.Title);invece di return string.Compare(a.Title, b.Title);? @NeilW
Joe

2
@ Joe, avevo bisogno di fare un confronto logico invece di un confronto di stringhe standard, motivo per cui dovevo scrivere l'estensione in primo luogo. La stringa logica confronta ordina i numeri in stringhe correttamente, invece di ordinarli come stringhe (1, 2, 20, 1000 invece di 1, 1000, 2, 20, ecc.)
NielW

4
questa è assolutamente la strada da percorrere. Ho aggiunto una mia risposta estendendola, permettendoti di passare un keySelector invece di usare IComparable, come normalmente fa LINQ.
Jonesopolis

39

Ho trovato un post di blog pertinente che fornisce una risposta migliore di quelle qui:

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

AGGIORNARE

L' ObservableSortedList che @romkyns indica nei commenti mantiene automaticamente l'ordinamento.

Implementa una raccolta osservabile che mantiene i suoi articoli in ordine ordinato. In particolare, le modifiche alle proprietà degli articoli che comportano modifiche dell'ordine vengono gestite correttamente.

Nota comunque anche l'osservazione

Potrebbe essere difettoso a causa della complessità comparativa dell'interfaccia coinvolta e della sua documentazione relativamente scarsa (vedere https://stackoverflow.com/a/5883947/33080 ).


2
In effetti, questo blog è più utile. Tuttavia, devo ancora trovare una risposta decente alla domanda di avere una raccolta osservabile che mantenga il suo ordinamento man mano che gli elementi vengono aggiunti e rimossi. Sto per scrivere il mio credo.
Stephen Drew

@Steve Puoi provare questo .
Roman Starkov

Grazie per il collegamento, sono andato con il metodo di estensione in quanto questa sembrava la soluzione più ordinata. Funziona alla grande: D
pengibot

bw qualcuno ha notato che il blog ha un errore di battitura nel nome del file html (obversablecollection)? : P
laishiekai

1
@romkyns la risposta è stata quella di estendere ObservableCollection <T>. Il GridView lo riconosce bene allora. Quindi nascondi i suoi metodi, proprio come fai tu. Pubblicherò una soluzione completa quando avrò tempo.
Weston

25

Puoi usare questo semplice metodo:

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

Puoi ordinare in questo modo:

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

Maggiori dettagli: http://jaider.net/2011-05-04/sort-a-observablecollection/


4
Ciò cancella la ObservableCollection, quindi aggiunge di nuovo tutti gli oggetti, quindi vale la pena notare che se la tua interfaccia utente è vincolata alla raccolta, non vedresti modifiche animate, ad esempio quando gli elementi si spostano
Carlos P

1
Non sono sicuro del motivo per cui devi visualizzare gli elementi in movimento ... ad esempio, normalmente sei ObservableCollectionassociato a ItemSource dei menu a discesa e non vedi affatto la raccolta. Anche questa operazione di svuotamento e riempimento è ultra veloce ... quella "lenta" può essere del tipo già ottimizzato. infine, puoi modificare questo codice per implementare il tuo metodo di spostamento, avere il sortedliste sourceil resto è facile.
Jaider

3
Se sei vincolato a un menu a discesa, non trarrai beneficio dal vedere gli elementi in movimento, è vero. Se sei vincolato a un ListBox, tuttavia, framework come WPF o Silverlight o app di Windows Store forniranno un utile feedback visivo quando gli oggetti nella raccolta vengono reindicizzati.
Carlos P

Sebbene sia più veloce dell'approccio Move, genera un numero di eventi Reset / Add. La risposta più votata (approccio Move) minimizza questo e giustamente solleva Moveeventi, anche questo solo per quelli veramente commossi.
nawfal

19

WPF fornisce l'ordinamento in tempo reale pronto all'uso utilizzando la ListCollectionViewclasse ...

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 volta completata questa inizializzazione, non c'è più niente da fare. Il vantaggio rispetto a un ordinamento passivo è che ListCollectionView fa tutto il lavoro pesante in un modo trasparente per lo sviluppatore. I nuovi elementi vengono inseriti automaticamente nell'ordinamento corretto. Qualsiasi classe che deriva da IComparerdi T è adatta per la proprietà di ordinamento personalizzata.

Vedere ListCollectionView per la documentazione e altre funzionalità.


6
che effettivamente ha funzionato: D è una soluzione decisamente migliore rispetto all'altra soluzione "ingegnerizzata" per un compito così semplice.
MushyPeas

Dove è andato il tuo blog?
phoog

Il problema con le cose "trasparenti" è che non puoi vedere dove guardare quando non funziona. La documentazione di Microsoft ha un esempio trasparente al 100%, cioè non puoi vederlo affatto.
Paul McCarthy

15

Mi è piaciuto l'approccio del metodo di estensione Bubble Sort sul blog di "Richie" sopra, ma non voglio necessariamente ordinare solo confrontando l'intero oggetto. Più spesso desidero ordinare in base a una proprietà specifica dell'oggetto. Quindi l'ho modificato per accettare un selettore di chiave nel modo in cui OrderBy fa in modo da poter scegliere quale proprietà ordinare:

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

Che chiameresti nello stesso modo in cui chiameresti OrderBy tranne che ordinerà l'istanza esistente della tua ObservableCollection invece di restituire una nuova raccolta:

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

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

1
Grazie per aver postato questo - come sottolineato nei commenti sul blog di Richie, ci sono alcuni miglioramenti utili a questo codice; in particolare utilizzando il metodo 'Move' della sorgente. Immagino che questo sostituirebbe le righe Rimuovi / Inserisci con source.Move (j-1, j);
Carlos P

2
Questo algoritmo di ordinamento non è ottimizzato en.wikipedia.org/wiki/Sorting_algorithm
Jaider

@Jaider Sì, è ottimizzato, non solo per la velocità grezza complessiva.
jv42

Questo solleva un numero di eventi Rimuovi / Aggiungi (per ogni N credo). La risposta più votata minimizza questo e giustamente solleva eventi Move, anche questo solo per quelli veramente spostati. La chiave qui è non fare subito un ordinamento sul posto, ma ordinarlo esternamente usando OrderBye quindi fare un confronto per capire il cambiamento effettivo.
nawfal

11

La risposta di @ NielW è la strada da percorrere, per un vero smistamento sul posto. Volevo aggiungere una soluzione leggermente modificata che ti permetta di evitare di dover usare 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);
    }
}

ora puoi chiamarlo come la maggior parte dei metodi LINQ:

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

2
Per un biscotto al cioccolato extra puoi aggiungere un parametro booleano "Ascending" e un if(!Ascending) sorted.Reverse();appena prima di for: D (e non c'è bisogno di preoccuparsi -ulteriore- della memoria, quel metodo Reverse non crea nuovi oggetti, è al suo posto inverso)
Sharky

Secondo i miei test collection.Move (0,0) porta a un evento CollectionChanged. Pertanto sarebbe un miglioramento delle prestazioni controllare prima se è necessaria una mossa.
sa il

10

Vorrei aggiungere alla risposta di NeilW . Incorporare un metodo che assomigli a orderby. Aggiungi questo metodo come estensione:

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

E usa come:

myCollection = new ObservableCollection<MyObject>();

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

8

Una variazione è dove si ordina la raccolta in posizione utilizzando un algoritmo di ordinamento della selezione . Gli elementi vengono spostati in posizione utilizzando il Movemetodo. Ogni mossa attiverà l' CollectionChangedevento con NotifyCollectionChangedAction.Move(e anchePropertyChanged con il nome della proprietà Item[]).

Questo algoritmo ha alcune proprietà interessanti:

  • L'algoritmo può essere implementato come un ordinamento stabile.
  • Il numero di elementi spostati nella raccolta (ad esempio CollectionChangedeventi attivati) è quasi sempre inferiore rispetto ad altri algoritmi simili come l'ordinamento per inserzione e l'ordinamento a bolle.

L'algoritmo è abbastanza semplice. La raccolta viene iterata per trovare l'elemento più piccolo che viene quindi spostato all'inizio della raccolta. Il processo viene ripetuto a partire dal secondo elemento e così via fino a quando tutti gli elementi non sono stati spostati in posizione. L'algoritmo non è molto efficiente ma per tutto ciò che verrà visualizzato in un'interfaccia utente non dovrebbe importare. Tuttavia, in termini di numero di operazioni di spostamento, è abbastanza efficiente.

Ecco un metodo di estensione che per semplicità richiede l'implementazione degli elementi IComparable<T>. Altre opzioni utilizzano un fileIComparer<T> o un file 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);
    }
  }

}

Ordinare una raccolta è semplicemente una questione di invocare il metodo di estensione:

var collection = new ObservableCollection<String>(...);
collection.Sort();

1
Questo è il mio metodo di ordinamento preferito tra tutti quelli descritti qui, sfortunatamente il metodo Move non è disponibile in Silverlight 5.
Eduardo Brites

1
Sto ricevendo l'errore "Profiler.Profile.ProfileObject" non può essere utilizzato come parametro di tipo "T" nel tipo o metodo generico "ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>)". Non vi è alcuna conversione di riferimento implicita da "Profiler.Profile.ProfileObject" a "System.IComparable <Profiler.Profile.ProfileObject>
New Bee

1
@NewBee: Questo metodo di estensione specifica un vincolo generico a Tpoter ordinare gli elementi della collezione. L'ordinamento implica il concetto di maggiore e minore di e solo tu puoi definire come ProfileObjectviene ordinato. Per utilizzare il metodo di estensione è necessario implementare IComparable<ProfileObject>su ProfileObject. Altre alternative sono come indicato specificando an IComparer<ProfileObject>o a Func<ProfileObject, ProfileObject, int>e modificare il codice di ordinamento di conseguenza.
Martin Liversage

4

Per migliorare un po 'il metodo di estensione su xr280xr answer ho aggiunto un parametro bool opzionale per determinare se l'ordinamento è discendente o meno. Ho anche incluso il suggerimento di Carlos P nel commento a quella risposta. Vedi sotto.

public static void Sort<TSource, TKey>(this ObservableCollection<TSource> source, Func<TSource, TKey> keySelector, bool desc = false)
    {
        if (source == null) return;

        Comparer<TKey> comparer = Comparer<TKey>.Default;

        for (int i = source.Count - 1; i >= 0; i--)
        {
            for (int j = 1; j <= i; j++)
            {
                TSource o1 = source[j - 1];
                TSource o2 = source[j];
                int comparison = comparer.Compare(keySelector(o1), keySelector(o2));
                if (desc && comparison < 0)
                    source.Move(j, j - 1);
                else if (!desc && comparison > 0)
                    source.Move(j - 1, j);
            }
        }
    }

2

Hai bisogno di mantenere sempre ordinata la tua collezione? Quando recuperi le coppie, hai bisogno che siano sempre ordinate, o è solo per poche volte (forse solo per la presentazione)? Quanto grande ti aspetti che sia la tua collezione? Ci sono molti fattori che possono aiutarti a decidere il metodo da usare.

Se hai bisogno che la raccolta sia ordinata in ogni momento, anche quando inserisci o elimini elementi e la velocità di inserimento non è un problema, forse dovresti implementare un tipo SortedObservableCollectioncome @Gerrie Schenck menzionato o controllare questa implementazione .

Se hai bisogno che la tua collezione sia ordinata solo per poche volte usa:

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

Questo richiederà del tempo per ordinare la raccolta, ma anche così, potrebbe essere la soluzione migliore a seconda di cosa ci fai.


1
Il collegamento in questa risposta è al codice con licenza LGPL, quindi se sei Silverlight (non puoi collegare dinamicamente) o non sei open source, fai attenzione a quel codice.
yzorg

2

La mia risposta attuale ha già il maggior numero di voti, ma ho trovato un modo migliore e più moderno per farlo.

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

non sarebbe meglio aggiornare la risposta originale?
Nathan Hughes

No. È già stato votato più di ogni altra risposta. Non presumo che le persone preferiscano farlo in questo modo. Ho solo pensato di offrire un altro modo per farlo, soprattutto perché c'era una taglia sulle nuove risposte.
NielW

1

Crea una nuova classe SortedObservableCollection, derivala ObservableCollectione implementala IComparable<Pair<ushort, string>>.


1

Un modo sarebbe convertirlo in un List e quindi chiamare Sort (), fornendo un delegato di confronto. Qualcosa di simile a:-

(non testato)

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


1

Che diamine, inserirò anche una risposta rapidamente messa insieme ... sembra un po 'come alcune altre implementazioni qui, ma la aggiungerò qualsiasi chi:

(appena testato, spero di non imbarazzarmi)

Stabiliamo prima alcuni obiettivi (le mie ipotesi):

1) Deve ordinare ObservableCollection<T>sul posto, per mantenere le notifiche, ecc.

2) Non deve essere orribilmente inefficiente (ovvero qualcosa di simile alla "buona" efficienza di smistamento standard)

public static class Ext
{
    public static void Sort<T>(this ObservableCollection<T> src)
        where T : IComparable<T>
    {
        // Some preliminary safety checks
        if(src == null) throw new ArgumentNullException("src");
        if(!src.Any()) return;

        // N for the select,
        // + ~ N log N, assuming "smart" sort implementation on the OrderBy
        // Total: N log N + N (est)
        var indexedPairs = src
            .Select((item,i) => Tuple.Create(i, item))
            .OrderBy(tup => tup.Item2);
        // N for another select
        var postIndexedPairs = indexedPairs
            .Select((item,i) => Tuple.Create(i, item.Item1, item.Item2));
        // N for a loop over every element
        var pairEnum = postIndexedPairs.GetEnumerator();
        pairEnum.MoveNext();
        for(int idx = 0; idx < src.Count; idx++, pairEnum.MoveNext())
        {
            src.RemoveAt(pairEnum.Current.Item1);
            src.Insert(idx, pairEnum.Current.Item3);            
        }
        // (very roughly) Estimated Complexity: 
        // N log N + N + N + N
        // == N log N + 3N
    }
}

1

Nessuna di queste risposte ha funzionato nel mio caso. O perché rovina il binding, o richiede così tanta codifica aggiuntiva che è una specie di incubo, o la risposta è semplicemente rotta. Quindi, ecco un'altra risposta più semplice che ho pensato. È molto meno codice e rimane la stessa raccolta osservabile con un metodo di tipo this.sort aggiuntivo. Fammi sapere se c'è qualche motivo per cui non dovrei farlo in questo modo (efficienza, ecc.)?

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

... Dove ScoutItem è la mia classe pubblica. Sembrava molto più semplice. Vantaggio aggiunto: funziona effettivamente e non crea problemi con le associazioni o restituisce una nuova raccolta, ecc.


1

Bene, poiché avevo problemi a far funzionare ObservableSortedList con XAML, sono andato avanti e ho creato SortingObservableCollection . Eredita da ObservableCollection, quindi funziona con XAML e l'ho testato con una copertura del codice del 98%. L'ho usato nelle mie app, ma non prometto che sia privo di bug. Sentiti libero di contribuire. Ecco un esempio di utilizzo del codice:

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

È un PCL, quindi dovrebbe funzionare con Windows Store, Windows Phone e .NET 4.5.1.


1
Probabilmente non dovresti usare newsu tutti questi metodi, se qualcuno ha un'istanza tipizzata più genericamente quei metodi non verranno chiamati. Invece overrideogni metodo sovrascrivibile e modificarli secondo necessità o fallback base.Method(...). Tu, ad esempio, non devi nemmeno preoccuparti .Addperché quello utilizza internamente .InsertItem, quindi se .InsertItemviene sovrascritto e regolato, .Addnon interferirà con l'ordinamento.
HB

1

Questo è quello che faccio con le estensioni OC:

    /// <summary>
    /// Synches the collection items to the target collection items.
    /// This does not observe sort order.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source">The items.</param>
    /// <param name="updatedCollection">The updated collection.</param>
    public static void SynchCollection<T>(this IList<T> source, IEnumerable<T> updatedCollection)
    {
        // Evaluate
        if (updatedCollection == null) return;

        // Make a list
        var collectionArray = updatedCollection.ToArray();

        // Remove items from FilteredViewItems not in list
        source.RemoveRange(source.Except(collectionArray));

        // Add items not in FilteredViewItems that are in list
        source.AddRange(collectionArray.Except(source));
    }

    /// <summary>
    /// Synches the collection items to the target collection items.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source">The source.</param>
    /// <param name="updatedCollection">The updated collection.</param>
    /// <param name="canSort">if set to <c>true</c> [can sort].</param>
    public static void SynchCollection<T>(this ObservableCollection<T> source,
        IList<T> updatedCollection, bool canSort = false)
    {
        // Synch collection
        SynchCollection(source, updatedCollection.AsEnumerable());

        // Sort collection
        if (!canSort) return;

        // Update indexes as needed
        for (var i = 0; i < updatedCollection.Count; i++)
        {
            // Index of new location
            var index = source.IndexOf(updatedCollection[i]);
            if (index == i) continue;

            // Move item to new index if it has changed.
            source.Move(index, i);
        }
    }

1

Questo ha funzionato per me, l'ho trovato molto tempo fa da qualche 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));
            }
        }
    }

Utilizzo:

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

0

Avevo bisogno di essere in grado di ordinare più cose non solo una. Questa risposta si basa su alcune delle altre risposte ma consente un ordinamento più complesso.

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

Quando lo usi, passa una serie di chiamate OrderBy / ThenBy. Come questo:

Children.Sort(col => col.OrderByDescending(xx => xx.ItemType == "drive")
                    .ThenByDescending(xx => xx.ItemType == "folder")
                    .ThenBy(xx => xx.Path));

0

Ho imparato molto dalle altre soluzioni, ma ho riscontrato un paio di problemi. Innanzitutto, alcuni dipendono da IndexOf che tende ad essere piuttosto lento per elenchi di grandi dimensioni. In secondo luogo, la mia ObservableCollection aveva entità EF e l'utilizzo di Remove sembrava danneggiare alcune delle proprietà della chiave esterna. Forse sto facendo qualcosa di sbagliato.

Indipendentemente da ciò, è possibile utilizzare A Move al posto di Rimuovi / Inserisci, ma ciò causa alcuni problemi con la correzione delle prestazioni.

Per risolvere il problema delle prestazioni, creo un dizionario con i valori ordinati IndexOf. Per mantenere il dizionario aggiornato e per preservare le proprietà dell'entità, utilizzare uno scambio implementato con due mosse invece di una come implementato in altre soluzioni.

Una singola mossa sposta gli indici degli elementi tra le posizioni, il che invaliderebbe il dizionario IndexOf. L'aggiunta di una seconda mossa per implementare uno scambio ripristina le posizioni.

public static void Sort<TSource, TKey>(this ObservableCollection<TSource> collection, Func<TSource, TKey> keySelector)
{
    List<TSource> sorted = collection.OrderBy(keySelector).ToList();
    Dictionary<TSource, int> indexOf = new Dictionary<TSource, int>();

    for (int i = 0; i < sorted.Count; i++)
        indexOf[sorted[i]] = i;

    int idx = 0;
    while (idx < sorted.Count)
        if (!collection[idx].Equals(sorted[idx])) {
            int newIdx = indexOf[collection[idx]]; // where should current item go?
            collection.Move(newIdx, idx); // move whatever's there to current location
            collection.Move(idx + 1, newIdx); // move current item to proper location
        }
        else {
            idx++;
        }
}

-3
var collection = new ObservableCollection<int>();

collection.Add(7);
collection.Add(4);
collection.Add(12);
collection.Add(1);
collection.Add(20);

// ascending
collection = new ObservableCollection<int>(collection.OrderBy(a => a));

// descending
collection = new ObservableCollection<int>(collection.OrderByDescending(a => a));

Oh ho capito ... Gayot voleva dare la taglia alla risposta più sottovalutata lol
NielW

Mai visto ricompensare con sarcasmo :)
nawfal
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.