How to Sort an Observable Collection

Sort ObservableCollectionstring through C#

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:

  • How to: Sort and Group Data Using a View in XAML.
  • WPF’s CollectionViewSource.

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.

How do I sort an observable collection?

Sorting an observable and returning the same object sorted can be done using an extension method. For larger collections watch out for the number of collection changed notifications.

I have updated my code to improve performance (thanks to nawfal) and to handle duplicates which no other answers here do at time of writing. The observable is partitioned into a left sorted half and a right unsorted half, where each time the minimum item (as found in the sorted list) is shifted to the end of the sorted partition from the unsorted. Worst case O(n). Essentially a selection sort (See below for output).

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
}

usage:
Sample with an observer (used a Person class to keep it 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();
};

Details of sorting progress showing how the collection is pivoted:

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,

The Person class implements both IComparable and IEquatable the latter is used to minimise the changes to the collection so as to reduce the number of change notifications raised

  • EDIT Sorts same collection without creating a new copy *


To return an ObservableCollection, call .ToObservableCollection on *sortedOC* using e.g. [this implementation][1].

**** orig answer - this creates a new collection ****
You can use linq as the doSort method below illustrates. A quick code snippet: produces

3:xey
6:fty
7:aaa

Alternatively you could use an extension method on the collection itself

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

How to sort ObservableCollection alphabetically?

bind your ObservableCollection to a CollectionViewsource, add a sort
on it, then use that CollectionViewSource as the ItemSource of a
Control.

Use CollectionViewSource to Sort

<CollectionViewSource x:Key='src' Source="{Binding ob}">
<CollectionViewSource.SortDescriptions>
<componentModel:SortDescription PropertyName="Name" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>

Sample usage

<ListView ItemsSource="{Binding Source={StaticResource src}}" >

How can I sort ObservableCollection?

You just need to create a new instance of it.

Persons = new ObservableCollection<Person>(from i in Persons orderby i.Age select i);

Sort ObservableCollection according to its property

Here's three common ways for sorting from your code.

  1. Manually apply the Sort anytime you add items. I would suggest linq for the actual sorting, although you can build your own. In your specific case, it would look like this :

    MyItems.Add(new MyCustomItemViewModel("Apple", windowBViewModel));
    MyItems.Add(new MyCustomItemViewModel("Orange", windowBViewModel));
    MyItems.Add(new MyCustomItemViewModel("Banana", windowBViewModel));

    MyItems = MyItems.Sort(p => p.ItemName);

    This assumes you enable the setter so MyItems is not readonly.

  2. In the getter for MyItems, return the collection sorted (and use a backing property for it). This is not really ideal, since you build the sorted collection for each call that is made to the getter. For your code, it would look like this :

    public class BaseWindowViewModel
    {
    public string PageTitle { get; set; }

    private ObservableCollection<MyCustomItemViewModel> _myItems = new ObservableCollection<MyCustomItemViewModel>()
    public ObservableCollection<MyCustomItemViewModel> MyItems
    {
    get { return _myItems.Sort(p => p.ItemName); }
    }
    }
  3. (Recommended) Work with a CollectionViewSource that sits on top of your collection and applies UI-friendly operations like sorting and filtering.

    public class BaseWindowViewModel
    {
    public string PageTitle { get; set; }

    private ObservableCollection<MyCustomItemViewModel> _myItems;
    public ObservableCollection<MyCustomItemViewModel> MyItems
    {
    get
    {
    if (_myItems == null)
    {
    _myItems = new ObservableCollection<MyCustomItemViewModel>();
    _myItemsSorted = CollectionViewSource.GetDefaultView(_myItems)
    _myItemsSorted.SortDescriptions.Add(new SortDescription() { PropertyName = "ItemName" });
    }
    return _myItems;
    }
    }

    private ICollectionView _myItemsSorted;
    public ICollectionView MyItemsSorted { get { return _myItemsSorted; }}

    }

    And then just bind to MyItemsSorted rather than MyItems

UWP/C#: ObservableCollection sort in-place (w/o scrolling)

What you can try is to create a temp collection that contains all the items from your original collection, sort it, then loop through its items and only re-order the ones of which position needs to be updated. Something like this -

public static void Sort<TSource, TKey>(this ObservableCollection<TSource> source, Func<TSource, TKey> keySelector)
{
var sortedSource = source.OrderBy(keySelector).ToList();

for (var i = 0; i < sortedSource.Count; i++)
{
var itemToSort = sortedSource[i];

// If the item is already at the right position, leave it and continue.
if (source.IndexOf(itemToSort) == i)
{
continue;
}

source.Remove(itemToSort);
source.Insert(i, itemToSort);
}
}

