Click here to Skip to main content
Click here to Skip to main content

Sorting an Observable Collection using the View Model from the MVVM Pattern

, 7 Apr 2011
Rate this:
Please Sign up or sign in to vote.
Shows how to allow sorting of controls (e.g. a Listview) that are bound to an ObservableCollection using the MVVM (model, view, view model) pattern and the sorting takes place in the view model.

Introduction

The purpose of this article is to show how to allow sorting of controls (e.g. a Listview) that are bound to an ObservableCollection using the MVVM (model, view, view model) pattern and the sorting takes place in the view model.

Background

The background behind this article is that I had a requirement to add sorting to a listview that was data bound to an ObservableCollection. The main issue I found was that ObservableCollection is an unordered list and you can't sort it. I struggled to find an article to help achieve this within an MVVM patterned framework, in order that the sorting was done in the view model as opposed to in code behind. This would therefore be more testable and separates my concerns more cleanly.

In order to help anyone else who may have the same problem in the future, I thought I'd write up how I achieved this requirement in an MVVM environment.

The sample application for this article is a list of people which you will be able to sort by either first name or last name.

So to illustrate, we have an unsorted list as below:

UnSorted.png

By clicking on the first name header, it sorts it ascending as shown:

SortedFirstAscending.png

Then we click first name again and it resorts it descending as shown:

SortedFirstDescending.png

Out of scope for this article is to explain what the MVVM pattern is and any frameworks used to implement MVVM in a more fuller implementation. For more information on MVVM, there are some very good articles on this site that explain both MVVM as an overview and frameworks that can be used when developing full applications.

Also out of scope is the way in which the test data is loaded in the application. This data should be loaded from a database, file or entered into the application. The way in which this is done in the code is not best practices but only used to illustrate the sorting element.

Test for the Sort

To start off, here is the test that shows how the sort will be done.

All of the test code can be found in the ObservableCollectionSortingExample.Test project.

1. using Microsoft.VisualStudio.TestTools.UnitTesting;
2. 
3. namespace ObservableCollectionSortingExample.Test
4. {
5.     [TestClass]
6.     public class SortingTests
7.     {
8.         PeopleVewModel viewModel;
9.         Person person1;
10.         Person person2;
11.         Person person3;
12.         Person person4;
13.         Person person5;
14.         Person person6;
15. 
16.         [TestInitializeAttribute]
17.         public void InitialiseViewModel()
18.         {
19.             viewModel = new PeopleVewModel();
20. 
21.             People people = new People();
22.             person1 = new Person() { Firstname = "Michael", Lastname = "Bookatz" };
23.             people.Add(person1);
24.             person2 = new Person() { Firstname = "Chris", Lastname = "Johnson" };
25.             people.Add(person2);
26.             person3 = new Person() { Firstname = "John", Lastname = "Doe" };
27.             people.Add(person3);
28.             person4 = new Person() { Firstname = "Ann", Lastname = "Other" };
29.             people.Add(person4);
30.             person5 = new Person() { Firstname = "Jack", Lastname = "Smith" };
31.             people.Add(person5);
32.             person6 = new Person() { Firstname = "Charles", Lastname = "Langford" };
33.             people.Add(person6);
34. 
35.             viewModel.People = people;
36.         }
37. 
38.         [TestMethod]
39.         public void SortByFirstname()
40.         {
41.             viewModel.SortList.Execute("Firstname");
42. 
43.             Assert.IsTrue(viewModel.PeopleView.Count == 6);
44.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(0)) == person4);
45.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(1)) == person6);
46.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(2)) == person2);
47.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(3)) == person5);
48.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(4)) == person3);
49.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(5)) == person1);
50. 
51.             viewModel.SortList.Execute("Firstname");
52. 
53.             Assert.IsTrue(viewModel.PeopleView.Count == 6);
54.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(5)) == person4);
55.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(4)) == person6);
56.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(3)) == person2);
57.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(2)) == person5);
58.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(1)) == person3);
59.             Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(0)) == person1);
60.         }
61.     }
62. }

Lines 8 to 14 are the declaration for the fields that will be used in the test for the objects that represent the view model and a list of people to be tested against.

On line 11, we create the object that is going to do the loop iteration. This is a generic type so that it can be used to test all different classes.

On lines 16 to 36, the test initialisation is run. This sets up the objects for the model and then creates the view model that the test will be run against.

Line 39 starts the actual test method. Line 41 and 51 represent the list being sorted. The first set of assert on lines 43 to 49 makes sure that after the first sorting by first name, the order of the list is in the correct order of ascending first name. We reverse the list on line 51 and then check on Lines 53 to 50 that the order has been reversed.

One of the important points to note in the test is that property from the view model used to obtain the Person to compare to the expected person isn't an ObservableCollection but a ListCollectionView. This is because an ObservableCollection is an unsorted list of items. The way round sorting an ObservableCollection (and also applying grouping and filtering) is to use a class that implements ICollectionView which ListCollectionView does.

For the sake of completeness, below is the test for sorting by last name:

