Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Built-in Automatic Sorting for WPF ListView Items

5.00/5 (3 votes)
14 Feb 2015MIT1 min read 26K   483  
A custom ListView class that uses reflection to sort columns based on the DisplayMemberBinding's bound data type

Introduction

For most LOB (Line Of Business) applications, a ListView is a common control used to display rows of data in a grid like format. In WinForms, the ListView is very easy to sort data based on a data type. In WPF however, this has been a sore spot for many developers because the WPF version of the control's built in sorting mechanism is based on simple string comparisons. The default ListView control also does not allow you to manipulate GridRowColumns so easily. This makes a custom sorting solution difficult in WPF.

Background

The solution I provide here is a small extended ListView class called ListViewSorter that "bakes-in" custom sorting logic on any bound types that subscribe to the IComparable interface (i.e. can be compared).

Using the Code

To use the ListViewSorter class, simply import the XML namespace containing the class into a Window or UserControl. Then use the control as you would the default in ListView control. Make sure you create GridViewColumns and set their DisplayMemberBinding attributes to a property of a view model. Run the program and click the headers to see the sorting in action.

ListViewSorter.xaml.cs:

C#
public partial class ListViewSorter : ListView
{
    private CustomSorter _customSorter = new CustomSorter();
    private ListSortDirection _sortDirection = ListSortDirection.Ascending;

    public ListViewSorter()
    {
        InitializeComponent();
    }

    private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
    {
        GridViewColumnHeader columnHeader = e.OriginalSource as GridViewColumnHeader;
        if (columnHeader == null)
            return;
        Sort(columnHeader);
    }

    private void Sort(GridViewColumnHeader columnHeader)
    {
        Binding binding = columnHeader.Column.DisplayMemberBinding as Binding;
        if (binding != null)
        {
            ICollectionView dataView = CollectionViewSource.GetDefaultView(this.ItemsSource);
            ListCollectionView view = (ListCollectionView)dataView;
            _customSorter.SortPropertyName = binding.Path.Path;;
            view.CustomSort = _customSorter;
        }
    }

    public class CustomSorter : IComparer
    {
        private Dictionary<string, ListSortDirection>_dictOfSortDirections =
            new Dictionary<string, ListSortDirection>();     

        private string _sortPropertyName;
        public string SortPropertyName
        {
            get { return _sortPropertyName; }
            set
            {
                _sortPropertyName = value;
                if (!_dictOfSortDirections.ContainsKey(_sortPropertyName))
                {
                    _dictOfSortDirections.Add(_sortPropertyName, ListSortDirection.Ascending);
                }
                // Alternate sort directions inside the dictionary
                _dictOfSortDirections[_sortPropertyName] =
                    (_dictOfSortDirections[_sortPropertyName] == ListSortDirection.Ascending) ?
                        ListSortDirection.Descending : ListSortDirection.Ascending;
            }
        }

        public int Compare(object x, object y)
        {
            PropertyInfo pi = x.GetType().GetProperty(_sortPropertyName);
            if (pi != null)
            {
                object value1 = pi.GetValue(x);
                object value2 = pi.GetValue(y);

                bool valuesAreNotSortable = (!(value1 is IComparable) || !(value2 is IComparable));
                if (valuesAreNotSortable)
                    return 0;

                ListSortDirection dir = _dictOfSortDirections[_sortPropertyName];

                if (dir == ListSortDirection.Ascending)
                    return ((IComparable)value1).CompareTo(value2);
                else
                    return ((IComparable)value2).CompareTo(value1);
            }
            return 0;
        }
    }
}

ListViewSorter.xaml:

XML
<ListView x:Class="Controls.ListViewSorter"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      mc:Ignorable="d" 
      d:DesignHeight="300"
      GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler">

<ListView.Resources>
    <Style TargetType="{x:Type GridViewColumnHeader}">
        <Setter Property="HorizontalContentAlignment"
                Value="Left" />
    </Style>
</ListView.Resources>

</ListView>

MainWindow.xaml:

XML
<Window x:Class="ListViewSorter.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:Controls"
    Title="MainWindow"
    Height="212"
    Width="447"
    WindowStartupLocation="CenterScreen">
    
    <Grid>
        <controls:ListViewSorter ItemsSource="{Binding Rows}" FontSize="14">
            <ListView.View>
                <GridView>
                    <GridViewColumn Width="100" Header="Name" DisplayMemberBinding="{Binding Name}" />
                    <GridViewColumn Width="100" Header="Account ID" 
            DisplayMemberBinding="{Binding AccountId}"  />
                    <GridViewColumn Width="100" Header="Weight" 
            DisplayMemberBinding="{Binding Weight, StringFormat=F2}"  />
                    <GridViewColumn Width="100" Header="DOB" 
            DisplayMemberBinding="{Binding DOB, StringFormat={}{0:MM/dd/yyyy}}" />
                </GridView>
            </ListView.View>
        </controls:ListViewSorter>
    </Grid>
</Window>

Points of Interest

Please let me know if you find any bugs or add any features, happy coding!

History

  • 02/14/2015 - Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License