Wie sortiere ich eine beobachtbare Sammlung?

97

Ich habe eine folgende Klasse:

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

Was ich in eine ObservableCollection aufgenommen habe:

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

F: Wie sortiere ich es nach Schlüssel?

Maciek
quelle
Suchen Sie nach einer Sortierimplementierung innerhalb der Klasse oder ist nur eine Sortierung möglich?
OK,
Ich bin mir nicht sicher, wie ich das verstehen soll. Grundsätzlich möchte ich es nur sortieren lassen, die Sammlung wird nicht sehr groß sein (max. 20 Artikel), also wird (höchstwahrscheinlich) alles funktionieren
Maciek
Siehe dies für eine WPF-Lösung stackoverflow.com/questions/1945461/…
Gayot Fow
Schauen Sie sich die Antworten auf dieser Seite an: Sehr deutlicher Hinweis auf eine fehlerhafte API, wenn mehr als 22 Antworten für einige wichtige und grundlegende Funktionen erforderlich sind.
Gerry
Mögliches Duplikat von Sort ObservableCollection <string> bis C #
Tim Pohlmann

Antworten:

19

Das Sortieren eines beobachtbaren Objekts und das Zurückgeben des gleichen sortierten Objekts kann mithilfe einer Erweiterungsmethode erfolgen. Achten Sie bei größeren Sammlungen auf die Anzahl der geänderten Sammlungsbenachrichtigungen, z

public static void Sort<T>(this ObservableCollection<T> observable) where T : IComparable<T>, IEquatable<T>
    {
        List<T> 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++;
            }
        }
    }

Verwendung: Probe mit einem Beobachter (verwendet eine Personenklasse, um es einfach zu halten)

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

Ausgabe von oben:
entfernt Katy im Alter von 51 Jahren bei Index 0
hinzugefügt Katy im Alter von 51 Jahren bei Index 8
entfernt Mary im Alter von 41 Jahren bei Index 3
hinzugefügt Mary im Alter von 41 Jahren bei Index 7
entfernt Jane im Alter von 20 Jahren bei Index 3
hinzugefügt Jane im Alter von 20 Jahren bei Index 5
entfernt Jim im Alter von 39 Jahren bei Index 3
hinzugefügt Jim im Alter von 39 bei Index 6
Jane im Alter von 20 bei Index 4 entfernt
hinzugefügt Jane im Alter von 20 bei Index 5

Die Person-Klasse implementiert sowohl IComparable als auch IEquatable. Letzteres wird verwendet, um die Änderungen an der Sammlung zu minimieren und die Anzahl der ausgelösten Änderungsbenachrichtigungen zu verringern

  • BEARBEITEN Sortiert dieselbe Sammlung, ohne eine neue Kopie zu erstellen *

Um eine ObservableCollection zurückzugeben, rufen Sie .ToObservableCollection auf * sortedOC * auf, indem Sie z. B. [diese Implementierung] [1] verwenden.

**** orig Antwort - dies erstellt eine neue Sammlung **** Sie können linq verwenden, wie die folgende doSort-Methode zeigt. Ein schneller Code-Ausschnitt: Erzeugt

3: xey 6: fty 7: aaa

Alternativ können Sie eine Erweiterungsmethode für die Sammlung selbst verwenden

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;
    }
}
Andrew
quelle
Fand dies und fand es am hilfreichsten. Ist es LINQ, das die sortierteOC-Variable ausmacht?
Jason94
9
Kein Fan dieser Antwort, da Sie keine sortierte ObservableCollection erhalten.
xr280xr
63
-1 , da es nicht sortieren ist das ObservableCollection , sondern eine neue Kollektion erstellt.
Kos
2
Der aktualisierte Code funktioniert, hat jedoch eine zeitliche Komplexität von O (n ^ 2). Dies kann zu O (n * log (n)) verbessert werden, indem BinarySearchanstelle von verwendet wird IndexOf.
William Morrison
2
Hervorragende Lösung! Für diejenigen, die von ObservableCollection <T> erben, ist es möglich, die geschützte MoveItem () -Methode anstelle der RemoveAt- und Insert-Methoden zu verwenden. Siehe auch: referencesource.microsoft.com/#system/compmod/system/…
Herman Cordes
84

