How to group items in a list or grid (XAML)

[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]

When a ListView or GridView is bound to a data source that contains groups of data, you can display the grouped data in the list or grid. There are different ways that you can group data coming into your app. The data source might be a list of items where each item also contains a list of items. For example, you could display a list of projects, where each project has a property that is a list of activities. Or you might use a LINQ query that returns grouped items. Here, we see how to display these groups in a list or grid.

Roadmap: How does this topic relate to others? See:

What you need to know

Technologies

Prerequisites

Instructions

Step 1: Bind the ItemsSource to a CollectionViewSource

To display grouped data, you must set the ItemsControl.ItemsSource property to a CollectionViewSource that has its IsSourceGrouped property set to true. The CollectionViewSource acts as a proxy over the collection class to enable currency and grouping support.

To use a grouped data source

  1. Set the ItemsSource property of the list or grid to an instance of CollectionViewSource.
  2. Set the CollectionViewSource.Source property to the grouped data source.
  3. Set the CollectionViewSource.IsSourceGrouped property to true.

Here, a collection of Activity items is added to a list. Each Activity item has a Project property that specifies a project that the Activity is associated with. A LINQ query is used to group the activities by project. The resulting grouped data is set as the CollectionViewSource.Source. The definition of the Activity class is shown later in the full example code.

List<Activity> Activities = new List<Activity>();

Activities.Add(new Activity() 
    { Name = "Activity 1", Complete = true, 
        DueDate = startDate.AddDays(4), Project = "Project 1" });
Activities.Add(new Activity() 
    { Name = "Activity 2", Complete = true, 
        DueDate = startDate.AddDays(5), Project = "Project 1" });
Activities.Add(new Activity() 
    { Name = "Activity 3", Complete = false, 
        DueDate = startDate.AddDays(7), Project = "Project 1" });
Activities.Add(new Activity() 
    { Name = "Activity 4", Complete = false, 
        DueDate = startDate.AddDays(9), Project = "Project 1" });
Activities.Add(new Activity() 
    { Name = "Activity 5", Complete = false, 
        DueDate = startDate.AddDays(14), Project = "Project 1" });
Activities.Add(new Activity() 
    { Name = "Activity A", Complete = true, 
        DueDate = startDate.AddDays(2), Project = "Project 2" });
Activities.Add(new Activity() 
    { Name = "Activity B", Complete = false, 
        DueDate = startDate.AddDays(4), Project = "Project 2" });
Activities.Add(new Activity() 
    { Name = "Activity C", Complete = true, 
        DueDate = startDate.AddDays(5), Project = "Project 2" });
Activities.Add(new Activity() 
    { Name = "Activity D", Complete = false, 
        DueDate = startDate.AddDays(9), Project = "Project 2" });
Activities.Add(new Activity() 
    { Name = "Activity E", Complete = false, 
        DueDate = startDate.AddDays(18), Project = "Project 2" });

var result = from act in Activities group act by act.Project into grp orderby grp.Key select grp;
cvsActivities.Source = result;

The CollectionViewSource is declared in XAML as a resource of the main page. The IsSourceGrouped property is set to true. The ItemsSource property of the ListView is bound to the CollectionViewSource.

<CollectionViewSource x:Name="cvsActivities" IsSourceGrouped="True"/>

You can display grouped data from a collection where each item has a list of child items that make a group. In this case, you set the CollectionViewSource.ItemsPath property to specify the property of the item that contains the collection of child items.

To use a data source where each item contains a collection of child items

  1. Follow steps 1 -3 shown earlier in this topic.
  2. Set the CollectionViewSource.ItemsPath property to the property that contains the collection of child items.

Here, you create list of Project items to display in a GridView. The data is set up so that each Project item has an Activities property that is a list of Activity items. The list of projects is set as the CollectionViewSource.Source. The definitions of the Project and Activity classes are shown later in the full example code.

List<Project> Projects = new List<Project>();

Project newProject = new Project();
newProject.Name = "Project 1";
newProject.Activities.Add(new Activity() 
    { Name = "Activity 1", Complete = true, DueDate = startDate.AddDays(4) });
newProject.Activities.Add(new Activity() 
    { Name = "Activity 2", Complete = true, DueDate = startDate.AddDays(5) });
newProject.Activities.Add(new Activity() 
    { Name = "Activity 3", Complete = false, DueDate = startDate.AddDays(7) });
newProject.Activities.Add(new Activity() 
    { Name = "Activity 4", Complete = false, DueDate = startDate.AddDays(9) });
