Eseguire il binding di dati gerarchici e creare una visualizzazione master/dettagli

Nota: vedi anche l'esempio master/dettagli.

Puoi creare una visualizzazione master/dettagli multilivello (anche nota come elenco/dettagli) di dati gerarchici associando controlli elementi a istanze CollectionViewSource associate in una catena. In questo argomento viene usata l'estensione di markup {x:Bind} quando possibile e l'estensione di markup {Binding}, più flessibile ma con prestazioni inferiori, quando necessario.

In base a un modello comune delle app della piattaforma UWP (Universal Windows Platform), quando un utente effettua una selezione in un elenco master vengono visualizzate pagine dei dettagli differenti. Questo è utile per offrire una rappresentazione visiva dettagliata dei vari elementi inclusi in ogni livello di una gerarchia. Un'altra opzione consiste nel visualizzare più livelli di dati in una singola pagina. Questa modalità è consigliabile se vuoi visualizzare alcuni elenchi semplici per consentire agli utenti di eseguire rapidamente il drill-down in un elemento di interesse. Questo argomento descrive come implementare l'interazione. Le istanze CollectionViewSource tengono traccia della selezione corrente a ogni livello gerarchico.

Creeremo una visualizzazione di una gerarchia per una squadra sportiva organizzata in elenchi per serie, divisioni e squadre, con una visualizzazione dettagliata delle squadre. Quando si seleziona un elemento in un elenco, le visualizzazioni successive vengono aggiornate automaticamente.

master/details view of a sports hierarchy

Prerequisiti

In questo argomento partiamo dal presupposto che tu sappia come creare una semplice app UWP. Per istruzioni sulla creazione della tua prima app UWP, vedi Creare la prima app UWP in C# o Visual Basic.

Creare il progetto

Per iniziare, crea un nuovo progetto App vuota (Windows universale). Chiamalo "MasterDetailsBinding".

Creare il modello di dati

Aggiungere una nuova classe al progetto, denominarla ViewModel.cs e aggiungere questo codice. Questa sarà la classe dell'origine di binding.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

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()
        {
            this.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
                   };
        }
    }
}

Creare la visualizzazione

Esponi quindi la classe dell'origine di binding dalla classe che rappresenta la pagina del markup. Puoi eseguire questa operazione aggiungendo una proprietà di tipo LeagueList a MainPage.

namespace MasterDetailsBinding
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.ViewModel = new LeagueList();
        }
        public LeagueList ViewModel { get; set; }
    }
}

Infine, sostituisci il contenuto del file MainPage.xaml con il markup seguente, che dichiara tre istanze CollectionViewSource e le associa in una catena. I controlli successivi possono quindi associare l'oggetto CollectionViewSource corretto a seconda del relativo livello nella gerarchia.

<Page
    x:Class="MasterDetailsBinding.MainPage"
    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">

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

    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

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

Tieni presente che eseguendo il binding direttamente a CollectionViewSource, stai implicando che vuoi associare l'elemento corrente nei binding in cui non è possibile trovare il percorso nella raccolta stessa. Non è necessario specificare la proprietà CurrentItem come percorso per il binding, ma puoi farlo se esiste un'ambiguità. Ad esempio, la proprietà Content dell'oggetto ContentControl che rappresenta la visualizzazione delle squadre è associata a TeamsCollectionViewSource. Tuttavia, i controlli in DataTemplate si associano a proprietà della classe Team perché CollectionViewSource fornisce automaticamente la squadra attualmente selezionata nell'elenco, quando necessario.