Click here to Skip to main content
15,662,626 members
Articles / Desktop Programming / WPF
Article
Posted 11 May 2008

Stats

85.8K views
1.5K downloads
24 bookmarked

A more generic way of sorting a WPF ListView with IComparer

Rate me:
Please Sign up or sign in to vote.
4.59/5 (16 votes)
24 May 2008Ms-PL2 min read
An easy way of sorting the WPF ListView with a generic method.

Introduction

By default, there is no easy way of sorting a ListView in WPF. While there is a sample available on MSDN on how to sort a ListView, it isn’t very satisfying. The sample sorts the ListView based on the header of the column instead of the actual DisplayMemberBinding attribute. This is not an optimal solution, because column headers don’t necessarily contain text. Secondly, it could be that the column header differs from the actual property name. In addition to that, I assume that this isn’t working very well in a multi-language application either.

Background

I wanted a more generic solution to this issue. After playing around a while, I ended up with the solution shown below.

Requirements:

  • The solution needs to be easy to be attached to a ListView without adding a lot of code each time. The new attached properties functionality from WPF, using the Ramora pattern solves this issue nicely.
  • The sorting needed to be customizable. By using the CustomSort property of the ListCollectionView, this could be accomplished easily.

Using the code

To create a custom comparer, you need to implement the abstract base class ListViewCustomComparer<> and create your own comparer. In the sample application, I created a PersonComparer:

C#
public class PersonComparer : ListViewCustomComparer<Person>
{

/// <summary>
/// Compares the specified x to y.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns></returns>
public override int Compare(Person x, Person y)
{
    try
    {
        String valueX = String.Empty, valueY = String.Empty;
        switch (SortBy)
        {
        default:
        case "FirstName":
            valueX = x.FirstName;
            valueY = y.FirstName;
        break;
        case "LastName":
            valueX = x.LastName;
            valueY = y.LastName;
        break;
        case "Age":
            if (SortDirection.Equals(ListSortDirection.Ascending))
                return x.Age.CompareTo(y.Age);
            else
                return (-1) * x.Age.CompareTo(y.Age);
        }
        if (SortDirection.Equals(ListSortDirection.Ascending))
            return String.Compare(valueX, valueY);
        else 
            return (-1) * String.Compare(valueX, valueY);
    }
    catch (Exception)
    {
        return 0;
    }
}

The SortBy value is the value of the DisplayMemberBinding, which is set in the MainWindow.xaml markup code:

MainWindow.xaml:

XML
<GridViewColumn Header="FirstName" Width="100" DisplayMemberBinding="{Binding FirstName}" />
<GridViewColumn Header="LastName" Width="100" DisplayMemberBinding="{Binding LastName}" />
lt;GridViewColumn Header="Age" Width="100" DisplayMemberBinding="{Binding Age}" />

In the MainWindow itself, you need to add the local xml:ns and the data templates, which are used to display a different header with arrows on sorted columns. You also need to add the ListViewSorter.CustomListViewSorter attribute to the ListView XML element.

XML
<Window x:Class="WpfListViewSorting.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfListViewSorting"
    Title="WpfListViewSorting Demo" Height="300" Width="400">
    <Window.Resources>
    <DataTemplate x:Key="ListViewHeaderTemplateDescendingSorting">
        <DockPanel>
            <TextBlock Text="{Binding}"/>
        <Path x:Name="arrow"
            StrokeThickness = "1" 
            Fill = "gray"
            Data = "M 5,10 L 15,10 L 10,5 L 5,10"/>
    </DockPanel>
    </DataTemplate>
<DataTemplate x:Key="ListViewHeaderTemplateAscendingSorting">
<DockPanel>
    <TextBlock Text="{Binding }"/>
    <Path x:Name="arrow"
    StrokeThickness = "1" 
    Fill = "gray"
    Data = "M 5,5 L 10,10 L 15,5 L 5,5"/>
</DockPanel>
</DataTemplate>

<DataTemplate x:Key="ListViewHeaderTemplateNoSorting">
    <DockPanel>
        <TextBlock Text="{Binding }"/>
    </DockPanel>
</DataTemplate>

</Window.Resources>
<Grid>
    <ListView Margin="5" 
        VirtualizingStackPanel.IsVirtualizing="True"
        IsSynchronizedWithCurrentItem="True"
        local:ListViewSorter.CustomListViewSorter="WpfListViewSorting.PersonComparer"
        x:Name="lstView" >
    <ListView.View>
        <GridView AllowsColumnReorder="True">
            <GridViewColumn Header="FirstName" Width="100" 
                     DisplayMemberBinding="{Binding FirstName}" />
            <GridViewColumn Header="LastName" Width="100" 
                     DisplayMemberBinding="{Binding LastName}" />
            <GridViewColumn Header="Age" Width="100" 
                     DisplayMemberBinding="{Binding Age}" />
        </GridView>
    </ListView.View>
    </ListView >
</Grid>
</Window>

This is all the code that needs to be added to add sorting to a ListView. For the next ListView, you only need to add the local:ListViewSorter.CustomListViewSorter = "WpfListViewSorting.PersonComparer" attribute to it to activate its working (if the bound object is the same; otherwise, create a new comparer). The ListViewSorter will keep track of the previous sorting direction and sorting column in a Dictionary, which holds a collection of ListViewSortItems.

Points of interest

None yet. Happy coding!

History

