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:
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);
}
_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:
<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:
<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