Introduction
WPF DataGrid has built-in functionality for sorting its items by clicking on a column
header. However, it only works for the current items in the DataGrid. This becomes a problem when paging functionality is implemented. Paging is a must
when items are so many that it should not be loaded into memory because it might
affect performance.
Application
Let’s create an application like the one below.

The window is made up of a data grid which shows a list of products and buttons for
moving through the list. The data grid is a WPF DataGrid. It is not yet a released
product but is downloadable from CodePlex under WPF Toolkit.
Paging
To implement paging, a method must be created that retrieves items from data storage
where the calling method can specify the range of items that should be returned.
The following code listing shows a static class that has the said method.
/// <summary>
/// Class that simulates a DataAccess module.
/// </summary>
public static class DataAccess
{
/// <summary>
/// A list of products. This should be replaced by a database.
/// </summary>
private static ObservableCollection<Product> products = new ObservableCollection<Product>
{
new Product(1, "Book"),
new Product(2, "Desktop Computer"),
new Product(3, "Notebook"),
new Product(4, "Netbook"),
new Product(5, "Business Software"),
new Product(6, "Antivirus Software"),
new Product(7, "Game Console"),
new Product(8, "Handheld Game Console"),
new Product(9, "Mobile Phone"),
new Product(10, "Multimedia Software"),
new Product(11, "PC Game")
};
/// <summary>
/// Gets the products.
/// </summary>
/// <param name="start">Zero-based index that determines the start of the products to be returned.</param>
/// <param name="itemCount">Number of products that is requested to be returned.</param>
/// <param name="sortColumn">Name of column or member that is the basis for sorting.</param>
/// <param name="ascending">Indicates the sort direction to be used.</param>
/// <param name="totalItems">Total number of products.</param>
/// <returns>List of products.</returns>
public static ObservableCollection<Product> GetProducts(int start, int itemCount, string sortColumn, bool ascending, out int totalItems)
{
totalItems = products.Count;
ObservableCollection<Product> sortedProducts = new ObservableCollection<Product>();
// Sort the products. In reality, the items should be stored in a database and
// use SQL statements for sorting and querying items.
switch (sortColumn)
{
case ("Id"):
sortedProducts = new ObservableCollection<Product>
(
from p in products
orderby p.Id
select p
);
break;
case ("Name"):
sortedProducts = new ObservableCollection<Product>
(
from p in products
orderby p.Name
select p
);
break;
}
sortedProducts = ascending ? sortedProducts : new ObservableCollection<Product>(sortedProducts.Reverse());
ObservableCollection<Product> filteredProducts = new ObservableCollection<Product>();
for (int i = start; i < start + itemCount && i < totalItems; i++)
{
filteredProducts.Add(sortedProducts[i]);
}
return filteredProducts;
}
}
The GetProducts() determines the range of products to return by using the start and
itemCount parameters. The sortColumn and ascending parameters are used in sorting
the items, which will be discussed later. The totalItems is an out parameter
that is set by the method to the total number of products. This can be more useful
if there is a search function. The totalItems will be set to the number of products
that matched the specified search criteria instead.
Notice that the GetProducts() method only gets the products from a static member
defined in the class. This is for demonstration purposes only. Accessing items
from a database is more desirable.
Now let’s take a look at XAML definition of the window that was shown earlier and
make a way to call the GetProducts() method.
<Window
x:Class="WPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tk="http://schemas.microsoft.com/wpf/2008/toolkit"
Width="350"
Height="190"
Title="WPF DataGrid Paging and Sorting">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<tk:DataGrid
AutoGenerateColumns="False"
IsReadOnly="True"
ItemsSource="{Binding Products}">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn
Header="PRODUCT ID"
Binding="{Binding Id}"
Width="*"/>
<tk:DataGridTextColumn
Header="PRODUCT NAME"
Binding="{Binding Name}"
Width="*"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
<StackPanel
Margin="4"
Grid.Row="1"
Orientation="Horizontal"
HorizontalAlignment="Center">
<Button
Margin="4,0"
Content="<<"
Command="{Binding FirstCommand}"/>
<Button
Margin="4,0"
Content="<"
Command="{Binding PreviousCommand}"/>
<StackPanel
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Text="{Binding Start}"/>
<TextBlock
Text=" to "/>
<TextBlock
Text="{Binding End}"/>
<TextBlock
Text=" of "/>
<TextBlock
Text="{Binding TotalItems}"/>
</StackPanel>
<Button
Margin="4,0"
Content=">"
Command="{Binding NextCommand}"/>
<Button
Margin="4,0"
Content=">>"
Command="{Binding LastCommand}"/>
</StackPanel>
</Grid>
</Window>
The DataGrid’s ItemsSource dependency property is bounded to the Products property
in the window’s ViewModel. The Products property is set to a new object every
time a user clicks on a navigation button. Each button has its Command property
bounded to a property in the ViewModel. If you are unfamiliar with Model-View-ViewModel
design pattern, you may look at my previous article entitled “WPF and the Model
View View Model Pattern” or you could search for other resources.
Most of the logic is implemented in the window’s ViewModel. The following code listing
shows the ViewModel.
/// <summary>
/// ViewModel of the MainWindow. This is assigned to the MainWindow's DataContext
/// property. Implements the INotifyPropertyChanged interface to notify the View
/// of property changes.
/// </summary>
public class MainViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Private Fields
private ObservableCollection<Product> products;
private int start = 0;
private int itemCount = 5;
private string sortColumn = "Id";
private bool ascending = true;
private int totalItems = 0;
private ICommand firstCommand;
private ICommand previousCommand;
private ICommand nextCommand;
private ICommand lastCommand;
#endregion
/// <summary>
/// Constructor. Initializes the list of products.
/// </summary>
public MainViewModel()
{
RefreshProducts();
}
/// <summary>
/// The list of products in the current page.
/// </summary>
public ObservableCollection<Product> Products
{
get
{
return products;
}
private set
{
if (object.ReferenceEquals(products, value) != true)
{
products = value;
NotifyPropertyChanged("Products");
}
}
}
/// <summary>
/// Gets the index of the first item in the products list.
/// </summary>
public int Start { get { return start + 1; } }
/// <summary>
/// Gets the index of the last item in the products list.
/// </summary>
public int End { get { return start + itemCount < totalItems ? start + itemCount : totalItems ; } }
/// <summary>
/// The number of total items in the data store.
/// </summary>
public int TotalItems { get { return totalItems; } }
/// <summary>
/// Gets the command for moving to the first page of products.
/// </summary>
public ICommand FirstCommand
{
get
{
if (firstCommand == null)
{
firstCommand = new RelayCommand
(
param =>
{
start = 0;
RefreshProducts();
},
param =>
{
return start - itemCount >= 0 ? true : false;
}
);
}
return firstCommand;
}
}
/// <summary>
/// Gets the command for moving to the previous page of products.
/// </summary>
public ICommand PreviousCommand
{
get
{
if (previousCommand == null)
{
previousCommand = new RelayCommand
(
param =>
{
start -= itemCount;
RefreshProducts();
},
param =>
{
return start - itemCount >= 0 ? true : false;
}
);
}
return previousCommand;
}
}
/// <summary>
/// Gets the command for moving to the next page of products.
/// </summary>
public ICommand NextCommand
{
get
{
if (nextCommand == null)
{
nextCommand = new RelayCommand
(
param =>
{
start += itemCount;
RefreshProducts();
},
param =>
{
return start + itemCount < totalItems ? true : false;
}
);
}
return nextCommand;
}
}
/// <summary>
/// Gets the command for moving to the last page of products.
/// </summary>
public ICommand LastCommand
{
get
{
if (lastCommand == null)
{
lastCommand = new RelayCommand
(
param =>
{
start = (totalItems / itemCount - 1) * itemCount;
start += totalItems % itemCount == 0 ? 0 : itemCount;
RefreshProducts();
},
param =>
{
return start + itemCount < totalItems ? true : false;
}
);
}
return lastCommand;
}
}
/// <summary>
/// Refreshes the list of products. Called by navigation commands.
/// </summary>
private void RefreshProducts()
{
Products = DataAccess.GetProducts(start, itemCount, sortColumn, ascending, out totalItems);
NotifyPropertyChanged("Start");
NotifyPropertyChanged("End");
NotifyPropertyChanged("TotalItems");
}
/// <summary>
/// Notifies subscribers of changed properties.
/// </summary>
/// <param name="propertyName">Name of the changed property.</param>
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The ViewModel contains the commands for navigating through the list of products.
Basically, these commands just set the start variable then call the RefreshProducts()
method. For example, the FirstCommand just sets the start variable to the value
0. Afterwards, it calls the RefreshProducts() method which in turn calls the
DataAccess.GetProducts() method which uses the updated start variable.
The itemCount value is not changed anywhere in the application. It is useful when
a user wants to select the number of items that can be displayed. This is a common
functionality in most applications that implement paging. This is not implemented
in the example.
Sorting
Now that paging has been implemented, the only thing left is custom sorting. The
following screenshot shows a sorted list of products if the data grid’s built-in
sorting is used.

