Export (0) Print
Expand All
2 out of 6 rated this helpful - Rate this topic

How to: Group, Sort, and Filter Data in the DataGrid Control

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 CollectionView that supports these functions. You can then work with the data in the CollectionView without affecting the underlying source data. The changes in the collection view are reflected in the DataGrid user interface (UI).

The CollectionView class provides grouping and sorting functionality for a data source that implements the IEnumerable interface. The CollectionViewSource class enables you to set the properties of a CollectionView from XAML.

In this example, a collection of Task objects is bound to a CollectionViewSource. The CollectionViewSource is used as the ItemsSource for the DataGrid. Grouping, sorting, and filtering are performed on the CollectionViewSource and are displayed in the DataGrid UI.

Grouped Data in a DataGrid

Grouped data in a DataGrid

To group, sort, and filter data in a DataGrid control, you bind the DataGrid to a CollectionView that supports these functions. In this example, the DataGrid is bound to a CollectionViewSource that provides these functions for a List<T> of Task objects.

To bind a DataGrid to a CollectionViewSource

  1. Create a data collection that implements the IEnumerable interface.

    If you use List<T> to create your collection, you should create a new class that inherits from List<T> instead of instantiating an instance of List<T>. This enables you to data bind to the collection in XAML.

    Note 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. For more information, see How to: Implement Property Change Notification.

    // Requires using System.Collections.ObjectModel; 
    public class Tasks : ObservableCollection<Task>
    {
        // Creating the Tasks collection in this way enables data binding from XAML.
    }
    
  2. In XAML, create an instance of the collection class and set the x:Key Directive.

  3. In XAML, create an instance of the CollectionViewSource class, set the x:Key Directive, and set the instance of your collection class as the Source.

    <Window.Resources>
        <local:Tasks x:Key="tasks" />
        <CollectionViewSource x:Key="cvsTasks" Source="{StaticResource tasks}" 
                              Filter="CollectionViewSource_Filter">
        </CollectionViewSource>    
    </Window.Resources>
    
  4. Create an instance of the DataGrid class, and set the ItemsSource property to the CollectionViewSource.

    <DataGrid x:Name="dataGrid1" 
              ItemsSource="{Binding Source={StaticResource cvsTasks}}"
              CanUserAddRows="False">
    
  5. To access the CollectionViewSource from your code, use the GetDefaultView method to get a reference to the CollectionViewSource.

    ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
    

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 using XAML

  1. Create a PropertyGroupDescription that specifies the property to group by. You can specify the property in XAML or in code.

    1. In XAML, set the PropertyName to the name of the property to group by.

    2. In code, pass the name of the property to group by to the constructor.

  2. Add the PropertyGroupDescription to the CollectionViewSource.GroupDescriptions collection.

  3. Add additional instances of PropertyGroupDescription to the GroupDescriptions collection to add more levels of grouping.

    <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="ProjectName"/>
        <PropertyGroupDescription PropertyName="Complete"/>
    </CollectionViewSource.GroupDescriptions>
    

    ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
    if (cvTasks != null && cvTasks.CanGroup == true)
    {
        cvTasks.GroupDescriptions.Clear();
        cvTasks.GroupDescriptions.Add(new PropertyGroupDescription("ProjectName"));
        cvTasks.GroupDescriptions.Add(new PropertyGroupDescription("Complete"));
    }
    
  4. To remove a group, remove the PropertyGroupDescription from the GroupDescriptions collection.

  5. To remove all groups, call the Clear method of the GroupDescriptions collection.

    ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
    if (cvTasks != null)
    {
        cvTasks.GroupDescriptions.Clear();
    }
    

When items are grouped in the DataGrid, you can define a GroupStyle that specifies the appearance of each group. You apply the GroupStyle by adding it to the GroupStyle collection of the DataGrid. 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 GroupStyle is the CollectionViewGroup that the group represents.