newProject.Activities.Add(new Activity() 
    { Name = "Activity 5", Complete = false, DueDate = startDate.AddDays(14) });
Projects.Add(newProject);

newProject = new Project();
newProject.Name = "Project 2";
newProject.Activities.Add(new Activity() 
    { Name = "Activity A", Complete = true, DueDate = startDate.AddDays(2) });
newProject.Activities.Add(new Activity() 
    { Name = "Activity B", Complete = false, DueDate = startDate.AddDays(3) });
newProject.Activities.Add(new Activity() 
    { Name = "Activity C", Complete = true, DueDate = startDate.AddDays(5) });
newProject.Activities.Add(new Activity() 
    { Name = "Activity D", Complete = false, DueDate = startDate.AddDays(9) });
newProject.Activities.Add(new Activity() 
    { Name = "Activity E", Complete = false, DueDate = startDate.AddDays(18) });
Projects.Add(newProject);

newProject = new Project();
newProject.Name = "Project 3";
Projects.Add(newProject);

cvsProjects.Source = Projects;

The CollectionViewSource is declared in XAML as a resource of the main page. The IsSourceGrouped property is set to true, and the ItemsPath property is set to the Project.Activities property that contains the collection of Activity items. The ItemsSource property of the GridView is bound to the CollectionViewSource.

<CollectionViewSource x:Name="cvsProjects" IsSourceGrouped="True" ItemsPath="Activities"/>

For more info about data binding and grouping data, see Data binding with XAML.

Step 2: Set a GroupStyle to specify how groups are displayed

To display the grouped data in the list or grid, you define a GroupStyle that specifies a header template for each group. By default, the group header is displayed for empty groups. To hide the header for empty groups, set the HidesIfEmpty property to true.

To specify how groups are displayed

  1. Define a GroupStyle for the ListView or GridView.
  2. In the GroupStyle, define a HeaderTemplate that specifies the appearance of the group headers.
  3. To hide group headers for empty groups, set the GroupStyle.HidesIfEmpty property to true.

Here, you use a GridView to display the Projects list you created earlier. The appearance of individual items in each group is defined by the ItemTemplate, just as it is for data that's not grouped. The ItemsPanel specifies how the groups are arranged in the GridView. The Projects list contains a project with an empty Activities collection. You set the HidesIfEmpty property to true to hide this empty group.

<GridView ItemsSource="{Binding Source={StaticResource cvsProjects}}" 
  Margin="0,120,0,0" MaxHeight="500" Grid.Column="1">
    <GridView.ItemTemplate>
        <DataTemplate>
            <StackPanel Margin="20">
                <TextBlock Text="{Binding Name}" FontWeight="Bold" 
                           Style="{StaticResource BaseTextBlockStyle}"/>
                <TextBlock Text="{Binding DueDate}" TextWrapping="NoWrap" 
                           Style="{StaticResource BodyTextBlockStyle}" />
                <CheckBox Content="Complete" IsChecked="{Binding Complete}" 
                          IsEnabled="False"/>
            </StackPanel>
        </DataTemplate>
    </GridView.ItemTemplate>
    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <ItemsWrapGrid MaximumRowsOrColumns="3"/>
        </ItemsPanelTemplate>
    </GridView.ItemsPanel>

    <GridView.GroupStyle>
        <GroupStyle HidesIfEmpty="True">
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <Grid Background="LightGray" Margin="0">
                        <TextBlock Text='{Binding Name}' 
                                   Foreground="Black" Margin="30"
                                   Style="{StaticResource HeaderTextBlockStyle}"/>
                    </Grid>
                </DataTemplate>
            </GroupStyle.HeaderTemplate>

        </GroupStyle>
    </GridView.GroupStyle>
</GridView>

Step 3: Set a GroupStyle using a GroupStyleSelector

To use GroupStyle that's defined as a resource, or to apply a GroupStyle using conditional logic, you use a GroupStyleSelector.