  • 24-05-2008 - Modified the code sample to check for Visual Studio design mode, so it won't cause any errors.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer (Junior)
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Suggestionusing Adorner instead of DataTemplates for sort mark Pin
evalese1-Sep-12 9:47
evalese1-Sep-12 9:47 
Bugan item with the same key has already been added Pin
evalese28-Aug-12 9:27
evalese28-Aug-12 9:27 
QuestionCode understandable code Pin
suneetha_g5-Apr-12 1:40
suneetha_g5-Apr-12 1:40 
QuestionCould u adjust your code for <GridViewColumn.CellTemplate> ? Pin
dimas197127-Jan-12 5:32
dimas197127-Jan-12 5:32 
Questionlittle bug Pin
michamh30-Aug-11 22:45
michamh30-Aug-11 22:45 
GeneralNice solution Pin
zameb12-Aug-10 3:29
zameb12-Aug-10 3:29 
GeneralMoving list sorting classes to external assembly Pin
Ergwun23-Feb-09 12:22
Ergwun23-Feb-09 12:22 
I am using this method of sorting list views in an application. It works fine for me when I add your classes to the application.

I would like to add them to an assembly that we use for generic UI functionality that is applicable in multiple applications, but am pretty new to .Net and WPF and cannot get this to work.

I have put IListViewCustomComparer.cs, ListViewCustomComparer.cs, ListViewSorter.cs and ListViewSortItem.cs in the external assembly (still within the main solution), and added it as an additional namespace in the user control that contains the listview I want to sort:

xmlns:ListViewSorting="clr-namespace:Blah.Ui.ListViewSorting;assembly=Blah.Ui"

I have left my equivalent of the PersonComparer.cs in my application's assembly, since that is where the class it sorts is defined and used.

I can't work out what the WPF line assigning the custom comparer property to the listview should be changed to:
<br />
local:ListViewSorter.CustomListViewSorter="WpfListViewSorting.PersonComparer"


I've tried a bunch of variations including putting the PersonComparer class inside wither the app's namespace or the ListViewSorting namespace (although still in the app's assembly), eg.:
ListViewSorting:ListViewSorter.CustomListViewSorter="PersonBrowser.PersonComparer" and
ListViewSorting:ListViewSorter.CustomListViewSorter="local:PersonBrowser.PersonComparer"

They all give me the same error when I run:

A first chance exception of type 'System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll
Additional information: 'local:PersonBrowser.PersonComparer' value cannot be assigned to property 'CustomListViewSorter' of object 'System.Windows.Controls.ListView'. Value cannot be null.
Parameter name: type Error at object 'FilteredPersonListView' in markup file 'PersonBrowser;component/filteredpeoplesummaryview.xaml'.


I've read up on referencing classes in external assemblies, but can't find out what to do in this situation where have a property defined in an external assembly and want to assign it with a class that is a local override of an externally defined base class. Any help would be appreciated. Thanks.
Generalsorting with CellTemplate (not DisplayMemberBinding) Pin
Brian Ellertson24-Jun-08 9:29
Brian Ellertson24-Jun-08 9:29 
GeneralRe: sorting with CellTemplate (not DisplayMemberBinding) Pin
SilverX695-Jul-11 21:52
SilverX695-Jul-11 21:52 
General[Message Removed] Pin
Mojtaba Vali24-May-08 18:12
Mojtaba Vali24-May-08 18:12 

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

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