1. [TestMethod]
2. public void SortByLastname()
3. {
4.     viewModel.SortList.Execute("Lastname");
5. 
6.     Assert.IsTrue(viewModel.PeopleView.Count == 6);
7.     Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(0)) == person1);
8.     Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(1)) == person3);
9.     Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(2)) == person2);
10.    Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(3)) == person6);
11.    Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(4)) == person4);
12.    Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(5)) == person5);
13. 
14.    viewModel.SortList.Execute("Lastname");
15. 
16.    Assert.IsTrue(viewModel.PeopleView.Count == 6);
17.    Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(5)) == person1);
18.    Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(4)) == person3);
19.    Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(3)) == person2);
20.    Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(2)) == person6);
21.    Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(1)) == person4);
22.    Assert.IsTrue(((Person)viewModel.PeopleView.GetItemAt(0)) == person5);
23. }

This is the same as the test above with the only changes being on line 4 and 14 where you sort by last name instead of first name.

Model

All the code for the Models can be found in ObservableCollectionSortingExample.Model.

The models used for this are very simple. All the person class does is define two string properties first name and last name and then equality overrides so we can test that two person objects are equal in the tests. The code is:

1. public class Person
2. {
3.     public string Firstname { get; set; }
4. 
5.     public string Lastname { get; set; }
6. 
7.     public override bool Equals(object obj)
8.     {
9.         if (obj == null || GetType() != obj.GetType())
10.         {
11.             return false;
12.         }
13. 
14.         Person other = obj as Person;
15. 
16.         if (this.Firstname != other.Firstname)
17.             return false;
18. 
19.         if (this.Lastname != other.Lastname)
20.             return false;
21. 
22.         return true;
23.     }
24. 
25.     public override int GetHashCode()
26.     {
27.         return Firstname.GetHashCode() ^ Lastname.GetHashCode();
28.     }
29. 
30.     public static bool operator ==(Person person1, Person person2)
31.     {
32.         if (Object.Equals(person1, null) && Object.Equals(person2, null))
33.         {
34.             return true;
35.         }
36.         return person1.Equals(person2);
37.     }
38. 
39.     public static bool operator !=(Person person1, Person person2)
40.     {
41.         return !(person1 == person2);
42.     }
43. }

Nothing particularly out of the ordinary here.

All the people class is is a specialisation of ObservableCollection class with Person as the type. The code is:

1. public class People : ObservableCollection<person>
2. {
3. 
4. }
</person>

I picked ObservableCollection as the collection type as I know this will be used in a display. It made sense to use ObservableCollection which has all the benefits of allowing you to databind to it, rather than have to copy from a different type collection into ObservableCollection later on in the program.

View Model

All the code for the view model can be found in the ObservableCollectionSortingExample project.

As we have now seen the tests and the models, let's look at the view model that will pass the tests above.

Below is part of the code from the view model. This is the property to set a list of people to be used by the view.

1. People observerablePeople = new People();
2. CollectionViewSource peopleView;
3.        
4. public People People
5. {
6.      private get
7.     {
8.         return this.observerablePeople;
9.     }
10.      set
11.     {
12.          this.observerablePeople = value;
13.          peopleView = new CollectionViewSource();
14.          peopleView.Source = this.observerablePeople;
15.     }
16. }

There are a few important parts of code to notice. First is that the get on line 6 is private. This is because for the ObservableCollection to be sortable, you need to bind to a view of the collection. So as to prevent binding to the underlying collection instead, the get is made private.

The set is used for the ObservableCollection that is the underlying data the display is based on. As part of the set, you also need to update the ObservableCollection view that will be used by the view to display the data. If you don't update the view by creating a new CollectionViewSource, then it will point to the original ObservableCollection and therefore display the incorrect information if the Observable collection changes.

The next part is the code that is the property that allows you to get the view onto the ObservableCollection.

1. public ListCollectionView PeopleView
2. {
3.     get
4.     {                
5.         return (ListCollectionView) peopleView.View;
6.     }
7. }

All this does is return the view onto the ObservableCollection that will be used by the View. We return a ListCollectionView instead of a CollectionView as the ListCollectionView offers better performance and the View property of CollectionViewSource only returns an interface. Plus as we know we are using an ObservableCollection then it makes sense to use the more specific class of ListCollectionView rather then CollectionView which is more generic.

The next part of the class is just a command property that is used by the Command Binding in the WPF to execute the sorting. Line 9 is where the Command has a method assigned to an event that is called when the command is executed.

1. private CommandStub sortList;
2. public ICommand SortList
3. {
4.     get
5.     {
6.         if (sortList == null)
7.         {
8.             sortList = new CommandStub();
9.             sortList.OnExecuting += 
		new CommandStub.ExecutingEventHandler(sortList_OnExecuting);
10.        }
11.        return sortList;
12.    }
13.}

The code below is the actual method that is called by the command set up above. As you can see, the actual code to do the sorting is quite simple.