To use a GroupStyleSelector

  1. Create a subclass of the GroupStyleSelector class and override the SelectGroupStyleCore method.

    Here, you create a class named ListGroupStyleSelector that's derived from GroupStyleSelector. You override the SelectGroupStyleCore method to return a GroupStyle resource that has the key listViewGroupStyle. This resource is declared in the App.xaml file. You can apply conditional logic here if you need to choose between different group styles to return.

    public class ListGroupStyleSelector : GroupStyleSelector
    {
        protected override GroupStyle SelectGroupStyleCore(object group, uint level)
        {
            return (GroupStyle)App.Current.Resources["listViewGroupStyle"];
        }
    }
    
  2. In your XAML file, define an instance of your group style selector class.

    This resource is declared in the Resources section of the page.

    <local:ListGroupStyleSelector x:Key="listGroupStyleSelector"/>
    
  3. Set your item control's GroupStyleSelector property to the resource you defined in the previous step.

    Here, you create a ListView to display the Activities list you created previously. The ItemTemplate is set to a resource with the key listViewItemTemplate, and the GroupStyleSelector property is set to a resource with the key listGroupStyleSelector. These resources are shown later in the full example code.

    <ListView ItemsSource="{Binding Source={StaticResource cvsActivities}}"
      ItemTemplate="{StaticResource listViewItemTemplate}"
      GroupStyleSelector="{StaticResource listGroupStyleSelector}"
      Margin="120" Width="320"/>
    

Remarks

Here's what the list and grid views look like when you run the app.

 

For more code examples that show grouping and displaying data, see these samples:

Complete example

Here's the full code for the examples used in this topic.

MainPage.xaml

<Page
    x:Class="ItemsControlGroupingSnippets.MainPage"
    IsTabStop="false"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ItemsControlGroupingSnippets"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <UserControl.Resources>
        <CollectionViewSource x:Name="cvsActivities" IsSourceGrouped="True"/>

        <CollectionViewSource x:Name="cvsProjects" IsSourceGrouped="True" ItemsPath="Activities"/>

        <local:ListGroupStyleSelector x:Key="listGroupStyleSelector"/>
    </UserControl.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="600"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <ListView ItemsSource="{Binding Source={StaticResource cvsActivities}}"
          ItemTemplate="{StaticResource listViewItemTemplate}"
          GroupStyleSelector="{StaticResource listGroupStyleSelector}"
          Margin="120" Width="320"/>

        <GridView ItemsSource="{Binding Source={StaticResource cvsProjects}}" 
          Margin="0,120,0,0" MaxHeight="500" Grid.Column="1">
            <GridView.ItemTemplate>
                <DataTemplate>
                    <StackPanel Margin="20">
                        <TextBlock Text="{Binding Name}" FontWeight="Bold" 
                                   Style="{StaticResource BaseTextBlockStyle}"/>
                        <TextBlock Text="{Binding DueDate}" TextWrapping="NoWrap" 
                                   Style="{StaticResource BodyTextBlockStyle}" />
                        <CheckBox Content="Complete" IsChecked="{Binding Complete}" 
                                  IsEnabled="False"/>
                    </StackPanel>
                </DataTemplate>
            </GridView.ItemTemplate>
            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsWrapGrid MaximumRowsOrColumns="3"/>
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>

            <GridView.GroupStyle>
                <GroupStyle HidesIfEmpty="True">
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <Grid Background="LightGray" Margin="0">
                                <TextBlock Text='{Binding Name}' 
                                           Foreground="Black" Margin="30"
                                           Style="{StaticResource HeaderTextBlockStyle}"/>
                            </Grid>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>

                </GroupStyle>
            </GridView.GroupStyle>
        </GridView>
    </Grid>
</Page>

MainPage.xaml code-behind

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