Diese einfache Erweiterung hat bei mir wunderbar funktioniert. Ich musste nur sicherstellen, dass das so MyObjectwar IComparable. Wenn die Sortiermethode für die beobachtbare Sammlung von aufgerufen wird, MyObjectswird die CompareToMethode on MyObjectaufgerufen, die meine logische Sortiermethode aufruft. Es hat zwar nicht alle Schnickschnack der restlichen Antworten, die hier veröffentlicht wurden, aber es ist genau das, was ich brauchte.

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
quelle
7
Dies sollte die Antwort sein
thumbmunkeys
1
Meine Antwort oben wurde aktualisiert, da sie akzeptiert wurde, und es wird eine Leistungsverbesserung gegenüber dieser Antwort angesprochen, die Änderungsbenachrichtigungen für alles in der Sammlung
Andrew
3
Gute Antwort. Gibt es einen Grund, warum Sie return Utils.LogicalStringCompare(a.Title, b.Title);anstelle von verwenden return string.Compare(a.Title, b.Title);? @NeilW
Joe
2
@ Joe, ich musste einen logischen Vergleich anstelle eines Standard-String-Vergleichs durchführen, weshalb ich die Erweiterung zuerst schreiben musste. Logischer String vergleicht Sortierzahlen in Strings richtig, anstatt sie wie Strings zu ordnen (1, 2, 20, 1000 statt 1, 1000, 2, 20 usw.)
NielW
4
Dies ist absolut der richtige Weg. Ich habe eine eigene Antwort hinzugefügt, die es Ihnen ermöglicht, einen keySelector zu übergeben, anstatt IComparable zu verwenden, wie es LINQ normalerweise tut.
Jonesopolis
39

Ich habe einen relevanten Blogeintrag gefunden, der eine bessere Antwort bietet als die hier:

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

AKTUALISIEREN

Die ObservableSortedList , auf die @romkyns in den Kommentaren hinweist, behält automatisch die Sortierreihenfolge bei.

Implementiert eine beobachtbare Sammlung, die ihre Elemente in sortierter Reihenfolge verwaltet. Insbesondere Änderungen an Positionseigenschaften, die zu Auftragsänderungen führen, werden korrekt behandelt.

Beachten Sie jedoch auch die Bemerkung