1.         void sortList_OnExecuting(object parameter)
2.         {
3.             string sortColumn = (string)parameter;
4.             this.peopleView.SortDescriptions.Clear();
5. 
6.             if (this.sortAscending)
7.             {
8.                 this.peopleView.SortDescriptions.Add
		(new SortDescription(sortColumn, ListSortDirection.Ascending));
9.                 this.sortAscending = false;
10.             }
11.             else
12.             {
13.                 this.peopleView.SortDescriptions.Add
		(new SortDescription(sortColumn, ListSortDirection.Descending));
14.                 this.sortAscending = true;
15.             }
16.         }

Line 3 works out the name of the column that is to be sorted. We then clear the current sorting in line 4.

Lines 6 to 15 performs the actual sorting. There is a flag set to determine if the sort order should be Ascending or Descending and then the correct view depending on ascending or descending is added to the peopleView in lines 8 or 13. The next line after this then toggles the sortAscending flag.

And that is all there is to the view model.

To sum up, all you need to do is make sure your view binds to a ListCollectionView on to the ObservableCollection and then add sorting to the view.

View

All the code for the view can be found in the ObservableCollectionSortingExample project.

The view is defined in XAML in the file PeopleView.xaml. I am going to assume that you have a basic familiarity with XAML, so I am not going to cover it here. For more information on XAML, you can find excellent resources online both at this site and elsewhere.

The XAML for the view is:

 1. <Window x:Class="ObservableCollectionSortingExample.PeopleView"
2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4. Title="SortingExample"Height="350"Width="200"
5. xmlns:local="clr-namespace:ObservableCollectionSortingExample">
6.   <Window.Resources>
7.     <local:PeopleVewModelx:Key="PeopleViewDataContext"></local:PeopleVewModel>
8.   </Window.Resources>
9.   <Grid DataContext="{StaticResourcePeopleViewDataContext}">
10.    <Grid.RowDefinitions>
11.      <RowDefinitionHeight="*"/>
12.    </Grid.RowDefinitions>
13.    <ListView HorizontalAlignment="Stretch" Margin="10,10,10,10" Name="ListOfName"
14.       VerticalAlignment="Top" ItemsSource="{BindingPath=PeopleView}" 
		HorizontalContentAlignment="Center">
15.    <ListView.View>
16.    <GridView>
17.      <GridViewColumn DisplayMemberBinding="{BindingPath=Firstname}">
18.        <GridViewColumnHeader Command="{BindingSortList}" 
		CommandParameter="Firstname"> Firstname</GridViewColumnHeader>
19.      </GridViewColumn>
20.      <GridViewColumn DisplayMemberBinding="{BindingPath=Lastname}">
21.        <GridViewColumnHeader Command="{BindingSortList}" 
		CommandParameter="Lastname"> Lastname</GridViewColumnHeader>
22.      </GridViewColumn>
23.    </GridView>
24.    </ListView.View>
25.    </ListView>
26.   </Grid>
27. </Window>

To pick up some of the important lines in this XAML is as follows. Line 7 sets up the link for this view to the view model as a resource in XAML which can then be used in the rest of the XAML.

The Data context is set up in line 9 so that all of the other controls in the control can access the view.

Now comes the real magic in line 14. The ItemSource is set to the PeopleView in the view model. This as you will remember is the view on to the underlying ObservableCollection list. The Binding on the actual columns of the listview is the same as if you where binding to an ObservableCollection as can be seen in line 17 and 20.

The command binding on lines 18 and 21 is where we bind to the SortList Command from the view model. We pass in the name of the column that will have the command action done to it. This is the parameter that is passed into the sortList_OnExecuting(object parameter) method in the view model. This parameter is used to know which column to sort by.

Conclusion

So as you can see from the example above, the key part to sorting is to make sure you bind to a ListCollectionView on to the ObservableCollection rather than onto the ObservableCollection itself.

History

  • 7th April, 2011: Initial post  

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Michael Bookatz
Software Developer
United Kingdom United Kingdom
I'm a software developer with 4 years experience of using C# and Microsoft SQL server.
 
I work on line of business desktop applications that cross the entire development stack with the full development life cycle.

Comments and Discussions

 
QuestionSorting? PinmemberClski6-May-14 0:24 
QuestionHow to I turn the Sorting Function off from the collection? [modified] PinmemberMember 378316128-Dec-12 15:57 
Generalmy vote of 5, good article Pinmembernet_201019-Oct-12 2:22 
QuestionSeeing as most listviews aren't static, how would you add a Set parameter to your PeopleView property? PinmemberBob Brady12-Sep-12 5:54 
GeneralMy vote of 5 PinmemberPeterJerz1-Aug-12 1:16 
GeneralVery good explanation! PinmemberPeterJerz1-Aug-12 1:15 
SuggestionThe code involving simple sorting in CollectionViewSource PinmemberCodekeeping30-Nov-11 11:05 
GeneralMy vote of 4 PinmemberPhilippe Devaux11-Apr-11 19:49 
GeneralRe: My vote of 4 PinmemberMichael Bookatz14-Apr-11 10:46 
QuestionSample Code PinmemberJani Giannoudis6-Apr-11 20:14 
AnswerRe: Sample Code PinmemberMichael Bookatz7-Apr-11 0:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140902.1 | Last Updated 7 Apr 2011
Article Copyright 2011 by Michael Bookatz
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid