WPF: Templates diferentes por tipo do cabeçalho quando se agrupa

Quando usam uma CollectionViewSource e agrupam por várias propriedades, provavelmente vão querer distinguir os cabeçalhos. Neste artigo vemos como especificar templates diferentes tendo em conta o tipo do cabeçalho.

Vamos considerar que queremos mostrar uma listagem de pessoas agrupadas por ano de nascimento e por estado civil.

A nossa classe Person:

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

O view model:

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

Definimos a nossa 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>

O agrupamento e ordenação:

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

Reparem que agrupamos pela propriedade Year do aniversário.

Não se esqueçam do namespace scm:

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

Este será o nosso estilo:

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

O namespace System:

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

Criamos triggers de estilo tendo em conta o tipo do cabeçalho.

Dentro do estilo estamos a ligar a uma CollectionViewGroup. A propriedade Name contém o objecto com o valor do cabeçalho, por isso ligamos a esse valor.

Estamos a usar um conversor para determinar o tipo. O conversor é também uma extensão de markup. Podem ler mais sobre esta abordagem em Como usar conversores sem criar recursos estáticos.

O conversor:

[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;
    }
}

Agora podemos definir os diferentes templates para cada tipo do cabeçalho.

Para o cabeçalho do aniversário:

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

Usamos a propriedade ItemCount para mostrar o número de resultados no grupo.

O cabeçalho do estado civil:

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

E finalmente o resultado:

Agrupar com templates diferentes para os cabeçalhos
Agrupar com templates diferentes para os cabeçalhos

Artigos relacionados