Kann aufgrund der vergleichenden Komplexität der Schnittstelle und der relativ schlechten Dokumentation fehlerhaft sein (siehe https://stackoverflow.com/a/5883947/33080 ).

Eric J.
quelle
2
In der Tat ist dieser Blog nützlicher. Ich muss jedoch noch eine anständige Antwort auf die Frage finden, ob es eine beobachtbare Sammlung gibt, die ihre Sortierung beibehält, wenn Elemente hinzugefügt und entfernt werden. Ich werde meine eigenen schreiben, denke ich.
Stephen Drew
@Steve können Sie versuchen , diese ein .
Roman Starkov
Vielen Dank für den Link, ich habe mich für die Erweiterungsmethode entschieden, da dies die sauberste Lösung zu sein schien.
Wirkt
bw hat jemand bemerkt, dass der Blog einen Tippfehler im HTML-Dateinamen hat (obversablecollection)? : P
laishiekai
1
@romkyns Die Antwort war, ObservableCollection <T> zu erweitern. Das GridView erkennt es dann ganz gut. Dann verstecke einfach die Methoden, ähnlich wie du. Ich werde eine vollständige Lösung veröffentlichen, wenn ich Zeit habe.
Weston
25

Sie können diese einfache Methode verwenden:

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

Sie können so sortieren:

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

Weitere Details: http://jaider.net/2011-05-04/sort-a-observablecollection/

Jaider
quelle
4
Dies löscht die ObservableCollection dann alle Objekte neu erstellt - so lohnt sich die Feststellung , dass , wenn die Benutzeroberfläche an die Sammlung gebunden ist, würden Sie nicht animiert Änderungen sehen, zum Beispiel , wenn Elemente bewegen
Carlos P
1
Ich bin mir nicht sicher, warum Sie sich bewegende Elemente anzeigen müssen ... z. B. haben Sie normalerweise die ObservableCollectionDropdown-Liste an ItemSource gebunden und sehen die Sammlung überhaupt nicht. Auch dieser Vorgang des Löschens und Auffüllens ist ultraschnell ... der "langsame" kann die Sorte sein, die bereits optimiert ist. Schließlich können Sie diesen Code ändern , um Ihre Bewegungsverfahren zu implementieren, mit der sortedlistund sourceder Rest ist einfach.
Jaider
3
Wenn Sie an ein Dropdown-Menü gebunden sind, würden Sie nicht davon profitieren, wenn sich Objekte bewegen, das stimmt. Wenn Sie jedoch an eine ListBox gebunden sind, bieten Frameworks wie WPF oder Silverlight oder Windows Store Apps nützliches visuelles Feedback, wenn Objekte in der Sammlung neu indiziert werden.
Carlos P
Dies ist zwar schneller als der Move-Ansatz, löst jedoch eine Reihe von Reset / Add-Ereignissen aus. Die Antwort mit der höchsten Stimme (Move-Ansatz) minimiert dies und löst zu Recht MoveEreignisse aus, auch dies nur für die wirklich bewegten.
Nawfal
18

WPF bietet Live-Sortierung mit der ListCollectionViewKlasse ...

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

Sobald diese Initialisierung abgeschlossen ist, gibt es nichts mehr zu tun. Der Vorteil gegenüber einer passiven Sortierung besteht darin, dass die ListCollectionView das gesamte schwere Heben auf eine Weise ausführt, die für den Entwickler transparent ist. Neue Artikel werden automatisch in der richtigen Sortierreihenfolge platziert. Jede Klasse, die von abgeleitet istIComparer von T abgeleitet ist, ist für die benutzerdefinierte Sortiereigenschaft geeignet.

In ListCollectionView finden Sie die Dokumentation und andere Funktionen.

Gayot Fow
quelle
6
das hat tatsächlich funktioniert: D das ist eine viel bessere Lösung als die andere "überarbeitete" Lösung für eine so einfache Aufgabe.
MushyPeas
Wo ist dein Blog geblieben?
Phoog
Das Problem mit "transparenten" Dingen ist, dass Sie nicht sehen können, wo Sie suchen müssen, wenn es nicht funktioniert. Die Dokumentation von Microsoft enthält ein 100% transparentes Beispiel, dh Sie können es überhaupt nicht sehen.
Paul McCarthy
15

Ich mochte den Ansatz der Blasensortierungserweiterungsmethode in "Richies" Blog oben, aber ich möchte nicht unbedingt nur das gesamte Objekt vergleichen. Ich möchte häufiger nach einer bestimmten Eigenschaft des Objekts sortieren. Deshalb habe ich es so geändert, dass es wie KeyBy einen Schlüssel-Selektor akzeptiert, sodass Sie auswählen können, nach welcher Eigenschaft sortiert werden soll:

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

Was Sie genauso aufrufen würden wie OrderBy, außer dass die vorhandene Instanz Ihrer ObservableCollection sortiert wird, anstatt eine neue Sammlung zurückzugeben:

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

people.Sort(p => p.FirstName);
xr280xr
quelle
1
Vielen Dank, dass Sie dies gepostet haben. Wie in den Kommentaren auf Richies Blog erwähnt, gibt es einige sinnvolle Verbesserungen an diesem Code. insbesondere mit der 'Move'-Methode der Quelle. Ich denke, dies würde die Zeilen zum Entfernen / Einfügen durch source.Move (j-1, j) ersetzen.
Carlos P
2
Dieser Sortieralgorithmus ist nicht optimiert. En.wikipedia.org/wiki/Sorting_algorithm
Jaider
@Jaider Ja, es ist optimiert, nur nicht für die Gesamtgeschwindigkeit.
Jv42
Dies löst eine Reihe von Ereignissen zum Entfernen / Hinzufügen aus (für jedes N, von dem ich glaube). Die Antwort mit der höchsten Stimme minimiert dies und löst zu Recht Verschiebungsereignisse aus, auch dies nur für die wirklich bewegten. Der Schlüssel hier ist, nicht sofort eine direkte Sortierung OrderBydurchzuführen , sondern diese extern mit zu sortieren und dann einen Vergleich durchzuführen, um die tatsächliche Änderung herauszufinden.
Nawfal
11

Die Antwort von @ NielW ist der richtige Weg für eine echte Sortierung vor Ort. Ich wollte eine leicht veränderte Lösung hinzufügen, mit der Sie umgehen müssen 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);
    }
}

