Comment trier une collection observable?


97

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
}

Ce que j'ai mis 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 puis-je le trier par clé?


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

Je ne sais pas comment comprendre cela. Fondamentalement, je veux juste le faire trier, la collection ne sera pas très grande (20 articles maximum) donc tout va faire (très probablement)
Maciek

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

Regardez les réponses sur cette page: indication très claire d'une API cassée lorsqu'il faut plus de 22 réponses pour certaines fonctionnalités critiques et de base.
Gerry

Réponses:


21

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 collections plus importantes, faites attention au nombre de notifications de modification de collection.

J'ai mis à jour mon code pour améliorer les performances et gérer les doublons (merci à nawfal pour avoir mis en évidence les mauvaises performances de l'original même si cela a bien fonctionné sur l'exemple de données d'origine). L'observable est partitionné en une moitié triée à gauche et une moitié droite non triée, où chaque fois que l'élément minimum (tel qu'il se trouve dans la liste triée) est déplacé à la fin de la partition triée à partir de l'élément non trié. Pire 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
            }

utilisation: Exemple avec un observateur (utilisé une classe Person 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 Person implémente à la fois IComparable et IEquatable, ce dernier est utilisé pour minimiser les modifications apportées à la collection afin de réduire le nombre de notifications de modification déclenchées

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

Pour renvoyer un ObservableCollection, appelez .ToObservableCollection sur * sortedOC * en utilisant par exemple [cette implémentation] [1].

**** réponse orig - 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;
    }
}

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

9
Je ne suis pas fan de cette réponse car elle ne vous donne pas une ObservableCollection triée.
xr280xr

63
-1 car il ne trie pas le ObservableCollection , mais crée à la place une nouvelle collection.
Kos

2
Le code mis à jour fonctionnera, mais a une complexité temporelle O (n ^ 2). Cela peut être amélioré en O (n * log (n)) en utilisant BinarySearchau lieu de IndexOf.
William Morrison

2
Excellente solution! Pour ceux qui héritent de ObservableCollection <T>, il est possible d'utiliser la méthode protégée MoveItem () au lieu d'utiliser les méthodes RemoveAt et Insert. Voir aussi: referencesource.microsoft.com/#system/compmod/system/…
Herman Cordes

84

Cette simple extension a parfaitement fonctionné pour moi. Je devais juste m'assurer que MyObjectc'était le cas IComparable. Lorsque la méthode de tri est appelée sur la collection observable de MyObjects, la CompareTométhode sur MyObjectest appelée, qui appelle ma méthode de tri logique. Bien qu'il n'ait pas toutes les cloches et les sifflets du reste des 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();

7
cela devrait être la réponse
thumbmunkeys

1
J'ai mis à jour ma réponse ci-dessus car c'était la réponse acceptée et corrige l'amélioration des performances par rapport à cette réponse, ce qui soulève des notifications de modification pour tout ce qui se trouve dans la collection
Andrew

3
Très bonne réponse. Une raison pour laquelle vous utilisez à la return Utils.LogicalStringCompare(a.Title, b.Title);place de return string.Compare(a.Title, b.Title);? @NeilW
Joe

2
@Joe, j'avais besoin de faire une comparaison logique au lieu d'une comparaison de chaîne standard, c'est pourquoi j'avais besoin d'écrire l'extension en premier lieu. La comparaison de chaînes logiques trie correctement les nombres dans les chaînes, plutôt que de les ordonner comme des chaînes (1, 2, 20, 1000 au lieu de 1, 1000, 2, 20, etc.)
NielW

4
c'est absolument la voie à suivre. J'ai ajouté ma propre réponse en étendant cela, vous permettant de passer un keySelector au lieu d'utiliser IComparable, comme le fait généralement LINQ.
Jonesopolis

39

J'ai trouvé une entrée de blog pertinente qui fournit une meilleure réponse que celles ici:

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

METTRE À JOUR

