105 votes

Comment trier une collection observable ?

J'ai une classe suivante :

[DataContract]
public class Pair<TKey, TValue> : INotifyPropertyChanged, IDisposable
{
    public Pair(TKey key, TValue value)
    {
        Key = key;
        Value = value;
    }

    #region Properties
    [DataMember]
    public TKey Key
    {
        get
        { return m_key; }
        set
        {
            m_key = value;
            OnPropertyChanged("Key");
        }
    }
    [DataMember]
    public TValue Value
    {
        get { return m_value; }
        set
        {
            m_value = value;
            OnPropertyChanged("Value");
        }
    }
    #endregion

    #region Fields
    private TKey m_key;
    private TValue m_value;
    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    { }

    #endregion
}

que j'ai placé dans une 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"));

Q : Comment trier par clé ?

0 votes

Cherchez-vous une implémentation de tri dans la classe ou n'importe quel type de tri fera l'affaire ?

0 votes

Je ne suis pas sûr de comprendre cela. En gros, je veux juste que ce soit trié, la collection ne sera pas très grande (20 articles maximum) donc n'importe quoi fera l'affaire (très probablement).

0 votes

Voir ceci pour une solution WPF stackoverflow.com/questions/1945461/

93voto

NielW Points 523

Cette simple extension a parfaitement fonctionné pour moi. Je devais juste m'assurer que MyObject était IComparable . Lorsque la méthode de tri est appelée sur la collection observable de MyObjects , le CompareTo méthode sur MyObject qui appelle ma méthode de tri logique. Bien qu'elle n'ait pas toutes les caractéristiques des autres réponses affichées ici, c'est exactement ce dont j'avais besoin.

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

0 votes

Il semble que ce soit la seule réponse qui trie réellement la liste d'origine, et qui le fait sans suppression/ajout d'éléments.

1 votes

J'ai mis à jour ma réponse ci-dessus car elle a été acceptée et permet d'améliorer les performances par rapport à cette réponse qui envoie des notifications de changement pour tout ce qui se trouve dans la collection.

3 votes

Excellente réponse. Quelle est la raison pour laquelle vous utilisez return Utils.LogicalStringCompare(a.Title, b.Title); au lieu de return string.Compare(a.Title, b.Title); ? @NeilW

39voto

Eric J. Points 73338

J'ai trouvé un article de blog pertinent qui fournit une meilleure réponse que celles données ici :

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

MISE À JOUR

En ObservableSortedList que @romkyns signale dans les commentaires maintient automatiquement l'ordre de tri.

Implémente une collection observable qui maintient ses éléments dans l'ordre. En particulier, les modifications des propriétés des éléments qui entraînent des changements d'ordre sont gérées correctement.

Toutefois, il convient également de noter la remarque suivante

Peut être bogué en raison de la complexité relative de l'interface concernée et de sa documentation relativement pauvre (voir https://stackoverflow.com/a/5883947/33080 ).

2 votes

En effet, ce blog est plus utile. Cependant, je n'ai pas encore trouvé de réponse décente à la question d'avoir une collection observable qui maintient son tri au fur et à mesure que des éléments sont ajoutés et supprimés. Je pense que je vais écrire la mienne.

0 votes

@Steve Vous pouvez essayer celui-ci .

0 votes

Merci pour le lien, j'ai opté pour la méthode de l'extension car cela me semblait être la solution la plus pratique. Ça marche du tonnerre :D

29voto

Jaider Points 2366

Vous pouvez utiliser cette méthode simple :

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

Vous pouvez faire le tri comme suit :

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

4 votes

Cela efface la ObservableCollection puis réintègre tous les objets - il est donc utile de noter que si votre interface utilisateur est liée à la collection, vous ne verrez pas les changements animés, par exemple lorsque les objets se déplacent.

1 votes

Je ne comprends pas très bien pourquoi vous devez afficher des éléments qui se déplacent... par exemple, vous avez normalement ObservableCollection lié à ItemSource des listes déroulantes et vous ne voyez pas du tout la collection. De plus, cette opération d'effacement et de remplissage est ultra rapide... l'opération "lente" peut être le tri qui est déjà optimisé. enfin, vous pouvez modifier ce code pour implémenter votre méthode de déplacement, en ayant l'attribut sortedlist y source le reste est facile.

3 votes

Si vous êtes lié à une liste déroulante, vous n'avez pas intérêt à ce que les éléments se déplacent, c'est vrai. En revanche, si vous êtes lié à une ListBox, des frameworks tels que WPF, Silverlight ou Windows Store Apps vous fourniront des informations visuelles utiles lorsque les objets de la collection seront réindexés.

23voto

Andrew Points 1187

Le tri d'un observable et le retour du même objet trié peuvent être effectués à l'aide d'une méthode d'extension. Pour les grandes collections, attention au nombre de notifications de changement de collection.

