ListView Grouping with UWP

Grouping a list of objects is an often used feature with applications. Using the CollectionViewSource and the ListView this can easily be done with XAML-based applications. This article gives a simple example showing grouping using the Universal Windows Platform (UWP) and compiled data binding.

Groups

Data

First, a data source is needed. I defined the type MenuItem with the properties Day and Title. The data will be grouped based on the day, and show menu items for every day of a week.

public class MenuItem : BindableBase
{
    public int MenuItemId { get; set; }

    private DateTime _day;
    public DateTime Day
    {
        get { return _day; }
        set { SetProperty(ref _day, value); }
    }
    private string _title;
    public string Title
    {
        get { return _title; }
        set { SetProperty(ref _title, value); }
    }
}

The class SampleDataService returns a list of menus with the GetMenus method. LINQ is used to generate two menu items for a day for seven days.

public class SampleDataService : IDataService
{
    private readonly List<MenuItem> _data;
    public SampleDataService()
    {
        DateTime today = DateTime.Today;
        var menus = Enumerable.Range(0, 14)
            .Select(i =>
                new MenuItem()
                {
                    MenuItemId = i,
                    Title = $"Sample Menu {i}",
                    Day = today.AddDays(x / 2)
                });
        _data = new List<MenuItem>(menus);
    }

    public IEnumerable<MenuItem> GetMenus() => _data;
}

Enumerable.Range gives an easy way to create a list of sample data.

Grouping the Data

A single group is represented by the class MenuItemsGroup. Because XAML can’t work with generic types, the class MenuItemsGroup is defined that represents a list of menu items. The interface IGrouping is returned by the LINQ GroupBy method. This interface is implemented with the MenuItemsGroup.

public class MenuItemsGroup : IGrouping<DateTime, MenuItem>
{
    private List<MenuItem> _menuItemsGroup;

    public MenuItemsGroup(DateTime key, IEnumerable<MenuItem> items)
    {
        Key = key;
        _menuItemsGroup = new List<MenuItem>(items);
    }

    public DateTime Key { get; }

    public IEnumerator<MenuItem> GetEnumerator() => 
        _menuItemsGroup.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() =>
        _menuItemsGroup.GetEnumerator();       
}

Calling the GroupBy method in the view-model groups the returned menus from the IDataService by the Day property of the MenuItem. For every group of items, a new MenuItemsGrup object is instantiated:

public class MainPageViewModel
{
    private readonly IDataService _dataService;

    public MainPageViewModel(IDataService dataService)
    {
        _dataService = dataService;
        GetData();
    }

    private void GetData()
    {
        var menus = _dataService.GetMenus();
        GroupedMenuItems =
            menus.GroupBy(m => m.Day, (key, list) => new MenuItemsGroup(key, list));
    }

    public IEnumerable<MenuItemsGroup> GroupedMenuItems { get; private set; }
}

The LINQ GroupBy method offers grouping of data passing defining a key-selector to group the data.

XAML Code – CollectionViewSource and ListView

After the functionality is in place, let’s move on to the user interface. With the XAML code of the MainPage, a CollectionViewSource is instantiated. The Source property binds to the GroupedMenuItems property of the view-model. For grouping, the IsSourceGrouped property is set to True.

<CollectionViewSource
    x:Name="CVSMenus"
    x:Key="CVSMenus"
    Source="{x:Bind ViewModel.GroupedMenuItems, Mode=OneWay}"
    IsSourceGrouped="True" />

With the ListView control in the sampe page, the ItemsSource property binds to the View of the CollectionViewSource. All items are displayed as defined by the ItemTemplate. The groups of the items are separated using the GroupStyle. These templates are defined next.

<ListView 
    ItemsSource="{x:Bind CVSMenus.View, Mode=OneWay}"
    ItemTemplate="{StaticResource MenuTemplate}"
    Margin="4"
    SelectionMode="Single"          
    ShowsScrollingPlaceholders="True">
    <ListView.GroupStyle>
        <GroupStyle HeaderTemplate="{StaticResource MenuHeaderTemplate}" />
    </ListView.GroupStyle>
</ListView>

The CollectionViewSource offers grouping from the underlying data source. With the ListView, styles can be specified how grouping of data should look like.

XAML Data Templates

Instead of defining the XAML templates in the page itself, a separate style file MenuStyles.xaml is defined. To use the new x:Bind binding expression from a resource file, the resource file needs a code-behind file with a partial class and invocation of InitializeComponent in the constructor.

public partial class MenuStyles
{
    public MenuStyles()
    {
        InitializeComponent();
    }
}

From the XAML dictionary, the class needs to be referenced using x:Class. The XAML compiler creates the second part of the class with the implementation of InitializeComponent.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    x:Class="GroupedListViewSample.Styles.MenuStyles"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:models="using:GroupedListViewSample.Models"
    xmlns:local="using:GroupedListViewSample.Styles">

With the code-behind class in place, the DataTemplate can make use of the compiled data binding. The MenuTemplate specifies the look for every menu, the MenuHeaderTemplate the look for the group header.

<DataTemplate x:Key="MenuTemplate" x:DataType="models:MenuItem">
    <TextBlock Text="{x:Bind Title}"
               Style="{StaticResource TitleTextBlockStyle}"/>
</DataTemplate>

<DataTemplate x:Key="MenuHeaderTemplate" x:DataType="models:MenuItemsGroup">
    <TextBlock Text="{x:Bind Key.DayOfWeek}"
               Style="{ThemeResource HeaderTextBlockStyle}"/>
</DataTemplate>

Having the resource dictionary strongly typed with a code-behind class, an instance can be created within the merged dictionaries instead of referencing the file using the ResourceDictionary element:

<Application
    x:Class="GroupedListViewSample.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:styles="using:GroupedListViewSample.Styles"
    RequestedTheme="Light">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <styles:MenuStyles />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

DataTemplates specified in a resource dictionary can make use of compiled data binding when a code-behind class is defined for the resource dictionary.

Running the appliation, you can see the ListView as shown.

UWP Grouping

Summary

The CollectionViewSource, ListView, DataTemplates, grouping by using LINQ methods, and compiled data binding are the main ingredients used to show data grouped.

With the source code shown, I didn’t show the parts not relevant to the discussion, but of course they are relevant to the sample application like instantiating the dependency injection container, and connecting the view-model. You will see all this in the complete source code that is available on More Samples. Also, see my other articles, e.g. on using Microsoft.Extensions.DependencyInjection with UWP.

Have fun with programming and learning!

Christian

More Information

More information about XAML and the Universal Windows Platform is in my book Professional C# 6 and .NET Core 1.0 and my workshops.

Problems using .NET Core with UWP?

Programming Universal Windows Platform Workshop

Compiled Data Binding with UWP

What’s in there for UWP App Developers?

Image © tavidom | Dreamstime.com – Groups of people

Advertisement

7 thoughts on “ListView Grouping with UWP

  1. I don’t know if it was intentional but the link to More Samples is dead due to dot between git and hub. Otherwise, thanks for this post!

    Like

  2. Any chance you know how to initialize the SemanticZoom so that it automatically zooms in to a particular item? e.g. so that you can restore a previously-zoomed in item upon initialization of the view that’s hosting the SemanticZoom?

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.