L' ObservableSortedList que @romkyns souligne dans les commentaires maintient automatiquement l'ordre de tri.

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

Notez cependant aussi la remarque

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


2
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 conserve son tri à mesure que des éléments sont ajoutés et supprimés. Je vais écrire le mien je pense.
Stephen Drew

@Steve Vous pouvez essayer celui-ci .
Roman Starkov

Merci pour le lien, j'ai opté pour la méthode d'extension car cela semblait la solution la plus précise. Fonctionne un charme: D
pengibot

bw est-ce que quelqu'un a remarqué que le blog a une faute de frappe dans le nom du fichier html (obversablecollection)? : P
laishiekai

1
@romkyns la réponse était d'étendre ObservableCollection <T>. Le GridView le reconnaît alors très bien. Ensuite, cachez simplement ses méthodes, comme vous le faites. Je publierai une solution complète quand j'aurai le temps.
Weston

25

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 trier comme ceci:

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

Plus de détails: http://jaider.net/2011-05-04/sort-a-observablecollection/


4
Cela efface ObservableCollection puis rajoute tous les objets - il est donc intéressant de noter que si votre interface utilisateur est liée à la collection, vous ne verrez pas de changements animés, par exemple lorsque les éléments se déplacent
Carlos P

1
Je ne sais pas pourquoi vous devez afficher des éléments en mouvement ... par exemple, vous avez normalement ObservableCollectionlié à ItemSource des listes déroulantes et vous ne voyez pas du tout la collection. Aussi cette opération de vidange et de remplissage est ultra rapide ... la "lente" peut être celle qui est déjà optimisée. enfin, vous pouvez modifier ce code pour implémenter votre méthode de déplacement, avoir le sortedlistet sourcele reste est facile.
Jaider

3
Si vous êtes lié à une liste déroulante, vous ne bénéficierez pas de voir des objets se déplacer, c'est vrai. Si vous êtes lié à un ListBox, les infrastructures telles que WPF ou Silverlight ou les applications du Windows Store fourniront des commentaires visuels utiles lorsque les objets de la collection sont réindexés.
Carlos P

Bien que cela soit plus rapide que l'approche Déplacer, cela déclenche un certain nombre d'événements Reset / Add. La réponse la plus votée (approche Move) minimise cela et soulève à juste titre des Moveévénements, cela aussi uniquement pour les personnes réellement émues.
nawfal

19

WPF fournit un tri en direct prêt à l' emploi à l'aide de 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);
    }
}

Une fois cette initialisation terminée, il n'y a plus rien à faire. L'avantage par rapport à un tri passif est que ListCollectionView fait tout le gros du travail d'une manière transparente pour le développeur. Les nouveaux éléments sont automatiquement placés dans leur ordre de tri correct. Toute classe qui dérive deIComparer de T convient à la propriété de tri personnalisée.

Consultez ListCollectionView pour la documentation et les autres fonctionnalités.


6
qui a réellement fonctionné: D c'est une solution bien meilleure que l'autre solution «sur-conçue» pour une tâche aussi simple.
MushyPeas

Où est passé votre blog?
phoog

Le problème avec les choses "transparentes" est que vous ne pouvez pas voir où chercher quand cela ne fonctionne pas. La documentation de Microsoft a un exemple 100% transparent, c'est-à-dire que vous ne pouvez pas le voir du tout.
Paul McCarthy

15

J'ai aimé l'approche de la méthode d'extension de tri à bulles sur le blog de "Richie" ci-dessus, mais je ne veux pas nécessairement simplement trier en comparant l'objet entier. Je souhaite plus souvent trier sur une propriété spécifique de l'objet. Je l'ai donc modifié pour accepter un sélecteur de clé comme le fait OrderBy afin que vous puissiez choisir sur quelle 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);
                }
            }
        }
    }

Ce que vous appelleriez de la même manière que vous appelleriez OrderBy, sauf qu'il 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
Merci de publier ceci - comme indiqué dans les commentaires sur le blog de Richie, il y a quelques améliorations intéressantes à ce code; notamment en utilisant la méthode 'Move' de la source. Je suppose que cela remplacerait les lignes Remove / Insert par source.Move (j-1, j);
Carlos P

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

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