J'ai mis à jour mon code pour améliorer les performances (grâce à nawfal) et pour gérer les doublons, ce qu'aucune autre réponse ne fait au moment où j'écris ces lignes. L'observable est partitionné en une moitié gauche triée et une moitié droite non triée, où à chaque fois l'élément minimum (tel que trouvé dans la liste triée) est déplacé à la fin de la partition triée à partir de la partition non triée. Dans le pire des cas, O(n). Essentiellement un tri par sélection (voir ci-dessous pour la sortie).

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
            }

l'utilisation : Exemple avec un observateur (utilisation d'une classe Personne pour rester simple)

    public class Person:IComparable<Person>,IEquatable<Person>
            { 
                public string Name { get; set; }
                public int Age { get; set; }

                public int CompareTo(Person other)
                {
                    if (this.Age == other.Age) return 0;
                    return this.Age.CompareTo(other.Age);
                }

                public override string ToString()
                {
                    return Name + " aged " + Age;
                }

                public bool Equals(Person other)
                {
                    if (this.Name.Equals(other.Name) && this.Age.Equals(other.Age)) return true;
                    return false;
                }
            }

          static void Main(string[] args)
            {
                Console.WriteLine("adding items...");
                var observable = new ObservableCollection<Person>()
                {
                    new Person {Name = "Katy", Age = 51},
                    new Person {Name = "Jack", Age = 12},
                    new Person {Name = "Bob", Age = 13},
                    new Person {Name = "Alice", Age = 39},
                    new Person {Name = "John", Age = 14},
                    new Person {Name = "Mary", Age = 41},
                    new Person {Name = "Jane", Age = 20},
                    new Person {Name = "Jim", Age = 39},
                    new Person {Name = "Sue", Age = 5},
                    new Person {Name = "Kim", Age = 19}
                };

                //what do observers see?

observable.CollectionChanged += (sender, e) =>
        {
            Console.WriteLine(
                e.OldItems[0] + " move from " + e.OldStartingIndex + " to " + e.NewStartingIndex);
            int i = 0;
            foreach (var person in sender as ObservableCollection<Person>)
            {
                if (i == e.NewStartingIndex)
                {
                    Console.Write("(" + (person as Person).Age + "),");
                }
                else
                {
                    Console.Write((person as Person).Age + ",");
                }

                i++;
            }

            Console.WriteLine();
        };

Détails de la progression du tri montrant comment la collection est pivotée :

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 Personne implémente à la fois IComparable et IEquatable, ce dernier étant utilisé pour minimiser les changements apportés à la collection afin de réduire le nombre de notifications de changement.

  • EDIT Trie la même collection sans créer de nouvelle copie *

Pour renvoyer une ObservableCollection, appelez .ToObservableCollection sur *sortedOC* en utilisant par exemple [this implementation][1].

**** orig réponse - cela crée une nouvelle collection **** Vous pouvez utiliser linq comme l'illustre la méthode doSort ci-dessous. Un extrait de code rapide : produit

3:xey 6:fty 7:aaa

Vous pouvez également utiliser une méthode d'extension sur la collection elle-même

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

0 votes

J'ai trouvé ceci et je l'ai trouvé très utile. Est-ce LINQ qui crée la var sortedOC ?

10 votes

Je ne suis pas fan de cette réponse car elle ne permet pas d'obtenir une ObservableCollection triée.

67 votes

-1 puisqu'il ne trie pas el ObservableCollection mais crée une nouvelle collection.

15voto

xr280xr Points 2057

J'ai bien aimé l'approche de la méthode d'extension du tri par bulles sur le blog de "Richie" ci-dessus, mais je ne veux pas nécessairement trier en comparant l'objet entier. Je veux plus souvent trier sur une propriété spécifique de l'objet. J'ai donc modifié la méthode pour qu'elle accepte un sélecteur de clé, comme le fait OrderBy, afin que vous puissiez choisir la propriété à trier :

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

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

        for (int i = source.Count - 1; i >= 0; i--)
        {
            for (int j = 1; j <= i; j++)
            {
                TSource o1 = source[j - 1];
                TSource o2 = source[j];
                if (comparer.Compare(keySelector(o1), keySelector(o2)) > 0)
                {
                    source.Remove(o1);
                    source.Insert(j, o1);
                }
            }
        }
    }

que vous appellerez de la même manière que OrderBy, sauf qu'elle triera l'instance existante de votre ObservableCollection au lieu de renvoyer une nouvelle collection :

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

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

1 votes

Merci d'avoir posté ceci - comme indiqué dans les commentaires sur le blog de Richie, il y a quelques améliorations intéressantes à apporter à ce code, en particulier l'utilisation de la méthode "Move" de la source. Je suppose que cela remplacerait les lignes Remove/Insert par source.Move(j-1, j) ;

2 votes

Cet algorithme de tri n'est pas optimisé fr.wikipedia.org/wiki/Sorting_algorithm

0 votes

@Jaider Oui, il est optimisé, mais pas pour la vitesse brute globale.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X