J'ai un cours suivant:
[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é?
OP Edit: comme beaucoup l’ont signalé correctement, la réponse originale ne renvoie pas la même collection ((à l’origine centrée sur le tri de la partie dictionnaire du Q). S'il vous plaît voir éditer en bas où je traite du tri d'une collection observable. Original laissé ici car il reçoit toujours des votes
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
Sinon, vous pouvez 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;
}
}
[~ # ~] éditer [~ # ~]
Pour renvoyer une ObservableCollection, appelez .ToObservableCollection sur sortOC à l’aide de ex. cette implémentation .
OP EDIT 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 de grande taille, surveillez le nombre de notifications de collection modifiées, par exemple
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++;
}
}
}
utilisation: Échantillon avec un observateur (utilise une classe Person pour que cela reste 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 = "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();
};
Sortie d'en haut:
a retiré Katy de 51 ans à l'indice 0
a ajouté Katy, âgée de 51 ans, à l'indice 8.
a retiré Mary âgée de 41 ans à l'index 3
a ajouté Mary à l'âge de 41 ans à l'indice 7.
a retiré Jane, âgée de 20 ans, index 3
a ajouté Jane à l'âge de 20 ans, indice 5.
a retiré Jim âgé de 39 ans à l'indice 3
a ajouté Jim âgé de 39 ans à l'indice 6.
a retiré Jane, 20 ans, indice 4
a ajouté Jane à l'âge de 20 ans, indice 5.
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 modifications émises.
Cette simple extension a fonctionné à merveille 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
, la méthode CompareTo
sur MyObject
est appelée et appelle ma méthode de tri logique. Bien qu'il ne contienne pas tous les éléments 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();
Je sais que cette question est ancienne, mais cela m'est arrivé pendant que nous cherchions sur Google et avons trouvé une entrée de blog pertinente qui fournit une meilleure réponse que celles-ci:
http://kiwigis.blogspot.com/2010/03/how-to-sort-obversablecollection.html
[~ # ~] met à jour [~ # ~]
La ObservableSortedList que @romkyns indique dans les commentaires conserve automatiquement l'ordre de tri.
Implémente une collection observable qui maintient ses éléments dans un ordre trié. En particulier, les modifications apportées aux propriétés d'élément qui entraînent des modifications d'ordre sont gérées correctement.
Cependant notez aussi la remarque
Peut-être problématique en raison de la complexité comparative de l'interface impliquée et de sa documentation relativement médiocre (voir https://stackoverflow.com/a/5883947/3308 ).
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/
WPF fournit un tri direct à l'aide de la classe ListCollectionView
...
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 le gros du travail de manière transparente pour le développeur. Les nouveaux éléments sont automatiquement placés dans le bon ordre de tri. Toute classe dérivée de IComparer
de T convient à la propriété de tri personnalisée.
Voir ListCollectionView pour la documentation et d’autres fonctionnalités.
J'ai aimé l'approche de la méthode d'extension de type bulle 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. Je l'ai donc modifié pour accepter un sélecteur de clé comme OrderBy vous permet de 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 appelleriez de la même façon que vous appelleriez OrderBy, à la différence 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);
La réponse de @ NielW est la voie à suivre, pour un vrai tri sur place. Je voulais ajouter une solution légèrement modifiée qui vous permet d'éviter de devoir utiliser 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);
}
}
vous pouvez maintenant l'appeler comme n'importe quelle méthode LINQ:
myObservableCollection.Sort(o => o.MyProperty);
Je voudrais ajouter à la réponse de NeilW. Pour incorporer une méthode qui ressemble à la 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 utiliser comme:
myCollection = new ObservableCollection<MyObject>();
//Sorts in place, on a specific Func<T,T>
myCollection.Sort(x => x.ID);
Une variante consiste à trier la collection sur place à l'aide d'un algorithme tri par sélection . Les éléments sont déplacés en utilisant la méthode Move
. Chaque mouvement déclenchera l'événement CollectionChanged
avec NotifyCollectionChangedAction.Move
(et aussi PropertyChanged
avec le nom de la propriété Item[]
).
Cet algorithme a quelques propriétés de Nice:
CollectionChanged
événements déclenchés) est presque toujours inférieur à celui d'autres algorithmes similaires, tels que le tri par insertion et le tri par bulle.L'algorithme est assez simple. La collection est itérative pour trouver le plus petit élément qui est ensuite déplacé au début de la collection. Le processus est répété en commençant par le deuxième élément et ainsi de suite jusqu'à ce que tous les éléments aient été déplacés. L'algorithme n'est pas très efficace, mais peu importe ce que vous allez afficher dans une interface utilisateur. Cependant, le nombre d'opérations de déménagement est très efficace.
Voici une méthode d'extension qui, pour des raisons de simplicité, 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);
}
}
}
Trier une collection consiste simplement à invoquer la méthode d'extension:
var collection = new ObservableCollection<String>(...);
collection.Sort();
Pour améliorer un peu la méthode d’extension sur la réponse xr280xr, j’ai ajouté un paramètre bool facultatif permettant de déterminer si le tri est décroissant ou non. J'ai également inclus la suggestion de Carlos P dans le commentaire de cette réponse. S'il vous plaît 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);
}
}
}
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 quelques fois (peut-être juste pour les présenter)? Quelle est la taille de votre collection? De nombreux facteurs peuvent vous aider à choisir 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 devez peut-être mettre en œuvre une sorte de SortedObservableCollection
comme @Gerrie Schenck ou la caisse cette implémentation .
Si vous avez besoin que votre collection soit triée juste pour quelques fois, utilisez:
my_collection.OrderBy(p => p.Key);
Cela prendra un certain temps pour trier la collection, mais même dans ce cas, cela pourrait être la meilleure solution selon ce que vous en feriez.
Ma réponse actuelle a déjà reçu le plus grand nombre de votes, mais j’ai trouvé une façon plus moderne et plus efficace de procéder.
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)))));
Aucune de ces réponses n'a fonctionné dans mon cas. Soit parce que la liaison est foutue, ou qu'elle nécessite tellement de codage supplémentaire que c'est un peu un cauchemar, ou bien la réponse est tout simplement cassée. Donc, voici encore une autre réponse plus simple que je pensais. C'est beaucoup moins de code et il 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 et ne gêne pas les liaisons, ne renvoie pas une nouvelle collection, etc.
Bon sang, je vais aussi ajouter une réponse rapide ... cela ressemble un peu à d'autres implémentations, mais je vais l'ajouter quand même:
(à peine testé, j'espère que je ne m'embarrasse pas moi-même)
Énumérons 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 proche à l'efficacité de tri standard "bonne")
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
}
}
Créez une nouvelle classe SortedObservableCollection
, dérivez-la de ObservableCollection
et implémentez IComparable<Pair<ushort, string>>
.
Une solution consisterait à le convertir en liste, puis à 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));
Cela a fonctionné pour moi, 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);
Voici 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);
}
}
Je pense que c'est la solution la plus élégante:
Bon, étant donné que j'avais des problèmes pour faire fonctionner ObservableSortedList avec XAML, j'ai décidé de créer 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 est exempt de bogues. N'hésitez pas à contribuer. Voici un exemple d'utilisation du 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 une PCL, donc elle devrait fonctionner avec Windows Store, Windows Phone et .NET 4.5.1.
Je devais être capable de trier par plusieurs choses et pas seulement une. 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, transmettez 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));