Jetzt können Sie es wie die meisten LINQ-Methoden aufrufen:

myObservableCollection.Sort(o => o.MyProperty);
Jonesopolis
quelle
2
Für eine zusätzliche Schokolade Cookie können Sie einen boolean Parameter „Aufsteigend“ und fügen Sie if(!Ascending) sorted.Reverse();kurz vor dem for: D (und keine Notwendigkeit, -further- Sorge über das Gedächtnis, das Reverse - Verfahren keine neuen Objekte schafft, ist es an Ort und Stelle umgekehrt)
Sharky
Nach meinen Tests führt collection.Move (0,0) zu einem CollectionChanged-Ereignis. Daher wäre es eine Leistungsverbesserung, zuerst zu prüfen, ob überhaupt ein Zug erforderlich ist.
Sa.he
10

Ich möchte NeilWs Antwort ergänzen . Einfügen einer Methode, die der Reihenfolge ähnelt. Fügen Sie diese Methode als Erweiterung hinzu:

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

Und verwenden Sie wie:

myCollection = new ObservableCollection<MyObject>();

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

In einer Variante sortieren Sie die Sammlung mithilfe eines Auswahlsortieralgorithmus . Elemente werden mithilfe der MoveMethode an ihren Platz verschoben . Jeder Zug löst das CollectionChangedEreignis mit NotifyCollectionChangedAction.Move(und auch PropertyChangedmit dem Eigenschaftsnamen Item[]) aus.

Dieser Algorithmus hat einige nette Eigenschaften:

  • Der Algorithmus kann als stabile Sortierung implementiert werden.
  • Die Anzahl der in der Sammlung verschobenen Elemente (z. B. CollectionChangedausgelöste Ereignisse) ist fast immer geringer als bei anderen ähnlichen Algorithmen wie Einfügesortierung und Blasensortierung.

Der Algorithmus ist recht einfach. Die Sammlung wird iteriert, um das kleinste Element zu finden, das dann an den Anfang der Sammlung verschoben wird. Der Vorgang wird ab dem zweiten Element usw. wiederholt, bis alle Elemente an ihren Platz verschoben wurden. Der Algorithmus ist nicht besonders effizient, aber für alles, was Sie in einer Benutzeroberfläche anzeigen möchten, sollte es keine Rolle spielen. In Bezug auf die Anzahl der Verschiebevorgänge ist es jedoch ziemlich effizient.

Hier ist eine Erweiterungsmethode, die der Einfachheit halber die Implementierung der Elemente erfordert IComparable<T>. Andere Optionen verwenden ein IComparer<T>oder ein 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);
    }
  }

}

Beim Sortieren einer Sammlung müssen Sie lediglich die Erweiterungsmethode aufrufen:

