Jak posortować obserwowalną kolekcję?


97

Mam następującą klasę:

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

Które umieściłem w 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: Jak to posortować według klucza?


Czy szukasz implementacji sortowania w klasie, czy może wystarczy dowolny typ sortowania?
okw

Nie wiem, jak to rozumieć. Po prostu chcę to posortować, kolekcja nie będzie zbyt duża (maksymalnie 20 pozycji), więc wszystko się uda (najprawdopodobniej)
Maciek

Zobacz to, aby zapoznać się z rozwiązaniem WPF stackoverflow.com/questions/1945461/…
Gayot Fow

Spójrz na odpowiedzi na tej stronie: bardzo wyraźne wskazanie zepsutego API, gdy wymaga ponad 22 odpowiedzi dla niektórych krytycznych i podstawowych funkcji.
Gerry

Odpowiedzi:


21

Sortowanie obserwowalnego i zwracanie tego samego posortowanego obiektu można wykonać za pomocą metody rozszerzenia. W przypadku większych kolekcji uważaj na liczbę powiadomień o zmianach kolekcji.

Zaktualizowałem kod, aby poprawić wydajność i poradzić sobie z duplikatami (dzięki nawfalowi za podkreślenie słabej wydajności oryginału, mimo że działał dobrze na przykładzie oryginalnych danych). To, co obserwowalne, jest podzielone na sortowaną po lewej stronie i prawą nieposortowaną połowę, gdzie za każdym razem minimalna pozycja (taka, jaką można znaleźć na posortowanej liście) jest przesuwana na koniec posortowanej partycji z nieposortowanej. Najgorszy przypadek O (n). Zasadniczo sortowanie przez wybór (patrz poniżej dla wyników).

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
            }

użycie: próbka z obserwatorem (użyto klasy Person, aby było to proste)

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

Szczegóły postępu sortowania pokazujące, w jaki sposób kolekcja jest przestawiana:

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,

Klasa Person implementuje zarówno IComparable, jak i IEquatable, przy czym ta ostatnia służy do minimalizowania zmian w kolekcji w celu zmniejszenia liczby zgłaszanych powiadomień o zmianach

  • EDYTUJ Sortuje tę samą kolekcję bez tworzenia nowej kopii *

Aby zwrócić ObservableCollection, wywołaj .ToObservableCollection na * sortOC * używając np. [Ta implementacja] [1].

**** orig odpowiedź - tworzy to nową kolekcję **** Możesz użyć linq, jak ilustruje poniższa metoda doSort. Krótki fragment kodu: produkuje

3: xey 6: fty 7: aaa

Alternatywnie możesz użyć metody rozszerzenia w samej kolekcji

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

Znalazłem to i znalazłem najbardziej pomocne. Czy to LINQ tworzy sortOC var?
Jason94

9
Nie jestem fanem tej odpowiedzi, ponieważ nie daje posortowanej kolekcji ObservableCollection.
xr280xr

63
-1, ponieważ nie sortować , lecz tworzy nową kolekcję. ObservableCollection
Kos

2
Zaktualizowany kod będzie działał, ale ma złożoność czasową O (n ^ 2). Można to poprawić do O (n * log (n)), używając BinarySearchzamiast IndexOf.
William Morrison

2
Doskonałe rozwiązanie! W przypadku tych dziedziczących po ObservableCollection <T> można użyć chronionej metody MoveItem () zamiast metod RemoveAt i Insert. Zobacz też: referenceource.microsoft.com/#system/compmod/system/…
Herman Cordes

84

To proste rozszerzenie działało pięknie dla mnie. Po prostu musiałem się upewnić, że MyObjectbył IComparable. Gdy metoda sortowania jest wywoływana w obserwowalnej kolekcji MyObjects, wywoływana jest CompareTometoda on MyObject, która wywołuje moją metodę Logical Sort. Chociaż nie ma wszystkich dzwonków i gwizdów z pozostałych odpowiedzi zamieszczonych tutaj, jest to dokładnie to, czego potrzebowałem.

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
to powinna być odpowiedź
thumbmunkeys

1
Zaktualizowałem moją odpowiedź powyżej, ponieważ była ona zaakceptowana i dotyczy poprawy wydajności w stosunku do tej odpowiedzi, która podnosi powiadomienia o zmianach dla wszystkiego w kolekcji
Andrew

