Sort ObservableCollection<string> through C#

158,518

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:

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

Share:
158,518
Bishan
Author by

Bishan

#SOreadytohelp

Updated on July 15, 2022

Comments

  • Bishan
    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
    Sergey Vyacheslavovich Brunov almost 11 years
    Please note that the order of items in collection is not modified after the OrderByDescending() method call.
  • Sergey Vyacheslavovich Brunov
    Sergey Vyacheslavovich Brunov almost 11 years
    Please note that the order of items in collection is not modified after the OrderByDescending() method call.
  • manji
    manji almost 11 years
    It's not working due to what @Sergey Brunov explained. You need to save the result in another variable.
  • Bram Van Strydonck
    Bram Van Strydonck almost 11 years
    @SergeyBrunov: Exactly: you should say: animals = animals.OrderByDescending(a => a.Name);
  • poudigne
    poudigne about 8 years
    I get a Cannot convert ObservableCollection to OrderedObservableCollection error.
  • some_name
    some_name about 8 years
    Works great. I was just looking for a solution that won't break the binding, thanks Marco.
  • M. Pipal
    M. Pipal about 8 years
    This 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
    Tom about 8 years
    Note: 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
    Tom about 8 years
    If 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
    Marco almost 8 years
    Bindings 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
    Travis almost 8 years
    animals = new ObservableCollection<Animal>(animals.OrderByDescending(a => a.Name)); This will get around the IOrderedEnumerable error.
  • vapcguy
    vapcguy over 7 years
    I 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 alphabetized Name values, and I had an ObservableCollection<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 a public static class Extensions { ... } in your namespace.
  • vapcguy
    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 as ObservableCollection<Animal> and not IOrderedEnumerable<T> to animals, as it will need to be converted from. I went with Marco's solution, instead, though
  • Tim Pohlmann
    Tim Pohlmann over 7 years
    You should add a check for equality of indices in your sort algorithm or you might run into problems: stackoverflow.com/questions/42204898/…
  • Tim Pohlmann
    Tim Pohlmann over 7 years
    I 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
    Christian Findlay over 6 years
    CollectionViewSource 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
    Thom Hubers over 5 years
    Descending seems to be implemented reverse. Wonder how the test did the right output.
  • uzrgm
    uzrgm about 5 years
    Good 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
    b.pell about 5 years
    The 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
    Quarkly about 5 years
    There is no future for the CollectionViewSource in UWP. This is a dead-end pattern.
  • Quarkly
    Quarkly about 5 years
    This 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
    Quarkly about 5 years
    Too damned slow for any reasonable data set.
  • Quarkly
    Quarkly about 5 years
    Too damned slow for any reasonably sized data set, even with the optimizations.
  • Tim Pohlmann
    Tim Pohlmann about 5 years
    @DonaldAirey fair, did you find a better solution?
  • Quarkly
    Quarkly about 5 years
    Yes, the solution by John Leone worked in about 15 ms where this one took 600 ms.
  • Marco
    Marco about 5 years
    If 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
    Tim Pohlmann about 5 years
    @DonaldAirey cool. Does removing and re-adding all items have any unwanted side-effects?
  • Tim Pohlmann
    Tim Pohlmann about 5 years
    While 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
    Tim Pohlmann about 5 years
    While 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
    Quarkly about 5 years
    I'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
    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
    Quarkly about 5 years
    I 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
    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
    Simon Mourier almost 5 years
    Nice. If the collection is always sorted, couldn't we skip on the Remove notification as well?
  • gusmally supports Monica
    gusmally supports Monica almost 5 years
    CollectionViewSources 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
    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
    nawfal almost 4 years
    But 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
    Tim Pohlmann almost 4 years
    @nawfal what do you mean with unstable?
  • nawfal
    nawfal almost 4 years
    @TimPohlmann List<T>.Sort is unstable. See en.wikipedia.org/wiki/Category:Stable_sorts for more
  • dgellow
    dgellow about 3 years
    @Quarkly What's the recommended solution for UWP?
  • Quarkly
    Quarkly about 3 years
    I'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
    Roman over 2 years
    Great solution, thanks!
  • Weissu
    Weissu over 2 years
    This is really genius solution, I love it!