namespace ItemsControlGroupingSnippets
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        DateTime startDate;
        public MainPage()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            DateTime.TryParse("1/1/2014", out startDate);

            PopulateProjects();
            PopulateActivities();
        }

        private void PopulateActivities()
        {
            List<Activity> Activities = new List<Activity>();

            Activities.Add(new Activity() 
                { Name = "Activity 1", Complete = true, 
                    DueDate = startDate.AddDays(4), Project = "Project 1" });
            Activities.Add(new Activity() 
                { Name = "Activity 2", Complete = true, 
                    DueDate = startDate.AddDays(5), Project = "Project 1" });
            Activities.Add(new Activity() 
                { Name = "Activity 3", Complete = false, 
                    DueDate = startDate.AddDays(7), Project = "Project 1" });
            Activities.Add(new Activity() 
                { Name = "Activity 4", Complete = false, 
                    DueDate = startDate.AddDays(9), Project = "Project 1" });
            Activities.Add(new Activity() 
                { Name = "Activity 5", Complete = false, 
                    DueDate = startDate.AddDays(14), Project = "Project 1" });
            Activities.Add(new Activity() 
                { Name = "Activity A", Complete = true, 
                    DueDate = startDate.AddDays(2), Project = "Project 2" });
            Activities.Add(new Activity() 
                { Name = "Activity B", Complete = false, 
                    DueDate = startDate.AddDays(4), Project = "Project 2" });
            Activities.Add(new Activity() 
                { Name = "Activity C", Complete = true, 
                    DueDate = startDate.AddDays(5), Project = "Project 2" });
            Activities.Add(new Activity() 
                { Name = "Activity D", Complete = false, 
                    DueDate = startDate.AddDays(9), Project = "Project 2" });
            Activities.Add(new Activity() 
                { Name = "Activity E", Complete = false, 
                    DueDate = startDate.AddDays(18), Project = "Project 2" });

            var result = from act in Activities group act by act.Project into grp orderby grp.Key select grp;
            cvsActivities.Source = result;
        }

        private void PopulateProjects()
        {
            List<Project> Projects = new List<Project>();

            Project newProject = new Project();
            newProject.Name = "Project 1";
            newProject.Activities.Add(new Activity() 
                { Name = "Activity 1", Complete = true, DueDate = startDate.AddDays(4) });
            newProject.Activities.Add(new Activity() 
                { Name = "Activity 2", Complete = true, DueDate = startDate.AddDays(5) });
            newProject.Activities.Add(new Activity() 
                { Name = "Activity 3", Complete = false, DueDate = startDate.AddDays(7) });
            newProject.Activities.Add(new Activity() 
                { Name = "Activity 4", Complete = false, DueDate = startDate.AddDays(9) });
            newProject.Activities.Add(new Activity() 
                { Name = "Activity 5", Complete = false, DueDate = startDate.AddDays(14) });
            Projects.Add(newProject);

            newProject = new Project();
            newProject.Name = "Project 2";
            newProject.Activities.Add(new Activity() 
                { Name = "Activity A", Complete = true, DueDate = startDate.AddDays(2) });
            newProject.Activities.Add(new Activity() 
                { Name = "Activity B", Complete = false, DueDate = startDate.AddDays(3) });
            newProject.Activities.Add(new Activity() 
                { Name = "Activity C", Complete = true, DueDate = startDate.AddDays(5) });
            newProject.Activities.Add(new Activity() 
                { Name = "Activity D", Complete = false, DueDate = startDate.AddDays(9) });
            newProject.Activities.Add(new Activity() 
                { Name = "Activity E", Complete = false, DueDate = startDate.AddDays(18) });
            Projects.Add(newProject);

            newProject = new Project();
            newProject.Name = "Project 3";
            Projects.Add(newProject);

            cvsProjects.Source = Projects;
        }

    }

    public class ListGroupStyleSelector : GroupStyleSelector
    {
        protected override GroupStyle SelectGroupStyleCore(object group, uint level)
        {
            return (GroupStyle)App.Current.Resources["listViewGroupStyle"];
        }
    }

    public class Project
    {
        public Project()
        {
            Activities = new ObservableCollection<Activity>();
        }

        public string Name { get; set; }
        public ObservableCollection<Activity> Activities { get; private set; }
    }

    public class Activity
    {
        public string Name { get; set; }
        public DateTime DueDate { get; set; }
        public bool Complete { get; set; }
        public string Project { get; set; }
    }
}

App.xaml

<Application
    x:Class="ItemsControlGroupingSnippets.App"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ItemsControlGroupingSnippets">

    <Application.Resources>
        <ResourceDictionary>
            <DataTemplate x:Key="listViewItemTemplate">
                <StackPanel Width="320" Margin="10">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" FontWeight="Bold" Style="{StaticResource BaseTextBlockStyle}"
                       Margin="2,0,0,0"/>
                        <TextBlock Text="{Binding DueDate}" Style="{StaticResource BodyTextBlockStyle}" TextWrapping="NoWrap"
                       Margin="20,0,0,0"/>
                    </StackPanel>
                    <CheckBox Content="Complete" IsChecked="{Binding Complete}" IsEnabled="False"/>
                </StackPanel>
            </DataTemplate>

            <GroupStyle x:Key="listViewGroupStyle">
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <Grid Background="LightGray"  >
                            <TextBlock Text='{Binding Key}' Foreground="Black" Margin="10"
                           Style="{StaticResource SubheaderTextBlockStyle}" />
                        </Grid>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ResourceDictionary>
    </Application.Resources>
</Application>

ListView

GridView

Adding ListView and GridView controls

XAML GridView grouping and SemanticZoom sample