To change the appearance of row group headers

  1. Create a GroupStyle that defines the appearance of the row group.

  2. Put the GroupStyle inside the <DataGrid.GroupStyle> tags.

    <DataGrid.GroupStyle>
        <!-- Style for groups at top level. -->
        <GroupStyle>
            <GroupStyle.ContainerStyle>
                <Style TargetType="{x:Type GroupItem}">
                    <Setter Property="Margin" Value="0,0,0,5"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type GroupItem}">
                                <Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
                                    <Expander.Header>
                                        <DockPanel>
                                            <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
                                            <TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}"/>
                                        </DockPanel>
                                    </Expander.Header>
                                    <Expander.Content>
                                        <ItemsPresenter />
                                    </Expander.Content>
                                </Expander>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </GroupStyle.ContainerStyle>
        </GroupStyle>
        <!-- Style for groups under the top level. -->
        <GroupStyle>
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <DockPanel Background="LightBlue">
                        <TextBlock Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Foreground="Blue" Margin="30,0,0,0" Width="100"/>
                        <TextBlock Text="{Binding Path=ItemCount}" Foreground="Blue"/>
                    </DockPanel>
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
        </GroupStyle>
    </DataGrid.GroupStyle>
    

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

  1. Create a SortDescription that specifies the property to sort by. You can specify the property in XAML or in code.

    1. In XAML, set the PropertyName to the name of the property to sort by.

    2. In code, pass the name of the property to sort by and the ListSortDirection to the constructor.

  2. Add the SortDescription to the CollectionViewSource.SortDescriptions collection.

  3. Add additional instances of SortDescription to the SortDescriptions collection to sort by additional properties.

    <CollectionViewSource.SortDescriptions>
        <!-- Requires 'xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"' declaration. -->
        <scm:SortDescription PropertyName="ProjectName"/>
        <scm:SortDescription PropertyName="Complete" />
        <scm:SortDescription PropertyName="DueDate" />
    </CollectionViewSource.SortDescriptions>
    

    // Requires using System.ComponentModel;
    ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
    if (cvTasks != null && cvTasks.CanSort == true)
    {
        cvTasks.SortDescriptions.Clear();
        cvTasks.SortDescriptions.Add(new SortDescription("ProjectName", ListSortDirection.Ascending));
        cvTasks.SortDescriptions.Add(new SortDescription("Complete", ListSortDirection.Ascending));
        cvTasks.SortDescriptions.Add(new SortDescription("DueDate", ListSortDirection.Ascending));
    }
    

To filter items in a DataGrid using a CollectionViewSource, you provide the filtering logic in the handler for the CollectionViewSource.Filter event.

To filter items in a DataGrid

  1. Add a handler for the CollectionViewSource.Filter event.

  2. In the Filter event handler, define the filtering logic.

    The filter will be applied every time the view is refreshed.

    <CollectionViewSource x:Key="cvsTasks" Source="{StaticResource tasks}" 
                          Filter="CollectionViewSource_Filter">
    

    private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
    {
        Task t = e.Item as Task;
        if (t != null)
        // If filter is turned on, filter completed items.
        {
            if (this.cbCompleteFilter.IsChecked == true && t.Complete == true)
                e.Accepted = false;
            else
                e.Accepted = true;
        }
    }
    

Alternatively, you can filter items in a DataGrid by creating a method that provides the filtering logic and setting the CollectionView.Filter property to apply the filter. To see an example of this method, see How to: Filter Data in a View.

The following example demonstrates grouping, sorting, and filtering Task data in a CollectionViewSource and displaying the grouped, sorted, and filtered Task data in a DataGrid. The CollectionViewSource is used as the ItemsSource for the DataGrid. Grouping, sorting, and filtering are performed on the CollectionViewSource and are displayed in the DataGrid UI.

To test this example, you will need to adjust the DGGroupSortFilterExample name to match your project name. If you are using Visual Basic, you will need to change the class name for Window to the following.

<Window x:Class="MainWindow"

<Window x:Class="DGGroupSortFilterExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DGGroupSortFilterExample"
        xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
        Title="Group, Sort, and Filter Example" Height="575" Width="525">
    <Window.Resources>
        <local:CompleteConverter x:Key="completeConverter" />
        <local:Tasks x:Key="tasks" />
        <CollectionViewSource x:Key="cvsTasks" Source="{StaticResource tasks}" 
                              Filter="CollectionViewSource_Filter">
            <CollectionViewSource.SortDescriptions>
                <!-- Requires 'xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"' declaration. -->
                <scm:SortDescription PropertyName="ProjectName"/>
                <scm:SortDescription PropertyName="Complete" />
                <scm:SortDescription PropertyName="DueDate" />
            </CollectionViewSource.SortDescriptions>
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="ProjectName"/>
                <PropertyGroupDescription PropertyName="Complete"/>
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="30" />
        </Grid.RowDefinitions>
        <DataGrid x:Name="dataGrid1" 
                  ItemsSource="{Binding Source={StaticResource cvsTasks}}"
                  CanUserAddRows="False">
            <DataGrid.GroupStyle>
                <!-- Style for groups at top level. -->
                <GroupStyle>
                    <GroupStyle.ContainerStyle>
                        <Style TargetType="{x:Type GroupItem}">
                            <Setter Property="Margin" Value="0,0,0,5"/>
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="{x:Type GroupItem}">
                                        <Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
                                            <Expander.Header>
                                                <DockPanel>
                                                    <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
                                                    <TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}"/>
                                                </DockPanel>
                                            </Expander.Header>
                                            <Expander.Content>
                                                <ItemsPresenter />
                                            </Expander.Content>
                                        </Expander>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </GroupStyle.ContainerStyle>
                </GroupStyle>
                <!-- Style for groups under the top level. -->
                <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <DockPanel Background="LightBlue">
                                <TextBlock Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Foreground="Blue" Margin="30,0,0,0" Width="100"/>
                                <TextBlock Text="{Binding Path=ItemCount}" Foreground="Blue"/>
                            </DockPanel>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                </GroupStyle>
            </DataGrid.GroupStyle>
            <DataGrid.RowStyle>
                <Style TargetType="DataGridRow">
                    <Setter Property="Foreground" Value="Black" />
                    <Setter Property="Background" Value="White" />
                </Style>
            </DataGrid.RowStyle>
        </DataGrid>
        <StackPanel Orientation="Horizontal" Grid.Row="1">
            <TextBlock Text=" Filter completed items " VerticalAlignment="Center" />
            <CheckBox x:Name="cbCompleteFilter" VerticalAlignment="Center"
                      Checked="CompleteFilter_Changed" Unchecked="CompleteFilter_Changed" />
            <Button Content="Remove Groups" Margin="10,2,2,2" Click="UngroupButton_Click" />
            <Button Content="Group by Project/Status" Margin="2" Click="GroupButton_Click" />
        </StackPanel>
    </Grid>