3
Świetna odpowiedź. Jakiś powód, dla którego używasz return Utils.LogicalStringCompare(a.Title, b.Title);zamiast return string.Compare(a.Title, b.Title);? @NeilW
Joe

2
@Joe, zamiast standardowego porównania ciągów musiałem przeprowadzić porównanie logiczne, dlatego w pierwszej kolejności musiałem napisać rozszerzenie. Ciąg logiczny porównuje poprawnie liczby w łańcuchach, zamiast porządkować je jak łańcuchy (1, 2, 20, 1000 zamiast 1, 1000, 2, 20 itd.)
NielW

4
to jest absolutna droga. Dodałem własną odpowiedź, rozszerzając to, umożliwiając przekazanie keySelector zamiast używania IComparable, jak zwykle robi to LINQ.
Jonesopolis

39

Znalazłem odpowiedni wpis na blogu, który zawiera lepszą odpowiedź niż te tutaj:

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

AKTUALIZACJA

ObservableSortedList że @romkyns podkreśla w komentarzach automatycznie utrzymuje porządek.

Implementuje obserwowalną kolekcję, która utrzymuje swoje elementy w posortowanej kolejności. W szczególności zmiany właściwości pozycji, które powodują zmiany kolejności, są obsługiwane poprawnie.

Zwróć jednak uwagę na uwagę

Może być wadliwy ze względu na porównawczą złożoność interfejsu i jego stosunkowo słabą dokumentację (patrz https://stackoverflow.com/a/5883947/33080 ).


2
Rzeczywiście, ten blog jest bardziej przydatny. Jednak nie znalazłem jeszcze przyzwoitej odpowiedzi na pytanie, czy istnieje obserwowalna kolekcja, która zachowuje sortowanie w miarę dodawania i usuwania elementów. Myślę, że napiszę własne.
Stephen Drew

@Steve Możesz spróbować tego .
Roman Starkov

Dzięki za link, wybrałem metodę rozszerzenia, ponieważ wydawało się to najczystszym rozwiązaniem. Działa urok: D
pengibot

bw czy ktoś zauważył, że blog zawiera literówkę w nazwie pliku html (obversablecollection)? : P
laishiekai

1
@romkyns odpowiedzią było rozszerzenie ObservableCollection <T>. GridView rozpoznaje to dobrze. Następnie po prostu ukryj jego metody, podobnie jak robisz. Opublikuję pełne rozwiązanie, gdy będę miał czas.
Weston,

25

Możesz użyć tej prostej metody:

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

Możesz sortować w ten sposób:

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

Więcej szczegółów: http://jaider.net/2011-05-04/sort-a-observablecollection/


4
Spowoduje to wyczyszczenie ObservableCollection, a następnie ponowne dodanie wszystkich obiektów - warto więc zauważyć, że jeśli twój interfejs użytkownika jest powiązany z kolekcją, nie zobaczysz animowanych zmian, np. Gdy elementy się poruszają
Carlos P

1
Nie jestem pewien, dlaczego musisz wyświetlać poruszające się przedmioty ... np. Normalnie jesteś ObservableCollectionpowiązany z ItemSource z list rozwijanych i w ogóle nie widzisz kolekcji. Również ta operacja usuwania i uzupełniania jest ultraszybka… „wolna” może być już zoptymalizowaną operacją. na koniec możesz zmodyfikować ten kod, aby zaimplementować metodę przenoszenia, posiadanie sortedlisti sourcereszta jest łatwa.
Jaider

3
Jeśli jesteś przywiązany do listy rozwijanej, nie skorzystasz z widoku poruszających się przedmiotów, to prawda. Jeśli jednak jesteś powiązany z ListBox, struktury, takie jak WPF, Silverlight lub aplikacje ze Sklepu Windows, zapewnią przydatne wizualne opinie, gdy obiekty w kolekcji zostaną ponownie indeksowane.
Carlos P

Chociaż jest to szybsze niż podejście Move, powoduje to zwiększenie liczby zdarzeń Reset / Add. Najwyższa głosowana odpowiedź (podejście Move) minimalizuje to i słusznie podnosi Movezdarzenia, także tylko dla naprawdę poruszonych.
nawfal

19

WPF zapewnia sortowanie na żywo natychmiast po wyjęciu z pudełka przy użyciu ListCollectionViewklasy ...

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

Po zakończeniu inicjalizacji nie ma już nic więcej do zrobienia. Zaletą w porównaniu z sortowaniem pasywnym jest to, że ListCollectionView wykonuje całe ciężkie podnoszenie w sposób przezroczysty dla dewelopera. Nowe elementy są automatycznie umieszczane we właściwej kolejności sortowania. Każda klasa, która pochodzi od IComparerT, jest odpowiednia dla niestandardowej właściwości sortowania.

Zobacz ListCollectionView, aby uzyskać dokumentację i inne funkcje.


6
to faktycznie zadziałało: D to o wiele lepsze rozwiązanie niż inne „przeprojektowane” rozwiązanie dla tak prostego zadania.
MushyPeas

Gdzie poszedł Twój blog?
phoog

Problem z „przezroczystymi” rzeczami polega na tym, że nie możesz zobaczyć, gdzie szukać, kiedy to nie działa. Dokumentacja Microsoftu ma w 100% przejrzysty przykład, tj. W ogóle go nie widać.
Paul McCarthy

15

Podobało mi się podejście metody rozszerzania sortowania bąbelkowego na blogu „Richiego” powyżej, ale niekoniecznie chcę sortować, porównując cały obiekt. Częściej chcę sortować według określonej właściwości obiektu. Więc zmodyfikowałem go, aby akceptował selektor kluczy w sposób, w jaki robi to OrderBy, dzięki czemu możesz wybrać właściwość do sortowania:

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

Który można wywołać w ten sam sposób, w jaki wywołałbyś OrderBy, z wyjątkiem tego, że posortuje istniejące wystąpienie Twojej ObservableCollection zamiast zwracać nową kolekcję:

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

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

1
Dziękuję za przesłanie tego - jak wskazano w komentarzach na blogu Richiego, istnieje kilka wartościowych ulepszeń tego kodu; w szczególności przy użyciu metody „Move” źródła. Wydaje mi się, że to zastąpiłoby linie Usuń / Wstaw z source.Move (j-1, j);
Carlos P,

2
Ten algorytm sortowania nie jest zoptymalizowany en.wikipedia.org/wiki/Sorting_algorithm
Jaider

@Jaider Tak, jest zoptymalizowany, ale nie pod kątem ogólnej szybkości surowej.
jv42

Zwiększa to liczbę zdarzeń Usuń / Dodaj (za każde N, jak sądzę). Najwyższa głosowana odpowiedź minimalizuje to i słusznie podnosi zdarzenia Move, również tylko dla naprawdę poruszonych. Kluczem jest tutaj, aby nie wykonywać sortowania na miejscu od razu, zamiast tego posortować je zewnętrznie, OrderBya następnie przeprowadzić porównanie, aby ustalić rzeczywistą zmianę.
nawfal

11

Odpowiedzią @ NielW jest droga do prawdziwego sortowania na miejscu. Chciałem dodać nieco zmienione rozwiązanie, które pozwala ominąć konieczność użycia 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);
    }
}