Cela soulève un certain nombre d'événements Remove / Add (pour chaque N je crois). La réponse la plus élevée minimise cela et soulève à juste titre les événements Move, cela aussi uniquement pour les vraiment déplacés. La clé ici est de ne pas faire un tri sur place tout de suite, mais plutôt de le trier en externe en utilisant OrderBy, puis de faire une comparaison pour déterminer le changement réel.
nawfal

11

La réponse de @ NielW est la voie à suivre, pour un véritable tri sur place. Je voulais ajouter une solution légèrement modifiée qui vous permet de contourner l'utilisation 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);
    }
}

maintenant, vous pouvez l'appeler comme la plupart des méthodes LINQ:

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

2
Pour un cookie au chocolat supplémentaire, vous pouvez ajouter un paramètre booléen "Ascending" et un if(!Ascending) sorted.Reverse();juste avant le for: D (et pas besoin de vous soucier de la mémoire, cette méthode Reverse ne crée aucun nouvel objet, elle est inversée en place)
Sharky

Selon ma collection de tests, Move (0,0) conduit à un événement CollectionChanged. Par conséquent, ce serait une amélioration des performances de vérifier d'abord si un mouvement est même nécessaire.
sa.he

10

Je voudrais ajouter à la réponse de NeilW . Pour incorporer une méthode qui ressemble au orderby. Ajoutez cette méthode en tant qu'extension:

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

Et utilisez comme:

myCollection = new ObservableCollection<MyObject>();

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

8

Une variante est l'endroit où vous triez la collection sur place à l'aide d'un algorithme de tri par sélection . Les éléments sont mis en place à l'aide de la Moveméthode. Chaque mouvement déclenchera l' CollectionChangedévénement avec NotifyCollectionChangedAction.Move(et aussi PropertyChangedavec le nom de la propriété Item[]).

Cet algorithme a quelques propriétés intéressantes:

  • L'algorithme peut être implémenté comme un tri stable.
  • Le nombre d'éléments déplacés dans la collection (par exemple les CollectionChangedévénements déclenchés) est presque toujours inférieur à celui d'autres algorithmes similaires comme le tri par insertion et le tri par bulles.

L'algorithme est assez simple. La collection est itérée pour trouver le plus petit élément qui est ensuite déplacé au début de la collection. Le processus est répété à partir du deuxième élément et ainsi de suite jusqu'à ce que tous les éléments aient été mis en place. L'algorithme n'est pas très efficace, mais pour tout ce que vous allez afficher dans une interface utilisateur, cela ne devrait pas avoir d'importance. Cependant, en termes de nombre d'opérations de déménagement, c'est assez efficace.

Voici une méthode d'extension qui, pour simplifier, nécessite que les éléments implémentent IComparable<T> . D'autres options utilisent un IComparer<T>ou un Func<T, T, Int32>.

public static class ObservableCollectionExtensions {

  public static void Sort<T>(this ObservableCollection<T> collection) where T : IComparable<T> {
    if (collection == null)
      throw new ArgumentNullException("collection");

    for (var startIndex = 0; startIndex < collection.Count - 1; startIndex += 1) {
      var indexOfSmallestItem = startIndex;
      for (var i = startIndex + 1; i < collection.Count; i += 1)
        if (collection[i].CompareTo(collection[indexOfSmallestItem]) < 0)
          indexOfSmallestItem = i;
      if (indexOfSmallestItem != startIndex)
        collection.Move(indexOfSmallestItem, startIndex);
    }
  }

}

Le tri d'une collection consiste simplement à appeler la méthode d'extension:

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

1
C'est ma méthode de tri préférée parmi toutes celles décrites ici, malheureusement la méthode Move n'est pas disponible dans Silverlight 5.
Eduardo Brites

