How to: Group, Sort, and Filter Data in the DataGrid Control
Microsoft Silverlight will reach end of support after October 2021. Learn more.
It is often useful to view data in a DataGrid in different ways by grouping, sorting, and filtering the data. To group, sort, and filter the data in a DataGrid, you bind it to a collection view that supports these functions. You can then manipulate the data in the collection view without affecting the underlying data. The changes in the collection view are reflected in the DataGrid user interface (UI).
The PagedCollectionView class provides grouping, sorting, and paging functionality for a data source that implements the IEnumerable interface. In this example, a collection of Task objects is wrapped in a PagedCollectionView. The PagedCollectionView is used as the ItemsSource for the DataGrid. Grouping, sorting, and filtering are performed on the PagedCollectionView and are displayed in the DataGrid UI.
The PagedCollectionView also provides paging functionality that can be used with a DataGrid. To see an example of paging, see the DataPager control.
Using a PagedCollectionView as an ItemsSource
To group, sort, and filter data in a DataGrid control, you bind the DataGrid to a collection view that supports these functions. In this example, the DataGrid is bound to a PagedCollectionView that provides these functions for a List<T> of Task objects.
To bind a DataGrid to a PagedCollectionView
Create a data collection that implements the IEnumerable interface.
Note: The objects in the collection must implement the INotifyPropertyChanged changed interface and the IEditableObject interface in order for the DataGrid to respond correctly to property changes and edits.
Create a PagedCollectionView and pass the data collection to the constructor.
Set the DataGrid.ItemsSource property to the PagedCollectionView.
' Create a collection to store task data. Dim taskList As ObservableCollection(Of Task) = New ObservableCollection(Of Task) ' Generate some task data and add it to the task list. For index = 1 To 14 taskList.Add(New Task() With _ {.ProjectName = "Project " & ((index Mod 3) + 1).ToString(), _ .TaskName = "Task " & index.ToString(), _ .DueDate = Date.Now.AddDays(index), _ .Complete = (index Mod 2 = 0), _ .Notes = "Task " & index.ToString() & " is due on " & Date.Now.AddDays(index) & ". Lorum ipsum..." _ }) Next Dim taskListView As New PagedCollectionView(taskList) Me.dataGrid1.ItemsSource = taskListView
// Create a collection to store task data. ObservableCollection<Task> taskList = new ObservableCollection<Task>(); // Generate some task data and add it to the task list. for (int i = 1; i <= 14; i++) { taskList.Add(new Task() { ProjectName = "Project " + ((i % 3) + 1).ToString(), TaskName = "Task " + i.ToString(), DueDate = DateTime.Now.AddDays(i), Complete = (i % 2 == 0), Notes = "Task " + i.ToString() + " is due on " + DateTime.Now.AddDays(i) + ". Lorum ipsum..." }); } PagedCollectionView taskListView = new PagedCollectionView(taskList); this.dataGrid1.ItemsSource = taskListView;
Grouping items in a DataGrid
To specify how items are grouped in a DataGrid, you use the PropertyGroupDescription type to group the items in the source view.
To group items in a DataGrid
Create a PropertyGroupDescription and pass the name of the property to group by to the constructor.
Add the PropertyGroupDescription to the PagedCollectionView.GroupDescriptions collection.
Add additional instances of PropertyGroupDescription to the PagedCollectionView.GroupDescriptions collection to add more levels of grouping.
If taskListView.CanGroup = True Then ' Group tasks by ProjectName... taskListView.GroupDescriptions.Add(New PropertyGroupDescription("ProjectName")) ' Then group by Complete status. taskListView.GroupDescriptions.Add(New PropertyGroupDescription("Complete")) End If
if (taskListView.CanGroup == true) { // Group tasks by ProjectName... taskListView.GroupDescriptions.Add(new PropertyGroupDescription("ProjectName")); // Then group by Complete status. taskListView.GroupDescriptions.Add(new PropertyGroupDescription("Complete")); }
When items are grouped in the DataGrid, each group has a header. You can change the appearance of the DataGridRowGroupHeader by defining a custom Style and adding it to the RowGroupHeaderStyles collection. If you have multiple levels of grouping, you can apply different styles to each group level. Styles are applied in the order in which they are defined. For example, if you define two styles, the first will be applied to top level row groups. The second style will be applied to all row groups at the second level and lower. The DataContext of the DataGridRowGroupHeader is the CollectionViewGroup that the header represents.
To change the appearance of row group headers
Create a Style with the TargetType of DataGridRowGroupHeader.
Put the Style inside the <DataGrid.RowGroupHeaderStyles> tags.
<sdk:DataGrid.RowGroupHeaderStyles> <!-- Style for groups at top level --> <Style TargetType="sdk:DataGridRowGroupHeader"> <Setter Property="PropertyNameVisibility" Value="Collapsed" /> <Setter Property="Background" Value="#FF112255" /> <Setter Property="Foreground" Value="#FFEEEEEE" /> <Setter Property="SublevelIndent" Value="15" /> </Style> <!-- Style for groups under the top level --> <Style TargetType="sdk:DataGridRowGroupHeader"> <Setter Property="Background" Value="#44225566" /> </Style> </sdk:DataGrid.RowGroupHeaderStyles>
When items are grouped in the DataGrid, you can use the following methods to manually collapse and expand the groups:
Click the collapse or expand arrow in the row group header.
Double-click anywhere in the row group header.
Press the left arrow key to collapse the group and the right arrow key to expand the group when the row group header has focus.
To programmatically collapse or expand a group, pass the CollectionViewGroup to the CollapseRowGroup or ExpandRowGroup method.
To collapse or expand row groups
Get the CollectionViewGroup that represents the group to collapse or expand.
Note: You can get the CollectionViewGroup from the Groups collection. Alternatively, you can use the GetGroupFromItem method to get an individual CollectionViewGroup as shown here.
Dim cvg = dataGrid1.GetGroupFromItem(dataGrid1.SelectedItem, 0)
CollectionViewGroup cvg = dataGrid1.GetGroupFromItem(dataGrid1.SelectedItem, 0);
To collapse the group, pass the CollectionViewGroup to the CollapseRowGroup method. Set the second argument to true to also collapse all sub groups.
To expand the group, pass the CollectionViewGroup to the ExpandRowGroup method. Set the second argument to true to also expand all sub groups.
The following example shows how to collapse or expand all groups in the PagedCollectionView.Groups collection.
Private Sub CollapseButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Dim pcv As PagedCollectionView = Me.dataGrid1.ItemsSource Try For Each group As CollectionViewGroup In pcv.Groups dataGrid1.ScrollIntoView(group, Nothing) dataGrid1.CollapseRowGroup(group, True) Next Catch ex As Exception ' Could not collapse group. MessageBox.Show(ex.Message) End Try End Sub
private void CollapseButton_Click(object sender, RoutedEventArgs e) { PagedCollectionView pcv = dataGrid1.ItemsSource as PagedCollectionView; try { foreach (CollectionViewGroup group in pcv.Groups) { dataGrid1.ScrollIntoView(group, null); dataGrid1.CollapseRowGroup(group, true); } } catch (Exception ex) { // Could not collapse group. MessageBox.Show(ex.Message); } }
Private Sub ExpandButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Dim pcv As PagedCollectionView = Me.dataGrid1.ItemsSource Try For Each group As CollectionViewGroup In pcv.Groups dataGrid1.ExpandRowGroup(group, True) Next Catch ex As Exception ' Could not expand group. MessageBox.Show(ex.Message) End Try End Sub
private void ExpandButton_Click(object sender, RoutedEventArgs e) { PagedCollectionView pcv = dataGrid1.ItemsSource as PagedCollectionView; try { foreach (CollectionViewGroup group in pcv.Groups) { dataGrid1.ExpandRowGroup(group, true); } } catch (Exception ex) { // Could not expand group. MessageBox.Show(ex.Message); } }
Sorting items in a DataGrid
To specify how items are sorted in a DataGrid, you use the SortDescription type to sort the items in the source view.
To sort items in a DataGrid
Create a SortDescription and pass the name of the property to sort by to the constructor.
Add the SortDescription to the PagedCollectionView.SortDescriptions collection.
Add additional instances of SortDescription to the PagedCollectionView.SortDescriptions collection to sort by additional properties.
If taskListView.CanSort = True Then '// By default, sort by ProjectName. taskListView.SortDescriptions.Add(New SortDescription("ProjectName", ListSortDirection.Ascending)) End If
if (taskListView.CanSort == true) { // By default, sort by ProjectName. taskListView.SortDescriptions.Add(new SortDescription("ProjectName", ListSortDirection.Ascending)); }
Filtering items in a DataGrid
To filter items in a DataGrid, you create a method that provides the filtering logic and then you use the PagedCollectionView.Filter property to apply the filter.
To filter items in a DataGrid
Create a method that provides the filtering logic. The method is used as a callback and accepts a parameter of type Object.
Apply the filter to the data by setting the PagedCollectionView.Filter property.
Remove the filter by setting the PagedCollectionView.Filter property to null.
The following example applies the filter when the CheckBox is Checked, and removes it when the CheckBox is Unchecked.
Private Sub CheckBox_Checked(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Dim pcv As PagedCollectionView = Me.dataGrid1.ItemsSource If pcv IsNot Nothing & pcv.CanFilter = True Then ' Apply the filter. pcv.Filter = New Predicate(Of Object)(AddressOf FilterCompletedTasks) End If End Sub Private Sub CheckBox_Unchecked(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Dim pcv As PagedCollectionView = Me.dataGrid1.ItemsSource If pcv IsNot Nothing Then ' Remove the filter. pcv.Filter = Nothing End If End Sub Public Function FilterCompletedTasks(ByVal t As Object) As Boolean Dim _task As New Task _task = t Return _task.Complete = False End Function
private void CheckBox_Checked(object sender, RoutedEventArgs e) { PagedCollectionView pcv = this.dataGrid1.ItemsSource as PagedCollectionView; if (pcv != null && pcv.CanFilter == true) { // Apply the filter. pcv.Filter = new Predicate<object>(FilterCompletedTasks); } } private void CheckBox_Unchecked(object sender, RoutedEventArgs e) { PagedCollectionView pcv = this.dataGrid1.ItemsSource as PagedCollectionView; if (pcv != null) { // Remove the filter. pcv.Filter = null; } } public bool FilterCompletedTasks(object t) { Task task = t as Task; return (task.Complete == false); }
Example
The following example demonstrates grouping, sorting, and filtering data in a PagedCollectionView and displaying the grouped, sorted, and filtered data in a DataGrid. A collection of Task objects is wrapped in a PagedCollectionView. The PagedCollectionView is used as the ItemsSource for the DataGrid. Grouping, sorting, and filtering are performed on the PagedCollectionView and are displayed in the DataGrid UI.
<!-- NOTE:
By convention, the sdk prefix indicates a URI-based XAML namespace declaration
for Silverlight SDK client libraries. This namespace declaration is valid for
Silverlight 4 only. In Silverlight 3, you must use individual XAML namespace
declarations for each CLR assembly and namespace combination outside the scope
of the default Silverlight XAML namespace. For more information, see the help
topic "Prefixes and Mappings for Silverlight Libraries".
-->
<UserControl x:Class="DataGridGrouping.MainPage"
xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Width="600" Height="500">
<Grid x:Name="LayoutRoot" Background="White" Margin="10">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<sdk:DataGrid x:Name="dataGrid1">
<sdk:DataGrid.RowGroupHeaderStyles>
<!-- Style for groups at top level -->
<Style TargetType="sdk:DataGridRowGroupHeader">
<Setter Property="PropertyNameVisibility" Value="Collapsed" />
<Setter Property="Background" Value="#FF112255" />
<Setter Property="Foreground" Value="#FFEEEEEE" />
<Setter Property="SublevelIndent" Value="15" />
</Style>
<!-- Style for groups under the top level -->
<Style TargetType="sdk:DataGridRowGroupHeader">
<Setter Property="Background" Value="#44225566" />
</Style>
</sdk:DataGrid.RowGroupHeaderStyles>
</sdk:DataGrid>
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="Filter Completed Tasks " />
<CheckBox Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" />
<Button Content="Expand All Groups" Margin="10,0,0,0" Click="ExpandButton_Click" />
<Button Content="Collapse All Groups" Margin="5,0,0,0" Click="CollapseButton_Click" />
</StackPanel>
</Grid>
</UserControl>
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Windows.Data
Partial Public Class MainPage
Inherits UserControl
Public Sub New()
InitializeComponent()
' Create a collection to store task data.
Dim taskList As ObservableCollection(Of Task) = New ObservableCollection(Of Task)
' Generate some task data and add it to the task list.
For index = 1 To 14
taskList.Add(New Task() With _
{.ProjectName = "Project " & ((index Mod 3) + 1).ToString(), _
.TaskName = "Task " & index.ToString(), _
.DueDate = Date.Now.AddDays(index), _
.Complete = (index Mod 2 = 0), _
.Notes = "Task " & index.ToString() & " is due on " & Date.Now.AddDays(index) & ". Lorum ipsum..." _
})
Next
Dim taskListView As New PagedCollectionView(taskList)
Me.dataGrid1.ItemsSource = taskListView
If taskListView.CanGroup = True Then
' Group tasks by ProjectName...
taskListView.GroupDescriptions.Add(New PropertyGroupDescription("ProjectName"))
' Then group by Complete status.
taskListView.GroupDescriptions.Add(New PropertyGroupDescription("Complete"))
End If
If taskListView.CanSort = True Then
'// By default, sort by ProjectName.
taskListView.SortDescriptions.Add(New SortDescription("ProjectName", ListSortDirection.Ascending))
End If
End Sub
Private Sub CheckBox_Checked(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim pcv As PagedCollectionView = Me.dataGrid1.ItemsSource
If pcv IsNot Nothing & pcv.CanFilter = True Then
' Apply the filter.
pcv.Filter = New Predicate(Of Object)(AddressOf FilterCompletedTasks)
End If
End Sub
Private Sub CheckBox_Unchecked(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim pcv As PagedCollectionView = Me.dataGrid1.ItemsSource
If pcv IsNot Nothing Then
' Remove the filter.
pcv.Filter = Nothing
End If
End Sub
Public Function FilterCompletedTasks(ByVal t As Object) As Boolean
Dim _task As New Task
_task = t
Return _task.Complete = False
End Function
Private Sub ExpandButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim pcv As PagedCollectionView = Me.dataGrid1.ItemsSource
Try
For Each group As CollectionViewGroup In pcv.Groups
dataGrid1.ExpandRowGroup(group, True)
Next
Catch ex As Exception
' Could not expand group.
MessageBox.Show(ex.Message)
End Try
End Sub
Private Sub CollapseButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim pcv As PagedCollectionView = Me.dataGrid1.ItemsSource
Try
For Each group As CollectionViewGroup In pcv.Groups
dataGrid1.ScrollIntoView(group, Nothing)
dataGrid1.CollapseRowGroup(group, True)
Next
Catch ex As Exception
' Could not collapse group.
MessageBox.Show(ex.Message)
End Try
End Sub
End Class
Public Class Task
Implements System.ComponentModel.INotifyPropertyChanged, IEditableObject
' The Task class implements INotifyPropertyChanged and IEditableObject
' so that the datagrid can properly respond to changes to the
' data collection and edits made in the DataGrid.
' Private task data.
Private m_ProjectName As String = String.Empty
Private m_TaskName As String = String.Empty
Private m_DueDate As DateTime = Date.Now
Private m_Complete As Boolean = False
Private m_Notes As String = String.Empty
' Data for undoing canceled edits.
Private temp_Task As Task = Nothing
Private m_Editing As Boolean = False
' Public properties.
Public Property ProjectName() As String
Get
Return Me.m_ProjectName
End Get
Set(ByVal value As String)
If Not value = Me.m_ProjectName Then
Me.m_ProjectName = value
NotifyPropertyChanged("ProjectName")
End If
End Set
End Property
Public Property TaskName() As String
Get
Return Me.m_TaskName
End Get
Set(ByVal value As String)
If Not value = Me.m_TaskName Then
Me.m_TaskName = value
NotifyPropertyChanged("TaskName")
End If
End Set
End Property
Public Property DueDate() As Date
Get
Return Me.m_DueDate
End Get
Set(ByVal value As Date)
If Not value = Me.m_DueDate Then
Me.m_DueDate = value
NotifyPropertyChanged("DueDate")
End If
End Set
End Property
Public Property Complete() As Boolean
Get
Return Me.m_Complete
End Get
Set(ByVal value As Boolean)
If Not value = Me.m_Complete Then
Me.m_Complete = value
NotifyPropertyChanged("Complete")
End If
End Set
End Property
Public Property Notes() As String
Get
Return Me.m_Notes
End Get
Set(ByVal value As String)
If Not value = Me.m_Notes Then
Me.m_Notes = value
NotifyPropertyChanged("Notes")
End If
End Set
End Property
' Implement INotifyPropertyChanged interface.
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
' Implement IEditableObject interface.
Public Sub BeginEdit() Implements IEditableObject.BeginEdit
If Not Me.m_Editing Then
Me.temp_Task = Me.MemberwiseClone()
Me.m_Editing = True
End If
End Sub
Public Sub CancelEdit() Implements IEditableObject.CancelEdit
If m_Editing = True Then
Me.ProjectName = Me.temp_Task.ProjectName
Me.TaskName = Me.temp_Task.TaskName
Me.DueDate = Me.temp_Task.DueDate
Me.Complete = Me.temp_Task.Complete
Me.Notes = Me.temp_Task.Notes
Me.m_Editing = False
End If
End Sub
Public Sub EndEdit() Implements IEditableObject.EndEdit
If m_Editing = True Then
Me.temp_Task = Nothing
Me.m_Editing = False
End If
End Sub
End Class
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace DataGridGrouping
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
// Create a collection to store task data.
ObservableCollection<Task> taskList = new ObservableCollection<Task>();
// Generate some task data and add it to the task list.
for (int i = 1; i <= 14; i++)
{
taskList.Add(new Task()
{
ProjectName = "Project " + ((i % 3) + 1).ToString(),
TaskName = "Task " + i.ToString(),
DueDate = DateTime.Now.AddDays(i),
Complete = (i % 2 == 0),
Notes = "Task " + i.ToString() + " is due on "
+ DateTime.Now.AddDays(i) + ". Lorum ipsum..."
});
}
PagedCollectionView taskListView = new PagedCollectionView(taskList);
this.dataGrid1.ItemsSource = taskListView;
if (taskListView.CanGroup == true)
{
// Group tasks by ProjectName...
taskListView.GroupDescriptions.Add(new PropertyGroupDescription("ProjectName"));
// Then group by Complete status.
taskListView.GroupDescriptions.Add(new PropertyGroupDescription("Complete"));
}
if (taskListView.CanSort == true)
{
// By default, sort by ProjectName.
taskListView.SortDescriptions.Add(new SortDescription("ProjectName", ListSortDirection.Ascending));
}
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
PagedCollectionView pcv = this.dataGrid1.ItemsSource as PagedCollectionView;
if (pcv != null && pcv.CanFilter == true)
{
// Apply the filter.
pcv.Filter = new Predicate<object>(FilterCompletedTasks);
}
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
PagedCollectionView pcv = this.dataGrid1.ItemsSource as PagedCollectionView;
if (pcv != null)
{
// Remove the filter.
pcv.Filter = null;
}
}
public bool FilterCompletedTasks(object t)
{
Task task = t as Task;
return (task.Complete == false);
}
private void ExpandButton_Click(object sender, RoutedEventArgs e)
{
PagedCollectionView pcv = dataGrid1.ItemsSource as PagedCollectionView;
try
{
foreach (CollectionViewGroup group in pcv.Groups)
{
dataGrid1.ExpandRowGroup(group, true);
}
}
catch (Exception ex)
{
// Could not expand group.
MessageBox.Show(ex.Message);
}
}
private void CollapseButton_Click(object sender, RoutedEventArgs e)
{
PagedCollectionView pcv = dataGrid1.ItemsSource as PagedCollectionView;
try
{
foreach (CollectionViewGroup group in pcv.Groups)
{
dataGrid1.ScrollIntoView(group, null);
dataGrid1.CollapseRowGroup(group, true);
}
}
catch (Exception ex)
{
// Could not collapse group.
MessageBox.Show(ex.Message);
}
}
}
public class Task : System.ComponentModel.INotifyPropertyChanged, IEditableObject
{
// The Task class implements INotifyPropertyChanged and IEditableObject
// so that the datagrid can properly respond to changes to the
// data collection and edits made in the DataGrid.
// Private task data.
private string m_ProjectName = string.Empty;
private string m_TaskName = string.Empty;
private DateTime m_DueDate = DateTime.Now;
private bool m_Complete = false;
private string m_Notes = string.Empty;
// Data for undoing canceled edits.
private Task temp_Task = null;
private bool m_Editing = false;
// Public properties.
[Display(Name = "Project")]
public string ProjectName
{
get { return this.m_ProjectName; }
set
{
if (value != this.m_ProjectName)
{
this.m_ProjectName = value;
NotifyPropertyChanged("ProjectName");
}
}
}
[Display(Name = "Task")]
public string TaskName
{
get { return this.m_TaskName; }
set
{
if (value != this.m_TaskName)
{
this.m_TaskName = value;
NotifyPropertyChanged("TaskName");
}
}
}
[Display(Name = "Due Date")]
public DateTime DueDate
{
get { return this.m_DueDate; }
set
{
if (value != this.m_DueDate)
{
this.m_DueDate = value;
NotifyPropertyChanged("DueDate");
}
}
}
public bool Complete
{
get { return this.m_Complete; }
set
{
if (value != this.m_Complete)
{
this.m_Complete = value;
NotifyPropertyChanged("Complete");
}
}
}
public string Notes
{
get { return this.m_Notes; }
set
{
if (value != this.m_Notes)
{
this.m_Notes = value;
NotifyPropertyChanged("Notes");
}
}
}
// Implement INotifyPropertyChanged interface.
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// Implement IEditableObject interface.
public void BeginEdit()
{
if (m_Editing == false)
{
temp_Task = this.MemberwiseClone() as Task;
m_Editing = true;
}
}
public void CancelEdit()
{
if (m_Editing == true)
{
this.ProjectName = temp_Task.ProjectName;
this.TaskName = temp_Task.TaskName;
this.DueDate = temp_Task.DueDate;
this.Complete = temp_Task.Complete;
this.Notes = temp_Task.Notes;
m_Editing = false;
}
}
public void EndEdit()
{
if (m_Editing == true)
{
temp_Task = null;
m_Editing = false;
}
}
}
}