Sort ObservableCollection<string> through C#
Solution 1
Introduction
Basically, if there is a need to display a sorted collection, please consider using the CollectionViewSource
class: assign ("bind") its Source
property to the source collection — an instance of the ObservableCollection<T>
class.
The idea is that CollectionViewSource
class provides an instance of the CollectionView
class. This is kind of "projection" of the original (source) collection, but with applied sorting, filtering, etc.
References:
Live Shaping
WPF 4.5 introduces "Live Shaping" feature for CollectionViewSource
.
References:
- WPF 4.5 New Feature: Live Shaping.
- CollectionViewSource.IsLiveSorting Property.
- Repositioning data as the data's values change (Live shaping).
Solution
If there still a need to sort an instance of the ObservableCollection<T>
class, here is how it can be done.
The ObservableCollection<T>
class itself does not have sort method. But, the collection could be re-created to have items sorted:
// Animals property setter must raise "property changed" event to notify binding clients.
// See INotifyPropertyChanged interface for details.
Animals = new ObservableCollection<string>
{
"Cat", "Dog", "Bear", "Lion", "Mouse",
"Horse", "Rat", "Elephant", "Kangaroo",
"Lizard", "Snake", "Frog", "Fish",
"Butterfly", "Human", "Cow", "Bumble Bee"
};
...
Animals = new ObservableCollection<string>(Animals.OrderBy(i => i));
Additional details
Please note that OrderBy()
and OrderByDescending()
methods (as other LINQ–extension methods) do not modify the source collection! They instead create a new sequence (i.e. a new instance of the class that implements IEnumerable<T>
interface). Thus, it is necessary to re-create the collection.
Solution 2
I know this is an old question, but is the first google result for "sort observablecollection" so thought it worth to leave my two cent.
The way
The way I would go is to build a List<>
starting from the ObservableCollection<>
, sort it (through its Sort()
method, more on msdn) and when the List<>
has been sorted, reorder the ObservableCollection<>
with the Move()
method.
The code
public static void Sort<T>(this ObservableCollection<T> collection, Comparison<T> comparison)
{
var sortableList = new List<T>(collection);
sortableList.Sort(comparison);
for (int i = 0; i < sortableList.Count; i++)
{
collection.Move(collection.IndexOf(sortableList[i]), i);
}
}
The test
public void TestObservableCollectionSortExtension()
{
var observableCollection = new ObservableCollection<int>();
var maxValue = 10;
// Populate the list in reverse mode [maxValue, maxValue-1, ..., 1, 0]
for (int i = maxValue; i >= 0; i--)
{
observableCollection.Add(i);
}
// Assert the collection is in reverse mode
for (int i = maxValue; i >= 0; i--)
{
Assert.AreEqual(i, observableCollection[maxValue - i]);
}
// Sort the observable collection
observableCollection.Sort((a, b) => { return a.CompareTo(b); });
// Assert elements have been sorted
for (int i = 0; i < maxValue; i++)
{
Assert.AreEqual(i, observableCollection[i]);
}
}
Notes
This is just a proof of concept, showing how to sort an ObservableCollection<>
without breaking the bindings on items.The sort algorithm has room for improvements and validations (like index checking as pointed out here).
Solution 3
I looked at these, I was getting it sorted, and then it broke the binding, as above. Came up with this solution, though simpler than most of yours, it appears to do what I want to,,,
public static ObservableCollection<string> OrderThoseGroups( ObservableCollection<string> orderThoseGroups)
{
ObservableCollection<string> temp;
temp = new ObservableCollection<string>(orderThoseGroups.OrderBy(p => p));
orderThoseGroups.Clear();
foreach (string j in temp) orderThoseGroups.Add(j);
return orderThoseGroups;
}
Solution 4
This is an ObservableCollection<T>
, that automatically sorts itself upon a change, triggers a sort only when necessary, and only triggers a single move collection change action.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
namespace ConsoleApp4
{
using static Console;
public class SortableObservableCollection<T> : ObservableCollection<T>
{
public Func<T, object> SortingSelector { get; set; }
public bool Descending { get; set; }
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
if (SortingSelector == null
|| e.Action == NotifyCollectionChangedAction.Remove
|| e.Action == NotifyCollectionChangedAction.Reset)
return;
var query = this
.Select((item, index) => (Item: item, Index: index));
query = Descending
? query.OrderBy(tuple => SortingSelector(tuple.Item))
: query.OrderByDescending(tuple => SortingSelector(tuple.Item));
var map = query.Select((tuple, index) => (OldIndex:tuple.Index, NewIndex:index))
.Where(o => o.OldIndex != o.NewIndex);
using (var enumerator = map.GetEnumerator())
if (enumerator.MoveNext())
Move(enumerator.Current.OldIndex, enumerator.Current.NewIndex);
}
}
//USAGE
class Program
{
static void Main(string[] args)
{
var xx = new SortableObservableCollection<int>() { SortingSelector = i => i };
xx.CollectionChanged += (sender, e) =>
WriteLine($"action: {e.Action}, oldIndex:{e.OldStartingIndex},"
+ " newIndex:{e.NewStartingIndex}, newValue: {xx[e.NewStartingIndex]}");
xx.Add(10);
xx.Add(8);
xx.Add(45);
xx.Add(0);
xx.Add(100);
xx.Add(-800);
xx.Add(4857);
xx.Add(-1);
foreach (var item in xx)
Write($"{item}, ");
}
}
}
Output:
action: Add, oldIndex:-1, newIndex:0, newValue: 10
action: Add, oldIndex:-1, newIndex:1, newValue: 8
action: Move, oldIndex:1, newIndex:0, newValue: 8
action: Add, oldIndex:-1, newIndex:2, newValue: 45
action: Add, oldIndex:-1, newIndex:3, newValue: 0
action: Move, oldIndex:3, newIndex:0, newValue: 0
action: Add, oldIndex:-1, newIndex:4, newValue: 100
action: Add, oldIndex:-1, newIndex:5, newValue: -800
action: Move, oldIndex:5, newIndex:0, newValue: -800
action: Add, oldIndex:-1, newIndex:6, newValue: 4857
action: Add, oldIndex:-1, newIndex:7, newValue: -1
action: Move, oldIndex:7, newIndex:1, newValue: -1
-800, -1, 0, 8, 10, 45, 100, 4857,
Solution 5
I created an extension method to the ObservableCollection
public static void MySort<TSource,TKey>(this ObservableCollection<TSource> observableCollection, Func<TSource, TKey> keySelector)
{
var a = observableCollection.OrderBy(keySelector).ToList();
observableCollection.Clear();
foreach(var b in a)
{
observableCollection.Add(b);
}
}
It seems to work and you don't need to implement IComparable
![Bishan](https://i.stack.imgur.com/deOKl.jpg?s=256&g=1)
Comments
-
Bishan almost 2 years
I have below
ObservableCollection<string>
. I need to sort this alphabetically.private ObservableCollection<string> _animals = new ObservableCollection<string> { "Cat", "Dog", "Bear", "Lion", "Mouse", "Horse", "Rat", "Elephant", "Kangaroo", "Lizard", "Snake", "Frog", "Fish", "Butterfly", "Human", "Cow", "Bumble Bee" };
I tried
_animals.OrderByDescending
. But I don't know how to use it correctly._animals.OrderByDescending(a => a.<what_is_here_?>);
How can I do this?
-
Sergey Vyacheslavovich Brunov almost 11 yearsPlease note that the order of items in collection is not modified after the
OrderByDescending()
method call. -
Sergey Vyacheslavovich Brunov almost 11 yearsPlease note that the order of items in collection is not modified after the
OrderByDescending()
method call. -
manji almost 11 yearsIt's not working due to what @Sergey Brunov explained. You need to save the result in another variable.
-
Bram Van Strydonck almost 11 years@SergeyBrunov: Exactly: you should say: animals = animals.OrderByDescending(a => a.Name);
-
poudigne about 8 yearsI get a Cannot convert ObservableCollection to OrderedObservableCollection error.
-
some_name about 8 yearsWorks great. I was just looking for a solution that won't break the binding, thanks Marco.
-
M. Pipal about 8 yearsThis doesn't work. It's not possible to assign ordered collection to ObservableCollection<T>, because type after sorting will be IOrderedEnumerable<T>, you'll get error.
-
Tom about 8 yearsNote: According to MSDN, the "ObservableCollection.IndexOf" Method is "an O(n) operation, where n is Count" and I suspect the "ObservableCollection.Move" Method calls the "ObservableCollection.RemoveAt" and "ObservableCollection.Insert" Methods each of which are (again, according to MSDN) also "an O(n) operation, where n is Count". Continued in next Comment...
-
Tom about 8 yearsIf run-time is significant, I suspect you're much better off re-creating the "ObservableCollection" using the sorted List (i.e. by using the "ObservableCollection<T>(List<T>)" Constructor). As I just commented on "D_Learning"'s Comment to "Sergey Brunov"'s Answer above: "If you (as you would when using, say, the MVVM Pattern) used a Property (inside a Class that Implements "INotifyPropertyChanged" Interface) to Set the "ObservableCollection" to the new sorted version and the Setter calls the Interface's "PropertyChanged" Event, then the Binding would not be broken."
-
Marco almost 8 yearsBindings wouldn't be broken, but all the subscriptions (in code) to any item's event would. On top of that, recreating the collection from scratch, the binded UI element (would it be a listview or whatever you want) might have to recreate all the visual items potentially taking a lot more (in time and/or memory consumption) then a 3*O(n) iteration... So, finally the right answer is always the same: it depends. This is just an alternative way to go when you can't or do not want to rebuild your list.
-
Travis almost 8 years
animals = new ObservableCollection<Animal>(animals.OrderByDescending(a => a.Name));
This will get around the IOrderedEnumerable error. -
vapcguy over 7 yearsI was able to implement this and it worked great in my WPF app where, if the bindings broke, the collection would not have populated in my ListBoxes. The data came through sorted back into the boxes just fine. So say I have a model:
public class Computer { public string Name { get; set; } }
, to where I wanted alphabetizedName
values, and I had anObservableCollection<Computer> myList
, then it was:myList.Sort((a, b) => { return a.Name.CompareTo(b.Name); });
. Remember when adding the method Marco provided that it goes in apublic static class Extensions { ... }
in your namespace. -
vapcguy over 7 years@Travis Not bad-it does work to get rid of the error, but in WPF, where you have to bind to the
ObservableCollection
, I tried this and it stopped recognizing my collection-it stopped allowing me to add things to the collection and have the changes reflect in the GUI. Binding broke. Probably better would be to assign the sorted collection to another one, then add it back asObservableCollection<Animal>
and notIOrderedEnumerable<T>
toanimals
, as it will need to be converted from. I went with Marco's solution, instead, though -
Tim Pohlmann over 7 yearsYou should add a check for equality of indices in your sort algorithm or you might run into problems: stackoverflow.com/questions/42204898/…
-
Tim Pohlmann over 7 yearsI proposed an edit to this answer which would fix the issue but apparently people thought this would change content of the answer to much.
-
Christian Findlay over 6 yearsCollectionViewSource should be used wherever is possible but Xamarin Forms does not have this so it is not possible. The second solution of using OrderBy is not feasible, because the assumption is that the UI is listening to the observable collection because it implement INotifyCollectionChanged. If you create a new instance, what's the point? The UI will do a full refresh.
-
Thom Hubers over 5 yearsDescending seems to be implemented reverse. Wonder how the test did the right output.
-
uzrgm about 5 yearsGood solution, works great in the real time! Few comments: Descending is reversed (need to fix); to whom don't want to install Nuget for Tuples just replace "(Item: item, Index: index));" -> "new { Index = index, Item = item });" and "(OldIndex: tuple.Index, NewIndex: index))" -> "new { OldIndex = tuple.Index, NewIndex = index })".
-
b.pell about 5 yearsThe equality check was necessary for me to use this without it crashing. If sortableList[i] == i then it would throw an exception. Adding "if (collection.IndexOf(sortableList[i]) != i)" before the collection.Move fixed that issue and the collection sorted correctly.
-
Quarkly about 5 yearsThere is no future for the CollectionViewSource in UWP. This is a dead-end pattern.
-
Quarkly about 5 yearsThis is far superior to the other solutions. #1 it doesn't break the bindings, #2, it doesn't rely on the CollectionViewSource which is an obsolete pattern, #3 it's simple, #4 it's blindingly fast compared to the 'Move Items Around' solution.
-
Quarkly about 5 yearsToo damned slow for any reasonable data set.
-
Quarkly about 5 yearsToo damned slow for any reasonably sized data set, even with the optimizations.
-
Tim Pohlmann about 5 years@DonaldAirey fair, did you find a better solution?
-
Quarkly about 5 yearsYes, the solution by John Leone worked in about 15 ms where this one took 600 ms.
-
Marco about 5 yearsIf you need to handle reordering over big data set, ObservableCollection may not be the best choice (when you need to keep the bindings alive). A CollectionViewSource is probably better suited for that...
-
Tim Pohlmann about 5 years@DonaldAirey cool. Does removing and re-adding all items have any unwanted side-effects?
-
Tim Pohlmann about 5 yearsWhile this seems to be way faster than moving all items around, it also triggers different events (remove and add instead of move). This might not be an issue depending on the use-case but is definitely a thing to keep in mind.
-
Tim Pohlmann about 5 yearsWhile this seems to be way faster than moving all items around, it also triggers different events (remove and add instead of move). This might not be an issue depending on the use-case but is definitely a thing to keep in mind.
-
Quarkly about 5 yearsI've got a fairly complex MVVM trading blotter with 500 rows in it. Virtualization, lots of binding to the underlying view model. The only difference I've noticed is one sorts instantly, the other has a noticeable lag.
-
Quarkly about 5 years@TimPohlmann - The Move triggers events as well, so the difference is O operations with the 'Move' algorithm vs O + 1 operations with the clear and add algorithm.
-
Quarkly about 5 yearsI can't stress this enough: There is no future in the CollectionViewSource. It isn't supported in UWP and you will be stuck with an obsolete design if you depend on it now. Having been burned on this exact issue, stick with the ObservableCollection if you want to port your code to Windows 10 someday in the future.
-
Tim Pohlmann about 5 years@DonaldAirey I was talking about the fact that the events are being triggered with different event arg contents. Depending on what you event handlers are listening for, this could cause some issues.
-
Simon Mourier almost 5 yearsNice. If the collection is always sorted, couldn't we skip on the
Remove
notification as well? -
gusmally supports Monica almost 5 yearsCollectionViewSources also don't allow you to specify exactly when to sort the list, from what i've seen. In my case, we don't want to sort the list until the user isn't looking at it anymore
-
nawfal almost 4 years@Quarkly while this approach is slower, this seems to me as more correct. When you do a sort operation like John's answer, it raises a Reset event and then a bunch of Adds. While this raises Move events..
-
nawfal almost 4 yearsBut this sort is unstable. I would assume these are problematic in UI contexts (which is were such bindable collections are mostly used). John's answer is stable.
-
Tim Pohlmann almost 4 years@nawfal what do you mean with unstable?
-
nawfal almost 4 years@TimPohlmann List<T>.Sort is unstable. See en.wikipedia.org/wiki/Category:Stable_sorts for more
-
dgellow about 3 years@Quarkly What's the recommended solution for UWP?
-
Quarkly about 3 yearsI've been using LINQ to sort the observable collections. For animation, I've actually gone the extra step to perform a diff on the old collection order vs the new collection order and then perform the minimal RemoveAt/InsertAt operations to place them in the right order. It's a lot of extra work, but it gives the animation a much more natural look.
-
Roman over 2 yearsGreat solution, thanks!
-
Weissu over 2 yearsThis is really genius solution, I love it!