Associar dados hierárquicos e criar uma exibição mestre/detalhada com o SDK do Aplicativo Windows

Observação

Confira também Exemplo de UWP de mestre/detalhes.

Você pode criar um modo de exibição mestre/detalhes de vários níveis (também conhecido como lista/detalhes) de dados hierárquicos, associando controles de itens a instâncias CollectionViewSource que são associadas em uma cadeia. Neste tópico, usamos a extensão de marcação {x:Bind} onde possível, e a extensão de marcação {Binding} mais flexível (mas menos eficiente) onde necessário.

Uma estrutura comum dos aplicativos do SDK de Aplicativo do Windows é navegar para páginas de detalhes diferentes quando um usuário faz uma seleção em uma lista mestra. Isso é útil quando você quiser providenciar uma representação visual detalhada de cada item em cada nível da hierarquia. Outra opção é exibir vários níveis de dados em uma única página. Isso é útil quando você deseja exibir algumas listas simples que permitem que o usuário explore rapidamente um item de interesse. Este tópico descreve como implementar essa interação. As instâncias de CollectionViewSource controlam a seleção atual em cada nível hierárquico.

Criaremos um modo de exibição de hierarquia de esportes em equipe que é organizado em listas de ligas, divisões e times e inclui um modo de exibição detalhado dos times. Quando você seleciona um item de qualquer lista, os modos de exibição subsequentes são automaticamente atualizados.

master/details view of a sports hierarchy

Pré-requisitos

Este tópico pressupõe que você saiba como criar um aplicativo básico do SDK de Aplicativo do Windows. Para saber como criar seu primeiro aplicativo do SDK de Aplicativo do Windows, confira Criar seu primeiro projeto do WinUI 3 (SDK de Aplicativo do Windows).

Criar o projeto

Crie um projeto Aplicativo em branco empacotado (WinUI 3 na Área de Trabalho). Chame-o de "MasterDetailsBinding".

Criar o modelo de dados

Adicione uma classe ao projeto, chame-a de ViewModel.cs e adicione esse código a ela. Esta será sua classe de origem de associação.

using System.Collections.Generic;
using System.Linq;

namespace MasterDetailsBinding
{
    public class Team
    {
        public string Name { get; set; }
        public int Wins { get; set; }
        public int Losses { get; set; }
    }

    public class Division
    {
        public string Name { get; set; }
        public IEnumerable<Team> Teams { get; set; }
    }

    public class League
    {
        public string Name { get; set; }
        public IEnumerable<Division> Divisions { get; set; }
    }

    public class LeagueList : List<League>
    {
        public LeagueList()
        {
            AddRange(GetLeague().ToList());
        }

        public IEnumerable<League> GetLeague()
        {
            return from x in Enumerable.Range(1, 2)
                   select new League
                   {
                       Name = "League " + x,
                       Divisions = GetDivisions(x).ToList()
                   };
        }

        public IEnumerable<Division> GetDivisions(int x)
        {
            return from y in Enumerable.Range(1, 3)
                   select new Division
                   {
                       Name = string.Format("Division {0}-{1}", x, y),
                       Teams = GetTeams(x, y).ToList()
                   };
        }

        public IEnumerable<Team> GetTeams(int x, int y)
        {
            return from z in Enumerable.Range(1, 4)
                   select new Team
                   {
                       Name = string.Format("Team {0}-{1}-{2}", x, y, z),
                       Wins = 25 - (x * y * z),
                       Losses = x * y * z
                   };
        }
    }
}

Criar o modo de exibição

Em seguida, exponha a classe de origem de associação na classe que representa a página de marcação. Para isso, adicione uma propriedade do tipo LeagueList a MainWindow.

namespace MasterDetailsBinding
{
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            ViewModel = new LeagueList();
        }
        public LeagueList ViewModel { get; set; }
    }
}

Por fim, substitua o conteúdo do arquivo MainWindow.xaml pela marcação a seguir, que declara três instâncias de CollectionViewSource e as associa em uma cadeia. Os controles subsequentes podem então ser associados ao CollectionViewSource apropriado, dependendo do seu nível na hierarquia.

<Window
    x:Class="MasterDetailsBinding.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MasterDetailsBinding"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid>
        <Grid.Resources>
            <CollectionViewSource x:Name="Leagues"
                Source="{x:Bind ViewModel}"/>
            <CollectionViewSource x:Name="Divisions"
                Source="{Binding Divisions, Source={StaticResource Leagues}}"/>
            <CollectionViewSource x:Name="Teams"
                Source="{Binding Teams, Source={StaticResource Divisions}}"/>
    
            <Style TargetType="TextBlock">
                <Setter Property="FontSize" Value="15"/>
                <Setter Property="FontWeight" Value="Bold"/>
            </Style>
            <Style TargetType="ListBox">
                <Setter Property="FontSize" Value="15"/>
            </Style>
            <Style TargetType="ContentControl">
                <Setter Property="FontSize" Value="15"/>
            </Style>
        </Grid.Resources>

        <StackPanel Orientation="Horizontal">

            <!-- All Leagues view -->
            <StackPanel Margin="5">
                <TextBlock Text="All Leagues"/>
                <ListBox ItemsSource="{Binding Source={StaticResource Leagues}}" 
                         DisplayMemberPath="Name"/>
            </StackPanel>

            <!-- League/Divisions view -->
            <StackPanel Margin="5">
                <TextBlock Text="{Binding Name, Source={StaticResource Leagues}}"/>
                <ListBox ItemsSource="{Binding Source={StaticResource Divisions}}" 
                         DisplayMemberPath="Name"/>
            </StackPanel>

            <!-- Division/Teams view -->
            <StackPanel Margin="5">
                <TextBlock Text="{Binding Name, Source={StaticResource Divisions}}"/>
                <ListBox ItemsSource="{Binding Source={StaticResource Teams}}" 
                         DisplayMemberPath="Name"/>
            </StackPanel>

            <!-- Team view -->
            <ContentControl Content="{Binding Source={StaticResource Teams}}">
                <ContentControl.ContentTemplate>
                    <DataTemplate>
                        <StackPanel Margin="5">
                            <TextBlock Text="{Binding Name}" 
                                       FontSize="15" FontWeight="Bold"/>
                            <StackPanel Orientation="Horizontal" Margin="10,10">
                                <TextBlock Text="Wins:" Margin="0,0,5,0"/>
                                <TextBlock Text="{Binding Wins}"/>
                            </StackPanel>
                            <StackPanel Orientation="Horizontal" Margin="10,0">
                                <TextBlock Text="Losses:" Margin="0,0,5,0"/>
                                <TextBlock Text="{Binding Losses}"/>
                            </StackPanel>
                        </StackPanel>
                    </DataTemplate>
                </ContentControl.ContentTemplate>
            </ContentControl>
        </StackPanel>
    </Grid>
</Window>

Observe que ao associar diretamente o CollectionViewSource, você está indicando que deseja associar ao item atual em associações onde o caminho não pode ser encontrado na própria coleção. Não é necessário especificar a propriedade CurrentItem como o caminho para a associação, embora você possa fazer isso se houver ambiguidade. Por exemplo, o ContentControl que representa o modo de exibição de equipe tem a propriedade Content associada a TeamsCollectionViewSource. No entanto, os controles no DataTemplate são associados a propriedades da classe Team porque o CollectionViewSource fornece automaticamente a equipe selecionada no momento na lista de equipes, quando necessário.

Confira também