WPF: Target a different template per header type when grouping

When using a CollectionViewSource and grouping by several properties you will probably want to customize each header template. In this article we will look at targeting different templates based on the header type.

Let’s consider a scenario where you want to display a list of people grouped by year of birth and by marital status.

The Person class:

public class Person
{
    public DateTime Birthday { get; set; }
    public string Name { get; set; }
    public MaritalStatus MaritalStatus { get; set; }
}

Our view model:

public class IPeopleViewModel
{
    public IEnumerable<Person> People { get; }
}

We define our ListView:

<ListView
        DataContext="{StaticResource PeopleGroups}"
        ItemsSource="{Binding}">
    <ListView.GroupStyle>
        <StaticResourceExtension ResourceKey="GroupStyle" />
    </ListView.GroupStyle>
    <ListView.View>
        <GridView>
            <GridViewColumn
                    Header="Birthday"
                    DisplayMemberBinding="{Binding Birthday,  StringFormat=&39;d MMMM&39;}" />
            <GridViewColumn
                    Header="Name"
                    DisplayMemberBinding="{Binding Name}" />
        </GridView>
    </ListView.View>
</ListView>

The grouping and the sorting:

<CollectionViewSource x:Key="PeopleGroups" Source="{Binding People}">
    <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="Birthday.Year" />
        <PropertyGroupDescription PropertyName="MaritalStatus" />
    </CollectionViewSource.GroupDescriptions>
    <CollectionViewSource.SortDescriptions>
        <scm:SortDescription PropertyName="Birthday" Direction="Ascending" />
        <scm:SortDescription PropertyName="Name" Direction="Ascending" />
    </CollectionViewSource.SortDescriptions>
</CollectionViewSource>

Notice that we group by the Year property of the birthday.

Don’t forget the scm namespace:

xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"

This will be our style:

<GroupStyle x:Key="GroupStyle">
    <GroupStyle.ContainerStyle>
        <Style TargetType="{x:Type GroupItem}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Name, Converter={my:TypeConverter}}" Value="{x:Type System:Int32}">
                    <Setter Property="Template" Value="{StaticResource BirthdayHeaderTemplate}" />
                </DataTrigger>
                <DataTrigger Binding="{Binding Name, Converter={my:TypeConverter}}" Value="{x:Type my:MaritalStatus}">
                    <Setter Property="Template" Value="{StaticResource MaritalStatusHeaderTemplate}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </GroupStyle.ContainerStyle>
</GroupStyle>

The System namespace:

xmlns:System="clr-namespace:System;assembly=mscorlib"

We create style triggers based on the header type.

Within the group style we are binding to a CollectionViewGroup. The Name property will have an object with the header value, so we bind to this value.

We are using a converter for determining the type. The converter is also a markup extension. You can read more about this approach at How to use converters without creating static resources.

The converter:

[ValueConversion(typeof(object), typeof(Type))]
public class TypeConverter : ConverterMarkupExtension<TypeConverter>
{
    public TypeConverter()
    {
    }
 
    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null)
        {
            return value.GetType();
        }
  
        return null;
    }
}

Now we can define different templates for each header’s type.

For the birthday header:

<ControlTemplate x:Key="BirthdayHeaderTemplate" TargetType="{x:Type GroupItem}">
    <StackPanel Margin="0,10,0,0">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Name}" FontWeight="Bold" />
            <TextBlock Text="{Binding ItemCount, StringFormat={} ({0})}" />
        </StackPanel>
        <ItemsPresenter />
    </StackPanel>
</ControlTemplate>

We make use of the ItemCount property to display the number of results in the group.

The status header template:

<ControlTemplate x:Key="MaritalStatusHeaderTemplate" TargetType="{x:Type GroupItem}">
    <StackPanel Margin="5,10,0,0">
        <TextBlock Text="{Binding Name}" FontStyle="Italic" />
        <ItemsPresenter />
    </StackPanel>
</ControlTemplate>

And now the final result:

Grouping with different header templates
Grouping with different header templates
Nuno Freitas
Posted by Nuno Freitas on April 18, 2014

Related articles