</Window>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

namespace DGGroupSortFilterExample
{
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // Get a reference to the tasks collection.
            Tasks _tasks = (Tasks)this.Resources["tasks"];

            // Generate some task data and add it to the task list. 
            for (int i = 1; i <= 14; i++)
            {
                _tasks.Add(new Task()
                {
                    ProjectName = "Project " + ((i % 3) + 1).ToString(),
                    TaskName = "Task " + i.ToString(),
                    DueDate = DateTime.Now.AddDays(i),
                    Complete = (i % 2 == 0)
                });
            }
        }

        private void UngroupButton_Click(object sender, RoutedEventArgs e)
        {
            ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
            if (cvTasks != null)
            {
                cvTasks.GroupDescriptions.Clear();
            }
        }

        private void GroupButton_Click(object sender, RoutedEventArgs e)
        {
            ICollectionView cvTasks = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
            if (cvTasks != null && cvTasks.CanGroup == true)
            {
                cvTasks.GroupDescriptions.Clear();
                cvTasks.GroupDescriptions.Add(new PropertyGroupDescription("ProjectName"));
                cvTasks.GroupDescriptions.Add(new PropertyGroupDescription("Complete"));
            }
        }

        private void CompleteFilter_Changed(object sender, RoutedEventArgs e)
        {
            // Refresh the view to apply filters.
            CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource).Refresh();
        }

        private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
        {
            Task t = e.Item as Task;
            if (t != null)
            // If filter is turned on, filter completed items.
            {
                if (this.cbCompleteFilter.IsChecked == true && t.Complete == true)
                    e.Accepted = false;
                else
                    e.Accepted = true;
            }
        }
    }

    [ValueConversion(typeof(Boolean), typeof(String))]
    public class CompleteConverter : IValueConverter
    {
        // This converter changes the value of a Tasks Complete status from true/false to a string value of 
        // "Complete"/"Active" for use in the row group header.
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool complete = (bool)value;
            if (complete)
                return "Complete";
            else 
                return "Active";
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string strComplete = (string)value;
            if (strComplete == "Complete")
                return true;
            else 
                return false;
        }
    }

    // Task Class 
    // Requires using System.ComponentModel; 
    public class Task : 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;

        // Data for undoing canceled edits. 
        private Task temp_Task = null;
        private bool m_Editing = false;

        // Public properties.  
        public string ProjectName
        {
            get { return this.m_ProjectName; }
            set
            {
                if (value != this.m_ProjectName)
                {
                    this.m_ProjectName = value;
                    NotifyPropertyChanged("ProjectName");
                }
            }
        }

        public string TaskName
        {
            get { return this.m_TaskName; }
            set
            {
                if (value != this.m_TaskName)
                {
                    this.m_TaskName = value;
                    NotifyPropertyChanged("TaskName");
                }
            }
        }

        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");
                }
            }
        }

        // 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;
                m_Editing = false;
            }
        }

        public void EndEdit()
        {
            if (m_Editing == true)
            {
                temp_Task = null;
                m_Editing = false;
            }
        }
    }
    // Requires using System.Collections.ObjectModel; 
    public class Tasks : ObservableCollection<Task>
    {
        // Creating the Tasks collection in this way enables data binding from XAML.
    }
}
Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.