var collection = new ObservableCollection<String>(...);
collection.Sort();
Martin Liversage
quelle
1
Dies ist meine bevorzugte Sortiermethode von allen hier beschriebenen, leider ist die Verschiebungsmethode in Silverlight 5 nicht verfügbar.
Eduardo Brites
1
Ich erhalte den Fehler 'Profiler.Profile.ProfileObject' kann nicht als Typparameter 'T' im generischen Typ oder der generischen Methode 'ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>)' verwendet werden. Es gibt keine implizite Referenzkonvertierung von 'Profiler.Profile.ProfileObject' zu 'System.IComparable <Profiler.Profile.ProfileObject>
New Bee
1
@NewBee: Diese Erweiterungsmethode gibt eine generische Einschränkung an T, um die Elemente in der Auflistung sortieren zu können. Das Sortieren beinhaltet das Konzept von größer und kleiner als und nur Sie können definieren, wie ProfileObjectbestellt wird. So verwenden Sie die Erweiterung Methode , die Sie implementieren müssen IComparable<ProfileObject>auf ProfileObject. Andere Alternativen sind wie angegeben unter Angabe von a IComparer<ProfileObject>oder a Func<ProfileObject, ProfileObject, int>und ändern den Sortiercode entsprechend.
Martin Liversage
4

Um die Erweiterungsmethode für die Antwort xr280xr ein wenig zu verbessern, habe ich einen optionalen bool-Parameter hinzugefügt, um festzustellen, ob die Sortierung absteigend ist oder nicht. Ich habe auch den Vorschlag von Carlos P in den Kommentar zu dieser Antwort aufgenommen. Siehe unten.

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
quelle
2

Müssen Sie Ihre Sammlung jederzeit sortieren? Müssen die Paare beim Abrufen immer sortiert sein oder nur einige Male (möglicherweise nur zum Präsentieren)? Wie groß soll Ihre Sammlung sein? Es gibt viele Faktoren, die Ihnen bei der Entscheidung für die Hexenmethode helfen können.

Wenn Sie möchten, dass die Sammlung jederzeit sortiert wird, auch wenn Sie Elemente einfügen oder löschen und die SortedObservableCollectionEinfügegeschwindigkeit kein Problem darstellt, sollten Sie möglicherweise eine Art von @Gerrie Schenck implementieren oder diese Implementierung überprüfen .

Wenn Sie Ihre Sammlung nur für ein paar Mal sortieren müssen, verwenden Sie:

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

Das Sortieren der Sammlung wird einige Zeit in Anspruch nehmen, ist jedoch möglicherweise die beste Lösung, je nachdem, was Sie damit tun.

bruno conde
quelle
1
Der Link in dieser Antwort bezieht sich auf LGPL-lizenzierten Code. Wenn Sie also Silverlight sind (keine dynamische Verknüpfung herstellen können) oder nicht Open Source sind, gehen Sie vorsichtig mit diesem Code um.
Yzorg
2

Meine aktuelle Antwort hat bereits die meisten Stimmen, aber ich habe einen besseren und moderneren Weg gefunden, dies zu tun.

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
quelle
Wäre es nicht besser, die ursprüngliche Antwort zu aktualisieren?
Nathan Hughes
Nein, es wurde bereits mehr als jede andere Antwort positiv bewertet. Ich gehe nicht davon aus, dass die Leute es lieber so machen würden. Ich dachte nur, ich würde eine andere Möglichkeit anbieten, zumal es eine Fülle neuer Antworten gibt.
NielW
1

Erstellen Sie eine neue Klasse SortedObservableCollection, leiten Sie sie ab ObservableCollectionund implementieren Sie sie IComparable<Pair<ushort, string>>.

Gerrie Schenck
quelle
1

Eine Möglichkeit wäre, es in eine Liste zu konvertieren und dann Sort () aufzurufen, um einen Vergleichsdelegierten bereitzustellen. Etwas wie:-

(ungetestet)

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

Was zum Teufel, ich werde auch eine schnell zusammengepflasterte Antwort einwerfen ... es sieht ein bisschen aus wie einige andere Implementierungen hier, aber ich werde es trotzdem hinzufügen:

(kaum getestet, hoffentlich schäme ich mich nicht)

Lassen Sie uns zuerst einige Ziele angeben (meine Annahmen):

1) Muss ObservableCollection<T>an Ort und Stelle sortiert werden , um Benachrichtigungen usw. aufrechtzuerhalten.

2) Darf nicht schrecklich ineffizient sein (dh etwas nah an Standard „gut“ Sortiereffizienz)

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
quelle
1