In the example, the product name is sorted. Notice that only the items that were
sorted are the current items in the data grid. The items stored in our data store
are not included in the sort. The following screenshot shows the sorted list
of products where custom sorting was used.

To implement custom sorting, some code changes need to be done. The following code
listing shows the updated data grid definition in the XAML file.
<tk:DataGrid
AutoGenerateColumns="False"
IsReadOnly="True"
ItemsSource="{Binding Products, NotifyOnTargetUpdated=True}"
Sorting="ProductsDataGrid_Sorting"
TargetUpdated="ProductsDataGrid_TargetUpdated"
Loaded="ProductsDataGrid_Loaded">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn
Header="PRODUCT ID"
Binding="{Binding Id}"
Width="*"
SortDirection="Ascending"/>
<tk:DataGridTextColumn
Header="PRODUCT NAME"
Binding="{Binding Name}"
Width="*"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
Notice that the following event handlers are added: ProductsDataGrid_Sorting, ProductsDataGrid_TargetUpdated
and ProductsDataGrid_Loaded. Also, the NotifyOnTargetUpdated property of the
ItemsSource’s binding is set to true. The following code listing shows the code-behind
file of the window. This shows the definition for the event handlers previously
mentioned.
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private DataGridColumn currentSortColumn;
private ListSortDirection currentSortDirection;
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
/// <summary>
/// Initializes the current sort column and direction.
/// </summary>
/// <param name="sender">The products data grid.</param>
/// <param name="e">Ignored.</param>
private void ProductsDataGrid_Loaded(object sender, RoutedEventArgs e)
{
DataGrid dataGrid = (DataGrid)sender;
// The current sorted column must be specified in XAML.
currentSortColumn = dataGrid.Columns.Where(c => c.SortDirection.HasValue).Single();
currentSortDirection = currentSortColumn.SortDirection.Value;
}
/// <summary>
/// Sets the sort direction for the current sorted column since the sort direction
/// is lost when the DataGrid's ItemsSource property is updated.
/// </summary>
/// <param name="sender">The parts data grid.</param>
/// <param name="e">Ignored.</param>
private void ProductsDataGrid_TargetUpdated(object sender, DataTransferEventArgs e)
{
if (currentSortColumn != null)
{
currentSortColumn.SortDirection = currentSortDirection;
}
}
/// <summary>
/// Custom sort the datagrid since the actual records are stored in the
/// server, not in the items collection of the datagrid.
/// </summary>
/// <param name="sender">The parts data grid.</param>
/// <param name="e">Contains the column to be sorted.</param>
private void ProductsDataGrid_Sorting(object sender, DataGridSortingEventArgs e)
{
e.Handled = true;
MainViewModel mainViewModel = (MainViewModel)DataContext;
string sortField = String.Empty;
// Use a switch statement to check the SortMemberPath
// and set the sort column to the actual column name. In this case,
// the SortMemberPath and column names match.
switch (e.Column.SortMemberPath)
{
case ("Id"):
sortField = "Id";
break;
case ("Name") :
sortField = "Name";
break;
}
ListSortDirection direction = (e.Column.SortDirection != ListSortDirection.Ascending) ?
ListSortDirection.Ascending : ListSortDirection.Descending;
bool sortAscending = direction == ListSortDirection.Ascending;
mainViewModel.Sort(sortField, sortAscending);
currentSortColumn.SortDirection = null;
e.Column.SortDirection = direction;
currentSortColumn = e.Column;
currentSortDirection = direction;
}
}
First, the current data grid column and sort direction are stored in member variables
because the current sort information is lost when the DataGrid’s ItemsSource
property is set to another instance, which will be done every time the user sorts
the list or navigates to other pages. These variables are initialized in the
Loaded event handler of the window. Note that there must be one column that has
its SortDirection property initialized by specifying it either in code or XAML.
To set the sort direction again when a user sorts the list or moves to another page,
the current column’s SortDirection property should be set in the TargetUpdated
event handler. The event is triggered when the DataGrid’s ItemsSource property
is updated. The NotifyOnTargetUpdated property in the binding expression should
be set to true. Take note that setting the sort direction does not sort the data
grid. It just specifies how the sort arrow of the column should be displayed.
The Sorting event handler overrides the default sorting mechanism of the data grid.
The Handled property of the DataGridSortingEventArgs parameter must be set to
true so that the default sorting is not executed. This method calls the newly
added Sort() method of the MainViewModel. It requires two parameters, the sort
column and the sort direction. The application should know beforehand the possible
values for the sort column. The possible values may be defined as an enumeration
type in the DataAccess module. I just used a string for simplicity.
The direction variable is a local variable that stores the next sort direction. Basically,
it toggles the sort direction for the column that is to be sorted. Meanwhile,
the sort direction for the current column that is sorted is set to null. Finally,
the currentSortColumn and currentSortDirection are set to their new values.
The Visual Studio 2008 example can be downloaded here.