1
Im obtenant l'erreur «Profiler.Profile.ProfileObject» ne peut pas être utilisé comme paramètre de type «T» dans le type ou la méthode générique «ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>)». Il n'y a pas de conversion de référence implicite de 'Profiler.Profile.ProfileObject' à 'System.IComparable <Profiler.Profile.ProfileObject>
New Bee

1
@NewBee: Cette méthode d'extension spécifie une contrainte générique sur Tpour pouvoir trier les éléments de la collection. Le tri implique le concept de plus et moins de et vous seul pouvez définir comment ProfileObjectest ordonné. Pour utiliser la méthode d'extension, vous devez implémenter IComparable<ProfileObject>sur ProfileObject. D'autres alternatives sont comme indiqué en spécifiant un IComparer<ProfileObject>ou un Func<ProfileObject, ProfileObject, int>et modifiez le code de tri en conséquence.
Martin Liversage

4

Pour améliorer un peu la méthode d'extension sur la réponse xr280xr, j'ai ajouté un paramètre booléen facultatif pour déterminer si le tri est descendant ou non. J'ai également inclus la suggestion de Carlos P dans le commentaire de cette réponse. Veuillez voir ci-dessous.

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

Avez-vous besoin de garder votre collection triée à tout moment? Lorsque vous récupérez les paires, avez-vous besoin qu'elles soient toujours triées, ou ce n'est que pour quelques fois (peut-être juste pour la présentation)? Quelle sera la taille de votre collection? De nombreux facteurs peuvent vous aider à décider de la méthode à utiliser.

Si vous avez besoin que la collection soit triée à tout moment, même lorsque vous insérez ou supprimez des éléments et que la vitesse d'insertion n'est pas un problème, vous devriez peut-être implémenter une sorte de SortedObservableCollection @Gerrie Schenck mentionné ou vérifier cette implémentation .

Si vous avez besoin que votre collection soit triée quelques fois, utilisez:

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

Cela prendra du temps pour trier la collection, mais même dans ce cas, cela pourrait être la meilleure solution en fonction de ce que vous en faites.


1
Le lien dans cette réponse est vers le code sous licence LGPL, donc si vous êtes Silverlight (ne peut pas lier dynamiquement) ou non open source, soyez prudent avec ce code.
yzorg

2

Ma réponse actuelle est déjà celle qui a le plus de voix, mais j'ai trouvé une manière meilleure et plus moderne de le faire.

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

ne serait-il pas préférable de mettre à jour la réponse originale?
Nathan Hughes

Non, il a déjà été voté plus que toute autre réponse. Je ne vais pas supposer que les gens préfèrent le faire de cette façon. Je pensais juste que je proposerais une autre façon de le faire, d'autant plus qu'il y avait une prime sur les nouvelles réponses.
NielW

1

Créez une nouvelle classe SortedObservableCollection, dérivez-la ObservableCollectionet implémentez-la IComparable<Pair<ushort, string>>.


1

Une façon serait de le convertir en une liste, puis d'appeler Sort (), en fournissant un délégué de comparaison. Quelque chose comme:-

(non testé)

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


1

Que diable, je vais également ajouter une réponse rapidement bricolée ... cela ressemble un peu à d'autres implémentations ici, mais je l'ajouterai quand même:

(à peine testé, j'espère que je ne me gêne pas)

Décrivons d'abord quelques objectifs (mes hypothèses):

1) Doit trier ObservableCollection<T>en place, pour maintenir les notifications, etc.

