Sort Observablecollection<String> Through C#

Sort ObservableCollection string 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}}" >

Dynamically sorting an ObservableCollection object 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);

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

Sorting ObservableCollection by String

To be able to retrieve your conditions, I did the below.

 // Retrieve an IQueryable for the colleciton with your specified conditions
var query = from c in collection
orderby c.IsChecked descending, c.Tag.Equals("cherry") descending, c.obsTag
select c;

// Clear the collection
collection = new ObservableCollection<myCollectionObject>();
// Replace the collection with your IQueryable results
foreach(myCollectionObject obj in query) {
collection.Add(obj);
}

If you want it all in one line:

 collection = new ObservableCollection<obsCol>(from c in collection 
orderby c.obsCheck descending, c.obsTag.Equals("cherry") descending, c.obsTag
select c);

Order a ObservableCollection T without creating a new one

Given that OrderBy also news up an array to match the size of your collection, and several other objects, you've two choices:

  1. Give up on LINQ OrderBy altogether and write your own sort that performs in-place sorting over your ObservableCollection using the Move method.
  2. Wait until the current implementation becomes problematic then apply 1.

Don't worry, newing stuff up isn't so terrible. Linq does it all the time. Unless it's a bottleneck, all is good. Unless there's compelling evidence that sorting in-place will really speed up the show, then there's no problem here.

Sorting ObservableCollection

You can sort the view of the collection rather that sorting the collection itself:

// xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
<myView.Resources>
<CollectionViewSource x:Key="ItemListViewSource" Source="{Binding Itemlist}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="SortingProperty" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</myView.Resources>

And then you can use the CollectionViewSource as ItemSource:

ItemsSource="{Binding Source={StaticResource ItemListViewSource}}"

Sorting ObservableCollection Class data in ListBox

OrderBy will return a IOrderedEnumerable<T> which is sorted according to the sort key. The original collection remains in its original state. You therefore need to overwrite the original collection with the sorted result collection:

this.LeftListBoxItems = new ObservableCollection<GraphViewModel>(this.LeftListBoxItems.OrderBy(value => value.ID))

To have the ObservableCollection sort automatically when adding/removing items, you can configure it once (e.g., from the constructor) by adding a SortDescription to the ICollectionView:

CollectionViewSource.GetDefaultView(this.LeftListBoxItems)
.SortDescriptions
.Add(new SortDescription(nameof(GraphViewModel.ID), ListSortDirection.Ascending));

Note
WPF is binding to the ICollectionView of a collection rather than to the collection directly. Setting the SortDescription on a ICollectionView will only sort this ICollectionView. This means the UI appears sorted whereas the underlying collection remains unsorted (or sorted by different rules).



Related Topics



Leave a reply



Submit