Before we get to our little exercise in Xaml fun I promised in my
last post, I want to solve a problem that's already been solved. Sounds like fun eh?
The ListView in WPF is a great control and long-term readers will know
I'm a big fan of Winfows.Form's ListView. The one thing
everybody wants from their ListView control is sorting.
Fortunately, the guys at MSDN have this covered and have a nice concise article covering just how to do this:
How to: Sort a GridView Column When a Header Is Clicked. Nice.
However, if you read the article I expect, like me, your object oriented spider sense* will start to tingle. All the code for the sorting finds its way into your Window or Pages's code-behind and needs member variables. What if I want multiple ListViews? It's all going to get very untidy!
However, there is a very simple and much more elegant solution within easy grasp.
Extending an existing control
The header above should give you a clue to what we're going to do here.
public class SortableListView : ListView
{
Easy start eh? We create a new 'SortableListView' by deriving from the existing ListView to get all that juicy functionality without any effort - we just want to extend it.
In order to support sorting, we need our new implementation to be notified whenever a nested Column Header is clicked. Let's wire up a RoutedEventHandler in the constructor.
public SortableListView()
{
// register a handler for the GridViewColumnHeader's Click Event
this.AddHandler(
GridViewColumnHeader.ClickEvent,
new RoutedEventHandler(GridViewColumnHeaderClickedHandler));
}
The actual delegate 'GridViewColumnHeaderClickedHandler' is pretty much taken exactly from that MSDN article with one main difference which we'll come to later. We also steal their 'Sort' method and make two little changes...
private void Sort(string sortBy, ListSortDirection direction)
{
// refer to our *OWN* ItemsSource
ICollectionView dataView = CollectionViewSource.GetDefaultView(this.ItemsSource);
// check the dataView isn't null
if (dataView != null)
{
dataView.SortDescriptions.Clear();
SortDescription sd = new SortDescription(sortBy, direction);
dataView.SortDescriptions.Add(sd);
dataView.Refresh();
}
}
First, we reference our own ItemsSource rather than that of the specified ListView control, called 'lv' in the example. Secondly, we check that the dataView isn't null to avoid an error when the ListView is empty.
And we're done...
But it won't work!
....well, not quite. There is another problem with the example code (and it is just an example, so fair enough!). If the Header of your GridViewColumn does not exactly match the member name then sorting won't work.
This is likely to be a common requirement as we don't want to show headers like 'MemberName' in the UI, much better that we can specify this independently.
Step up, attached properties
What we want to do here, is add an expando attribute to GridViewColumn to allow us to specify the SortPropertyName.
Mike Hillberg has a great post on
creating attached properties so I'll just show you the code to create one.
public static readonly DependencyProperty SortPropertyNameProperty =
DependencyProperty.RegisterAttached("SortPropertyName", typeof(string), typeof(SortableListView));
public static string GetSortPropertyName(GridViewColumn obj)
{
return (string)obj.GetValue(SortPropertyNameProperty);
}
public static void SetSortPropertyName(GridViewColumn obj, string value)
{
obj.SetValue(SortPropertyNameProperty, value);
}
Finally, let's take a look at that GridViewColumnHeaderClickedHandler method we mostly stole from the MSDN article:
private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
{
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
ListSortDirection direction;
if (headerClicked != null &&
headerClicked.Role != GridViewColumnHeaderRole.Padding)
{
if (headerClicked != _lastHeaderClicked)
{
direction = ListSortDirection.Ascending;
}
else
{
if (_lastDirection == ListSortDirection.Ascending)
{
direction = ListSortDirection.Descending;
}
else
{
direction = ListSortDirection.Ascending;
}
}
// see if we have an attached SortPropertyName value
string sortBy = GetSortPropertyName(headerClicked.Column);
if (string.IsNullOrEmpty(sortBy))
{
// otherwise use the column header name
sortBy = headerClicked.Column.Header as string;
}
Sort(sortBy, direction);
_lastHeaderClicked = headerClicked;
_lastDirection = direction;
}
}
We've used the GetSortPropertyName to see if the clicked column has a SortPropertyName, if not we just fallback to using the Header just like the example.
Now, the only Xaml we need in our Window is below.
<controls:SortableListView ItemsSource="{Binding Employees}">
<controls:SortableListView.View>
<GridView>
<GridViewColumn Header="First Name" DisplayMemberBinding="{Binding FirstName}"
controls:SortableListView.SortPropertyName="FirstName"/>
<GridViewColumn Header="Surname" DisplayMemberBinding="{Binding AverageCallTime}" />
</GridView>
</controls:SortableListView.View>
</controls:SortableListView>
Best of all, we don't need any codebehind at all from now on! Download the
full class listing.
Good lord. I love WPF. And Xaml.
* thanks to Chris Anderson for this superb phrase!
Note that I've left the up and down arrow as an exercise for the reader :)
PS - This example was put together pretty quickly so your own mileage may vary! Be careful to test it meets your needs before using it!

Post By
Josh Twist
02:52
04 Jul 2007
» Next Post:
Infinite Resolution and SnapsToDevicePixels
« Previous Post:
System.Diagnostics hidden SourceLevels
Comments are closed for this post.
Posted by
Mike Brown
@
10 Jul 2007
08:23
Hey Josh, I think another Josh (Smith) went the same route of using inheritance to implement sorting in the ListView.
I went another route. I used attach properties to decorate a standard ListView with sorting functionality. Check it out and tell me what you think.
http://mbrownchicago.spaces.live.com/blog/cns!2221DC39E0C749A4!331.entry
Posted by
Mike Brown
@
10 Jul 2007
08:39
Also you don't need an attached property to get the actual property name to sort by. You can get that off of the column's DisplayMemberBinding property like so
string headerProperty = ((Binding)headerClicked.Column.DisplayMemberBinding).Path.Path;
Posted by
Josh
@
10 Jul 2007
23:44
Hey Mike, thanks for dropping by. I did think about using the DisplayMemberBinding but I like'd the idea that the SortPropertyName is entirely independant.
The DisplayMemberBinding would probably be a better 'fallback' though.
I did point out that 'I want to solve a problem that's already been solved' - there are hundreds of posts out there :)
I like your implementation too - isn't Xaml awesome.
Posted by
Dean White
@
22 Apr 2008
21:58
Hi Josh
I am trying your Implimentation on a listview bound to a Linq to SQL datasource but without much sucess.
Should I maybe be sorting in Linq and rebinding
Regards,
Dean
Posted by
Joe
@
15 May 2008
07:23
Hi Mike, I am fairly new to WPF and XAML...
I am getting an error 'SortableListView is not supported in a WPF project'...
Is there something I need to include in the header of the XAML?
Posted by
Joe
@
15 May 2008
07:24
Follow up:
Also getting 'The namespace prefix "controls" is not defined...
Posted by
Josh
@
17 May 2008
03:02
You need to include that xlm namespace and have it reference the clr namespace. e.g.
<Window xmlns:controls="clr-namespace:Namespace.With.Your.Control.In.It; assembly=NameOfAssembly">
Posted by
omkar dande
@
21 Jun 2009
23:58
I have listview which contains numbers (double). I am using the same code but the sort is not working. Althought it works perfectly for the columns containing String ( ex, firstname or lastname) datatype.
Thanks in advance.
Posted by
josh
@
22 Jun 2009
08:50
Hi Omkar,
Is the datatype of the numbers actually a string or a double?
We use the ListCollectionView to do the sorting here and it deals with numeric types just fine - but if the datatype of the property is string, it will sort in alphanumeric order.
Josh
Posted by
Tuby
@
02 Aug 2009
06:12
Thanks for an excellent article...just what I was looking for.
Posted by
Shin
@
29 Aug 2009
04:02
Is there any way to stop sorting once its started???
Iam spawing other thread to handle sorting but it doesnt work out correctly somehow :-(
Posted by
Josh
@
06 Sep 2009
23:43
Hi Shin,
You could programmatically clear the SortDescriptions. But this implies that the items are sorted in some way originally (even if randomly). If you want it to return to that state you'll have to somehow remember the order.
Josh