teraz możesz to nazwać jak większość każdej metody LINQ:

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

2
Aby uzyskać dodatkowe czekoladowe ciasteczko, możesz dodać parametr boolowski „Ascending” i if(!Ascending) sorted.Reverse();tuż przed for: D (i nie ma potrzeby - dalej - martwić się o pamięć, ta metoda Reverse nie tworzy żadnych nowych obiektów, jest odwrócona w miejscu)
Sharky

Według moich testów collection.Move (0,0) prowadzi do zdarzenia CollectionChanged. Dlatego też poprawą wydajności byłoby najpierw sprawdzić, czy ruch jest w ogóle wymagany.
sa.he

10

Chciałbym dodać do odpowiedzi NeilW . Włączenie metody, która przypomina orderby. Dodaj tę metodę jako rozszerzenie:

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

I używaj jak:

myCollection = new ObservableCollection<MyObject>();

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

8

Odmiana polega na sortowaniu kolekcji w miejscu przy użyciu algorytmu sortowania przez wybór . Elementy są przenoszone na miejsce za pomocą tej Movemetody. Każdy ruch spowoduje uruchomienie CollectionChangedzdarzenia z NotifyCollectionChangedAction.Move(a także PropertyChangedz nazwą właściwości Item[]).

Ten algorytm ma kilka fajnych właściwości:

  • Algorytm można zaimplementować jako stabilny rodzaj.
  • Liczba elementów przenoszonych w kolekcji (np. CollectionChangedWywołanych zdarzeń) jest prawie zawsze mniejsza niż w przypadku innych podobnych algorytmów, takich jak sortowanie przez wstawianie i sortowanie bąbelkowe.

Algorytm jest dość prosty. Kolekcja jest powtarzana w celu znalezienia najmniejszego elementu, który jest następnie przenoszony na początek kolekcji. Proces jest powtarzany, zaczynając od drugiego elementu i tak dalej, aż wszystkie elementy zostaną przesunięte na miejsce. Algorytm nie jest zbyt wydajny, ale w przypadku wszystkiego, co zamierzasz wyświetlić w interfejsie użytkownika, nie powinno to mieć znaczenia. Jednak pod względem liczby operacji przenoszenia jest dość wydajny.

Oto metoda rozszerzenia, która dla uproszczenia wymaga implementacji elementów IComparable<T>. Inne opcje używają pliku IComparer<T>lub a 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);
    }
  }

}

Sortowanie kolekcji jest po prostu kwestią wywołania metody rozszerzającej:

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

1
To jest mój ulubiony sposób sortowania spośród wszystkich opisanych tutaj, niestety metoda Move nie jest dostępna w Silverlight 5.
Eduardo Brites

1
Pojawia się błąd „Profiler.Profile.ProfileObject” nie może być używany jako parametr typu „T” w typie ogólnym lub metodzie „ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>)”. Nie ma niejawnej konwersji odniesienia z „Profiler.Profile.ProfileObject” na „System.IComparable <Profiler.Profile.ProfileObject>
New Bee

1
@NewBee: Ta metoda rozszerzenie określa ogólne ograniczenia w Tcelu móc sortować elementy w kolekcji. Sortowanie obejmuje koncepcje większe i mniejsze niż i tylko Ty możesz określić, jak ProfileObjectjest uporządkowane. Aby użyć metody rozszerzenia, musisz zaimplementować IComparable<ProfileObject>na ProfileObject. Inne alternatywy są, jak wspomniano, z podaniem znaku IComparer<ProfileObject>lub a Func<ProfileObject, ProfileObject, int>i odpowiednio zmień kod sortowania.
Martin Liversage

4

Aby nieco ulepszyć metodę rozszerzania odpowiedzi xr280xr, dodałem opcjonalny parametr bool, aby określić, czy sortowanie jest malejące, czy nie. W komentarzu do tej odpowiedzi zawarłem również sugestię Carlosa P. Patrz poniżej.

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

Czy musisz zawsze posortować swoją kolekcję? Czy podczas pobierania par trzeba je zawsze sortować, czy tylko kilka razy (może tylko do prezentacji)? Jak duża będzie Twoja kolekcja? Istnieje wiele czynników, które mogą pomóc w podjęciu decyzji o zastosowaniu metody czarownicy.

Jeśli chcesz, aby kolekcja była sortowana przez cały czas, nawet gdy SortedObservableCollectionwstawiasz lub usuwasz elementy, a prędkość wstawiania nie stanowi problemu, może powinieneś zaimplementować coś takiego jak wspomniany @Gerrie Schenck lub sprawdzić tę implementację .

Jeśli potrzebujesz posortować swoją kolekcję tylko kilka razy, użyj:

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

Posortowanie kolekcji zajmie trochę czasu, ale mimo wszystko może to być najlepsze rozwiązanie w zależności od tego, co z nią zrobisz.


1
Łącze w tej odpowiedzi prowadzi do kodu licencjonowanego przez LGPL, więc jeśli jesteś Silverlight (nie możesz łączyć dynamicznie) lub nie jesteś open source, uważaj na ten kod.
yzorg

2

Moja obecna odpowiedź ma już najwięcej głosów, ale znalazłem lepszy i nowocześniejszy sposób na zrobienie tego.

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

czy nie byłoby lepiej zaktualizować pierwotną odpowiedź?
Nathan Hughes,

Nie. To już zostało przegłosowane bardziej niż jakakolwiek inna odpowiedź. Nie zamierzam zakładać, że ludzie woleliby to robić w ten sposób. Pomyślałem, że zaproponuję inny sposób zrobienia tego, zwłaszcza, że ​​nowe odpowiedzi są nagradzane.
NielW,

1

Utwórz nową klasę SortedObservableCollection, wyprowadź ją z ObservableCollectioni zaimplementuj IComparable<Pair<ushort, string>>.


1

Jednym ze sposobów byłoby przekonwertowanie go na Listę, a następnie wywołanie Sort (), zapewniając delegata porównania. Coś jak:-

(niesprawdzone)

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


1

Co do cholery, dorzucę też szybko zebraną odpowiedź ... wygląda trochę jak kilka innych implementacji tutaj, ale dodam to gdziekolwiek:

(ledwo przetestowane, mam nadzieję, że się nie zawstydzam)

Najpierw przedstawmy kilka celów (moje założenia):

1) Należy sortować ObservableCollection<T> na miejscu, aby zachować powiadomienia itp.

2) Nie może być strasznie nieefektywne (tj. Coś zbliżonego do standardowej „dobrej” wydajności sortowania)

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

Żadna z tych odpowiedzi nie zadziałała w moim przypadku. Albo dlatego, że psuje wiązanie, albo wymaga tak dużo dodatkowego kodowania, że ​​jest to koszmar, albo odpowiedź jest po prostu zepsuta. Oto kolejna prostsza odpowiedź, o której pomyślałem. Jest to dużo mniej kodu i pozostaje tą samą obserwowalną kolekcją z dodatkowym typem metody this.sort. Daj mi znać, jeśli jest jakiś powód, dla którego nie powinienem tego robić w ten sposób (efektywność itp.)?

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

... Gdzie ScoutItem jest moją klasą publiczną. Po prostu wydawało się o wiele prostsze. Dodatkowa korzyść: faktycznie działa i nie psuje wiązań ani nie zwraca nowej kolekcji itp.


1

W porządku, ponieważ miałem problemy z uzyskaniem ObservableSortedList do pracy z XAML, poszedłem dalej i utworzyłem SortingObservableCollection . Dziedziczy po ObservableCollection, więc działa z XAML i przetestowałem go na poziomie 98% pokrycia kodu. Używałem go we własnych aplikacjach, ale nie obiecuję, że jest wolny od błędów. Zapraszam do udziału. Oto przykładowe użycie kodu:

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

Jest to PCL, więc powinien działać ze Sklepem Windows, Windows Phone i .NET 4.5.1.


1
Prawdopodobnie nie powinieneś używać newna wszystkich tych metodach, jeśli ktoś ma instancję o bardziej typowym typie, te metody nie zostaną wywołane. Zamiast tego overridekażdą możliwą do zastąpienia metodę i zmień ją w razie potrzeby lub skorzystaj z opcji zastępczej base.Method(...). Na przykład nie musisz się nawet martwić, .Addponieważ to używa wewnętrznie .InsertItem, więc jeśli .InsertItemzostanie zastąpione i dostosowane, .Addnie będzie bałaganu z zamawianiem.
HB

1

Oto, co robię z rozszerzeniami 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

To zadziałało dla mnie, znalazłem to gdzieś dawno temu.

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

Stosowanie:

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

0

Musiałem mieć możliwość sortowania według wielu rzeczy, a nie tylko jednej. Ta odpowiedź jest oparta na niektórych innych odpowiedziach, ale pozwala na bardziej złożone sortowanie.

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

Kiedy go używasz, przekaż serię wywołań OrderBy / ThenBy. Lubię to:

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

0

Wiele się nauczyłem z innych rozwiązań, ale znalazłem kilka problemów. Po pierwsze, niektóre zależą od IndexOf, który wydaje się być dość powolny w przypadku dużych list. Po drugie, moja ObservableCollection miała jednostki EF i wydawało się, że użycie metody Remove uszkodziło niektóre właściwości klucza obcego. Może robię coś złego.

Niezależnie od tego, zamiast tego można użyć polecenia Usuń / Wstaw, ale powoduje to pewne problemy z poprawką wydajności.

Aby rozwiązać problem z wydajnością, tworzę słownik z posortowanymi wartościami IndexOf. Aby słownik był aktualny i aby zachować właściwości jednostki, użyj zamiany zaimplementowanej z dwoma ruchami zamiast jednego, jak zaimplementowano w innych rozwiązaniach.

Pojedynczy ruch przesuwa indeksy elementów między lokalizacjami, co mogłoby unieważnić słownik IndexOf. Dodanie drugiego ruchu w celu zaimplementowania zamiany przywraca lokalizacje.

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

Och, rozumiem ... Gayot chciał przyznać nagrodę za najbardziej negatywnie ocenioną odpowiedź lol
NielW

Nigdy nie widziałem przyznawania nagrody w
sarkazmie
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.