Introduction
In this tip, I will implement simple WPF Sorting in ListView
of Observable Collections by the Inputs provided from UI using MVVM arch.
For the UI, I will use several listView
elements for only sorting purposes & I think we can do better.
The application is built with the aim to provide an overview of the many simple best practices used in .NET programming for the newbie developer.
I had read somewhere that the three essential character flaws of any good programmer were sloth, impatience and hubris.
Good programmers want to do the minimum amount of work (sloth).
They want their programs to run quickly (impatience).
They take inordinate pride in what they have written (hubris).
This application will encourage the vices of sloth and hubris, by allowing programmers to do far less work but still produce great looking results.
OverView
:::: MVVM Observable Collections ::::
This is one of the very useful features for collections in WPF applications. In this application example, I am making the collections of my Model
Class in IObservable
Collections. Here, I had taken the name of Model
Class as "Model
" and ViewModel
class as "UserViewModel
".
Where I will first make the IObservable
Collections in this way.
public ObservableCollection<Model> _UsersList;
public UserViewModel()
{
_UsersList = new ObservableCollection<Model>();
}
:::: Comparer Classes ::::
In this example, I'm using two Comparer classes PersonComparer
& ListViewCustomComparer
.
In PersonComparer.cs, as per the required ListView
Elements (Tabs like SapId
, Name
, Dob
& Gender
in UI) condition are sorted with the param values.
using Assignments;
using System;
using System.ComponentModel;
namespace Assignments
{
public class PersonComparer : ListViewCustomComparer<Model>
{
public override int Compare(Model x, Model y)
{
try
{
String valueX = String.Empty, valueY = String.Empty;
switch (SortBy)
{
default:
case "Name":
valueX = x.Name;
valueY = y.Name;
break;
case "Gender":
valueX = x.Gender;
valueY = y.Gender;
break;
case "SapId":
if (SortDirection.Equals(ListSortDirection.Ascending))
return x.SapId.CompareTo(y.SapId);
else return (-1) * x.SapId.CompareTo(y.SapId);
case "Num":
if (SortDirection.Equals(ListSortDirection.Ascending))
return x.Num.CompareTo(y.Num);
else return (-1) * x.Num.CompareTo(y.Num);
}
if (SortDirection.Equals(ListSortDirection.Ascending))
return String.Compare(valueX, valueY);
else return (-1) * String.Compare(valueX, valueY);
}
catch (Exception)
{
return 0;
}
}
}
}
Now, In ListViewCustomComparer.cs which is my abstract Generic
class, I am comparing two params indicating whether less than, equal to, or greater than the other.
using System;
using System.Collections;
using System.ComponentModel;
namespace Assignments
{
public abstract class ListViewCustomComparer<T> : IComparer,
IListViewCustomComparer where T : class
{
#region [ Fields ]
private String sortBy = String.Empty;
private ListSortDirection direction = ListSortDirection.Ascending;
#endregion
#region [ Properties ]
public String SortBy
{
get { return sortBy; }
set { sortBy = value; }
}
public ListSortDirection SortDirection
{
get { return direction; }
set { direction = value; }
}
#endregion
#region [ Methods ]
public Int32 Compare(Object x, Object y)
{
T item1 = x as T;
T item2 = y as T;
if (item1 == null || item2 == null)
{
System.Diagnostics.Trace.Write("either x or y is null in compare(x,y)");
return 0;
}
return Compare(item1, item2);
}
public abstract Int32 Compare(T x, T y);
#endregion
}
}
:::: Sorting Classes ::::
Apart from this, I am having two Sorting classes ListViewSorter
& ListViewSortItem
.
In ListViewSorter.cs, I'm sorting the ListView
with binding the elements with each other in a column.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace Assignments
{
public static class ListViewSorter
{
#region [ Fields ]
private static Dictionary<String, ListViewSortItem>
_listViewDefinitions = new Dictionary<String, ListViewSortItem>();
#endregion
#region [ DependencyProperties ]
public static readonly DependencyProperty
CustomListViewSorterProperty = DependencyProperty.RegisterAttached(
"CustomListViewSorter",
typeof(String),
typeof(ListViewSorter),
new FrameworkPropertyMetadata("", new PropertyChangedCallback(OnRegisterSortableGrid)));
#region [ IsCustomListViewSorter get / set ]
public static String GetCustomListViewSorter(DependencyObject obj)
{
return (String)obj.GetValue(CustomListViewSorterProperty);
}
public static void SetCustomListViewSorter(DependencyObject obj, String value)
{
obj.SetValue(CustomListViewSorterProperty, value);
}
#endregion
#endregion
#region [ Public Methods ]
instance containing the event data.</param>
public static void GridViewColumnHeaderClickedHandler(Object sender, RoutedEventArgs e)
{
ListView view = sender as ListView;
if (view == null) return;
ListViewSortItem listViewSortItem = (_listViewDefinitions.ContainsKey(view.Name))
? _listViewDefinitions[view.Name] : null;
if (listViewSortItem == null) return;
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
if (headerClicked == null) return;
ListCollectionView collectionView =
CollectionViewSource.GetDefaultView(view.ItemsSource) as ListCollectionView;
if (collectionView == null) return;
ListSortDirection sortDirection = GetSortingDirection(headerClicked, listViewSortItem);
String header = (headerClicked.Column.DisplayMemberBinding as Binding).Path.Path as String;
if (String.IsNullOrEmpty(header)) return;
if (listViewSortItem.Comparer != null)
{
listViewSortItem.Comparer.SortBy = header;
listViewSortItem.Comparer.SortDirection = sortDirection;
collectionView.CustomSort = listViewSortItem.Comparer;
view.Items.Refresh();
}
else
{
view.Items.SortDescriptions.Clear();
view.Items.SortDescriptions.Add(new SortDescription
(headerClicked.Column.Header.ToString(), sortDirection));
view.Items.Refresh();
}
headerClicked.Column.HeaderTemplate =
GetHeaderColumnsDataTemplate(view, listViewSortItem, sortDirection);
listViewSortItem.LastColumnHeaderClicked = headerClicked;
listViewSortItem.LastSortDirection = sortDirection;
}
#endregion
#region [ Private Methods ]
private static void OnRegisterSortableGrid(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
if ((Boolean)(DesignerProperties.IsInDesignModeProperty.GetMetadata
(typeof(DependencyObject)).DefaultValue)) return;
ListView view = obj as ListView;
if (view != null)
{
_listViewDefinitions.Add(view.Name, new ListViewSortItem
(System.Activator.CreateInstance(Type.GetType
(GetCustomListViewSorter(obj))) as IListViewCustomComparer, null,
ListSortDirection.Ascending));
view.AddHandler(GridViewColumnHeader.ClickEvent,
new RoutedEventHandler(GridViewColumnHeaderClickedHandler));
}
}
private static DataTemplate GetHeaderColumnsDataTemplate
(ListView view, ListViewSortItem listViewSortItem, ListSortDirection sortDirection)
{
if (listViewSortItem.LastColumnHeaderClicked != null)
listViewSortItem.LastColumnHeaderClicked.Column.HeaderTemplate =
view.TryFindResource("ListViewHeaderTemplateNoSorting") as DataTemplate;
switch (sortDirection)
{
case ListSortDirection.Ascending:
return view.TryFindResource("ListViewHeaderTemplateAscendingSorting")
as DataTemplate;
case ListSortDirection.Descending:
return view.TryFindResource("ListViewHeaderTemplateDescendingSorting")
as DataTemplate;
default:
return null;
}
}
private static ListSortDirection GetSortingDirection
(GridViewColumnHeader headerClicked, ListViewSortItem listViewSortItem)
{
if (headerClicked != listViewSortItem.LastColumnHeaderClicked)
return ListSortDirection.Ascending;
else
return (listViewSortItem.LastSortDirection == ListSortDirection.Ascending)
? ListSortDirection.Descending : ListSortDirection.Ascending;
}
#endregion
}
}
In ListViewSortItem.cs, I'm just initializing a new instance of that class for compare, Last Sorted Direction & UI Tab Clicked.
using System.ComponentModel;
using System.Windows.Controls;
namespace Assignments
{
public class ListViewSortItem
{
#region [ Constructor ]
public ListViewSortItem(IListViewCustomComparer comparer,
GridViewColumnHeader lastColumnHeaderClicked, ListSortDirection lastSortDirection)
{
Comparer = comparer;
LastColumnHeaderClicked = lastColumnHeaderClicked;
LastSortDirection = lastSortDirection;
}
#endregion
#region [ Properties ]
public IListViewCustomComparer Comparer { get; private set; }
public GridViewColumnHeader LastColumnHeaderClicked { get; set; }
public ListSortDirection LastSortDirection { get; set; }
#endregion
}
}
Using the Code
:::: Interfaces for ICommand ::::
Now I have Interface for Icommand
as IListViewCustomComparer
where I'm just declaring the SortBy
& SortDirection
with Get
-Set
Properties.
using System;
using System.Collections;
using System.ComponentModel;
namespace Assignments
{
public interface IListViewCustomComparer : IComparer
{
String SortBy { get; set; }
ListSortDirection SortDirection { get; set; }
}
}
:::: MainWindow.xaml ::::
In this XAML part, we only have to do two things in our Window.Resources
we have to add DataTemplates
for arrows Buttons Styles.
<DataTemplate x:Key="ListViewHeaderTemplateDescendingSorting">
<DockPanel>
<TextBlock Text="{Binding}"/>
<Path x:Name="arrow"
StrokeThickness = "2"
Fill = "Red"
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 = "2"
Fill = "Green"
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>
In our List View, add the following reference to Comparer
class.
<ListView Name="UserGrid" ItemsSource="{Binding _UsersList}"
RenderTransformOrigin="0.538,-1.94" Margin="73,285,141,19"
local:ListViewSorter.CustomListViewSorter="Assignments.PersonComparer" >
ScreenShots
This is how the application looks like on startup.
DescendingSorting- Red Arrow
AscendingSorting - Green Arrow
For a basically lazy developer, to avoid too much work, this article is to relieve the workload.
So if you loved the way I explained to you, then stayed tuned. I will soon be uploading more tips for you!!
I must Serve you to lead all ~ Sumit Anand