Also, you will want the ListView to keep the scroll offset when items are animating. This can be done by setting -

<ItemsPanelTemplate>
<ItemsStackPanel ItemsUpdatingScrollMode="KeepScrollOffset" />
</ItemsPanelTemplate>

I found this UX related question really interesting and I even ended up creating a little demo project for it. :) The gif below demostrates the end result. To me it provides a better experience as I know visually, what items are or aren't repositioned by the sorting.

ListView sorting animation

Dynamically sorting an ObservableCollectionobject when the column is a string

So here is what I ended up doing to solve the problem. In the object, I concatenated the types

public class TestObject{
public string type1{get; set;}
public string type2{get; set;}
public string type3{get; set;}
public string types{get;set;}
}

and in the method where the TestObjects are being populated, I added this in:

public ObservableCollection<TestObject> TestObjects = new ObservableCollection<TestObject>();
TestObjects.Add(new TestObject('708','4','(A)','708.4.(A)'));
TestObjects.Add(new TestObject('7','41B','1(A)','7.41B.1(A)'));
TestObjects.Add(new TestObject('69','2','45', '69.2.45'));
TestObjects.Add(new TestObject('708','4','(B)', '708.4.(B)'));
TestObjects.Add(new TestObject('69','2','5', '69.2.5'));
TestObjects.Add(new TestObject('7','41','1(B)', '7.41.1(B)'));
TestObjects.Add(new TestObject('7','41','', '7.41.'));
TestObjects= SortObjects(TestObjects);

Next, I created a custom comparison method in the ViewModel, using the "Shlwapi.dll':

   [DllImport("Shlwapi.dll", Charset = Charset.Unicode)]
private static extern int StrCmpLogicalW(string x, string y);

public ObservableCollection<TestObject> SortObjects(ObservableCollection<TestObject> unsortedObjects){
var sortedCollection = new ObservableCollection<TestObject>();
var objectList = new List<TestObject>();
foreach(var obj in unsortedObjects){
objectList.Add(obj);
}
Comparison<TestObject> typesComp = (a,b) =>{
var aKey = a.types;
var bKey = b.types;
return StrCmpLogicalW(aKey,bKey);
}
objectList.Sort(typesComp);
foreach(var obj in objectList){
sortedCollection.Add(obj);
}
return sortedCollection;
}

This method allows me to return the TestObjects collection in ascending order. Still working on descending order switch, and will update this when I get it.

UPDATE: Here is the SortObjects method with the reversal ability

   public ObservableCollection<TestObject> SortObjects(ObservableCollection<TestObject> unsortedObjects, bool IsAscend){
var sortedCollection = new ObservableCollection<TestObject>();
var objectList = new List<TestObject>();
foreach(var obj in unsortedObjects){
objectList.Add(obj);
}
Comparison<TestObject> typesComp = (a,b) =>{
var aKey = a.types;
var bKey = b.types;
return StrCmpLogicalW(aKey,bKey);
}
objectList.Sort(typesComp);
if(!isAscend){
objectsList.Reverse();
}
foreach(var obj in objectList){
sortedCollection.Add(obj);
}
return sortedCollection;
}

And then it is called passing in a true / false parameter like this for ascending:

 TestObjects = SortObjects(TestObjects, true);

and like this for descending order:

 TestObjects = SortObjects(TestObjects, false);

Sorting an ObservableCollection of Objects by Properties in Xamarin Forms

Well first of all i don't think your property should be like

ObservableCollection<NameObject> _nameList {get; } = MainPage.NameList;

Since you have to made changes in it later i would suggest you have get as well as set in your property(Since you might have to use it in some other class):

ObservableCollection<NameObject> _nameList {get; set; } = MainPage.NameList;

Then to set your order you do it something like this:

 public YourFullNamesListPage()
{
InitializeComponent();
_nameList = new ObservableCollection<NameObject>(_nameList.OrderByDescending(x => x.Name));
FullNamesList.ItemsSource= _nameList;
}

Where the below is of the type System.Linq.IOrderedEnumerable<T> and hence you need to convert it to an observable collection by wrapping it into one.

_nameList.OrderByDescending(x => x.Name)

And then you are good to go
Revert in case of queries.



Related Topics



Leave a reply



Submit