Keine dieser Antworten hat in meinem Fall funktioniert. Entweder weil es die Bindung vermasselt oder so viel zusätzliche Codierung erfordert, dass es eine Art Albtraum ist, oder weil die Antwort einfach kaputt ist. Also, hier ist noch eine einfachere Antwort, dachte ich. Es ist viel weniger Code und es bleibt dieselbe beobachtbare Sammlung mit einer zusätzlichen Methode vom Typ this.sort. Lassen Sie mich wissen, ob es einen Grund gibt, warum ich es nicht so machen sollte (Effizienz usw.)?

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

... wo ScoutItem meine öffentliche Klasse ist. Schien einfach viel einfacher. Zusätzlicher Vorteil: Es funktioniert tatsächlich und spielt nicht mit Bindungen oder gibt eine neue Sammlung usw. zurück.

Maplemale
quelle
1

Okay, da ich Probleme hatte, ObservableSortedList mit XAML zum Laufen zu bringen , habe ich SortingObservableCollection erstellt . Es erbt von ObservableCollection, funktioniert also mit XAML und ich habe es auf 98% Codeabdeckung getestet. Ich habe es in meinen eigenen Apps verwendet, aber ich werde nicht versprechen, dass es fehlerfrei ist. Fühlen Sie sich frei, einen Beitrag zu leisten. Hier ist die Verwendung des Beispielcodes:

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

Da es sich um eine PCL handelt, sollte sie mit Windows Store, Windows Phone und .NET 4.5.1 funktionieren.

Weston
quelle
1
Sie sollten wahrscheinlich nicht newalle diese Methoden verwenden. Wenn jemand eine allgemeinere Instanz hat, werden diese Methoden nicht aufgerufen. Stattdessen overridejede überschreibbare Methode und ändern Sie sie nach Bedarf oder Fallback auf base.Method(...). Sie müssen sich zum Beispiel nicht einmal darum kümmern, .Addda dies intern verwendet .InsertItemwird. Wenn .InsertItemes also überschrieben und angepasst wird, .Addwird die Bestellung nicht beeinträchtigt.
HB
1

Folgendes mache ich mit OC-Erweiterungen:

    /// <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
quelle
1

Das hat bei mir funktioniert, ich habe es vor langer Zeit irgendwo gefunden.

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

Verwendung:

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

Ich musste in der Lage sein, nach mehreren Dingen zu sortieren, nicht nur nach einem. Diese Antwort basiert auf einigen anderen Antworten, ermöglicht jedoch eine komplexere Sortierung.

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

Wenn Sie es verwenden, übergeben Sie eine Reihe von OrderBy / ThenBy-Aufrufen. So was:

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

Ich habe viel aus den anderen Lösungen gelernt, aber ich habe ein paar Probleme gefunden. Erstens hängen einige von IndexOf ab, was bei großen Listen eher langsam ist. Zweitens hatte meine ObservableCollection EF-Entitäten und die Verwendung von Entfernen schien einige der Fremdschlüsseleigenschaften zu beschädigen. Vielleicht mache ich etwas falsch.

Unabhängig davon kann stattdessen A Move zum Entfernen / Einfügen verwendet werden. Dies führt jedoch zu einigen Problemen bei der Leistungskorrektur.

Um das Leistungsproblem zu beheben, erstelle ich ein Wörterbuch mit den sortierten IndexOf-Werten. Verwenden Sie einen Swap, der mit zwei Zügen implementiert ist, anstatt eines, wie in anderen Lösungen implementiert, um das Wörterbuch auf dem neuesten Stand zu halten und die Entitätseigenschaften beizubehalten.

Durch eine einzelne Verschiebung werden die Indizes der Elemente zwischen den Positionen verschoben, wodurch das IndexOf-Wörterbuch ungültig wird. Durch Hinzufügen eines zweiten Schrittes zum Implementieren eines Austauschs werden Standorte wiederhergestellt.

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
quelle
-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
quelle
Oh, ich verstehe ... Gayot wollte der am meisten herabgestuften Antwort das Kopfgeld geben lol
NielW
Nie gesehen, wie man im Sarkasmus Kopfgeld vergibt :)
Nawfal