2) Ne doit pas être horriblement inefficace (c'est-à-dire quelque chose de proche de la "bonne" efficacité de tri 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

Aucune de ces réponses n'a fonctionné dans mon cas. Soit parce qu'il fout la liaison, ou nécessite tellement de codage supplémentaire que c'est une sorte de cauchemar, soit la réponse est tout simplement cassée. Alors, voici une autre réponse plus simple que j'ai pensé. C'est beaucoup moins de code et cela reste la même collection observable avec un type de méthode this.sort supplémentaire. Faites-moi savoir s'il y a une raison pour laquelle je ne devrais pas le faire de cette façon (efficacité, etc.)?

public class ScoutItems : ObservableCollection<ScoutItem>
{
    public void Sort(SortDirection _sDir, string _sItem)
    {
             //TODO: Add logic to look at _sItem and decide what property to sort on
            IEnumerable<ScoutItem> si_enum = this.AsEnumerable();

            if (_sDir == SortDirection.Ascending)
            {
                si_enum = si_enum.OrderBy(p => p.UPC).AsEnumerable();
            } else
            {
                si_enum = si_enum.OrderByDescending(p => p.UPC).AsEnumerable();
            }

            foreach (ScoutItem si in si_enum)
            {
                int _OldIndex = this.IndexOf(si);
                int _NewIndex = si_enum.ToList().IndexOf(si);
                this.MoveItem(_OldIndex, _NewIndex);
            }
      }
}

... Où ScoutItem est ma classe publique. Cela semblait juste beaucoup plus simple. Avantage supplémentaire: cela fonctionne réellement et ne gâche pas les liaisons ou ne renvoie pas une nouvelle collection, etc.


1

D'accord, comme j'avais des problèmes pour faire fonctionner ObservableSortedList avec XAML, j'ai continué et j'ai créé SortingObservableCollection . Il hérite d'ObservableCollection, il fonctionne donc avec XAML et je l'ai testé à une couverture de code de 98%. Je l'ai utilisé dans mes propres applications, mais je ne promets pas qu'il soit sans bogue. N'hésitez pas à contribuer. Voici un exemple d'utilisation de code:

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

C'est un PCL, il devrait donc fonctionner avec Windows Store, Windows Phone et .NET 4.5.1.


1
Vous ne devriez probablement pas utiliser newtoutes ces méthodes, si quelqu'un a une instance de type plus générique, ces méthodes ne seront pas appelées. Au lieu de cela, overridechaque méthode remplaçable et modifiez-les si nécessaire ou utilisez la méthode de secours base.Method(...). Vous, par exemple, n'avez même pas besoin de vous inquiéter .Addcar cela utilise en interne .InsertItem, donc si .InsertItemest remplacé et ajusté, .Addne gênera pas l'ordre.
HB

1

C'est ce que je fais avec les extensions 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

Cela a fonctionné pour moi, je l'ai trouvé il y a longtemps quelque part.

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

Usage:

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

0

J'avais besoin de pouvoir trier par plusieurs choses et non pas une seule. Cette réponse est basée sur certaines des autres réponses, mais elle permet un tri plus complexe.

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

Lorsque vous l'utilisez, passez une série d'appels OrderBy / ThenBy. Comme ça:

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

0

J'ai beaucoup appris des autres solutions, mais j'ai trouvé quelques problèmes. Premièrement, certains dépendent d'IndexOf qui a tendance à être assez lent pour les grandes listes. Deuxièmement, mon ObservableCollection avait des entités EF et l'utilisation de Remove semblait corrompre certaines des propriétés de clé étrangère. Peut-être que je fais quelque chose de mal.

Quoi qu'il en soit, A Move peut être utilisé à la place Supprimer / Insérer, mais cela pose certains problèmes avec le correctif de performances.

Pour résoudre le problème de performances, je crée un dictionnaire avec les valeurs triées IndexOf. Pour maintenir le dictionnaire à jour et conserver les propriétés de l'entité, utilisez un swap implémenté avec deux mouvements au lieu d'un tel qu'implémenté dans d'autres solutions.

Un seul déplacement déplace les index des éléments entre les emplacements, ce qui invaliderait le dictionnaire IndexOf. L'ajout d'un second mouvement pour implémenter un échange restaure les emplacements.

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 je comprends ... Gayot voulait donner la prime à la réponse la plus négative lol
NielW

Jamais vu attribuer des primes dans le sarcasme :)
Nawfal
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.