Cenni preliminari sui modelli di dati

Il modello di applicazione di modelli di dati WPF offre una notevole flessibilità per definire la presentazione dei dati. I controlli WPF dispongono di funzionalità incorporate per supportare la personalizzazione della presentazione dei dati. Questo argomento illustra innanzitutto come definire un DataTemplate oggetto e quindi introduce altre funzionalità di creazione di modelli di dati, ad esempio la selezione di modelli in base alla logica personalizzata e il supporto per la visualizzazione di dati gerarchici.

Prerequisiti

Questo argomento è incentrato sulle funzionalità di applicazione dei modelli di dati e non è un'introduzione ai concetti di data binding. Per informazioni sui concetti di data binding, vedere Panoramica sul data binding.

DataTemplate riguarda la presentazione dei dati ed è una delle numerose funzionalità fornite dal modello di applicazione di stili e modelli WPF. Per un'introduzione dello stile e del modello di modelli WPF, ad esempio come usare un Style oggetto per impostare le proprietà nei controlli, vedere l'argomento Applicazione di stili e modelli .

Inoltre, è importante comprendere Resources, che sono essenzialmente ciò che abilita oggetti come Style e DataTemplate per essere riutilizzabili. Per altre informazioni sulle risorse, vedere Risorse XAML.

Nozioni fondamentali sui modelli di dati

Per illustrare perché DataTemplate è importante, verrà illustrato un esempio di data binding. In questo esempio è presente un oggetto ListBox associato a un elenco di Task oggetti . Ogni oggetto Task dispone di un TaskName (stringa), una Description (stringa), una Priority (intero) e un tipo di proprietà TaskType, vale a dire una Enum con valori Home e Work.

<Window x:Class="SDKSample.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:SDKSample"
  Title="Introduction to Data Templating Sample">
  <Window.Resources>
    <local:Tasks x:Key="myTodoList"/>

</Window.Resources>
  <StackPanel>
    <TextBlock Name="blah" FontSize="20" Text="My Task List:"/>
    <ListBox Width="400" Margin="10"
             ItemsSource="{Binding Source={StaticResource myTodoList}}"/>
  </StackPanel>
</Window>

Senza un DataTemplate

Senza , DataTemplatel'oggetto ListBox attualmente è simile al seguente:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox displaying the string representation SDKSample.Task for each source object.

Ciò che accade è che, senza istruzioni specifiche, per ListBox impostazione predefinita chiama quando ToString si tenta di visualizzare gli oggetti nella raccolta. Pertanto, se l'oggetto esegue l'override Task del ToString metodo , visualizza ListBox la rappresentazione di stringa di ogni oggetto di origine nell'insieme sottostante.

Se ad esempio la classe Task esegue l'override del metodo ToString in questo modo, dove name è il campo della proprietà TaskName:

public override string ToString()
{
    return name.ToString();
}
Public Overrides Function ToString() As String
    Return _name.ToString()
End Function

L'aspetto ListBox sarà quindi simile al seguente:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox displaying a list of tasks.

Questa soluzione risulta tuttavia limitante e poco flessibile. Inoltre, se si esegue il binding ai dati XML, non sarà possibile eseguire l'override ToStringdi .

Definizione di DataTemplate semplice

La soluzione consiste nel definire un oggetto DataTemplate. Un modo per eseguire questa operazione consiste nell'impostare la ItemTemplate proprietà di ListBox su un DataTemplateoggetto . Ciò che si specifica in DataTemplate diventa la struttura visiva dell'oggetto dati. Di seguito DataTemplate è piuttosto semplice. Vengono fornite istruzioni che ogni elemento viene visualizzato come tre TextBlock elementi all'interno di un oggetto StackPanel. Ogni TextBlock elemento è associato a una proprietà della Task classe .

<ListBox Width="400" Margin="10"
         ItemsSource="{Binding Source={StaticResource myTodoList}}">
   <ListBox.ItemTemplate>
     <DataTemplate>
       <StackPanel>
         <TextBlock Text="{Binding Path=TaskName}" />
         <TextBlock Text="{Binding Path=Description}"/>
         <TextBlock Text="{Binding Path=Priority}"/>
       </StackPanel>
     </DataTemplate>
   </ListBox.ItemTemplate>
 </ListBox>

I dati sottostanti per gli esempi in questo argomento sono una raccolta di oggetti CLR. Se si esegue il binding ai dati XML, i concetti fondamentali sono gli stessi, ma esiste una leggera differenza sintattica. Ad esempio, anziché avere Path=TaskName, è necessario impostare XPath@TaskName su (se TaskName è un attributo del nodo XML).

ListBox Ora l'aspetto è simile al seguente:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox displaying the tasks as TextBlock elements.

Creazione di DataTemplate come risorsa

Nell'esempio precedente è stato definito l'inline DataTemplate . È più comune definirlo nella sezione delle risorse perché qui diventa un oggetto riutilizzabile, come illustrato nell'esempio seguente:

<Window.Resources>
<DataTemplate x:Key="myTaskTemplate">
  <StackPanel>
    <TextBlock Text="{Binding Path=TaskName}" />
    <TextBlock Text="{Binding Path=Description}"/>
    <TextBlock Text="{Binding Path=Priority}"/>
  </StackPanel>
</DataTemplate>
</Window.Resources>

È possibile a questo punto usare myTaskTemplate come risorsa, come illustrato nell'esempio seguente:

<ListBox Width="400" Margin="10"
         ItemsSource="{Binding Source={StaticResource myTodoList}}"
         ItemTemplate="{StaticResource myTaskTemplate}"/>

Poiché myTaskTemplate è una risorsa, è ora possibile usarla in altri controlli con una proprietà che accetta un DataTemplate tipo. Come illustrato in precedenza, per ItemsControl gli oggetti, ad esempio ListBox, è la ItemTemplate proprietà . Per ContentControl gli oggetti, è la ContentTemplate proprietà .

Proprietà DataType

La DataTemplate classe ha una DataType proprietà molto simile alla TargetType proprietà della Style classe . Pertanto, invece di specificare un x:Key per nell'esempio DataTemplate precedente, è possibile eseguire le operazioni seguenti:

<DataTemplate DataType="{x:Type local:Task}">
  <StackPanel>
    <TextBlock Text="{Binding Path=TaskName}" />
    <TextBlock Text="{Binding Path=Description}"/>
    <TextBlock Text="{Binding Path=Priority}"/>
  </StackPanel>
</DataTemplate>

Questa operazione DataTemplate viene applicata automaticamente a tutti gli Task oggetti. Si noti che in questo caso il valore di x:Key è impostato in modo implicito. Pertanto, se si assegna questo DataTemplatex:Key valore, si esegue l'override dell'oggetto implicito x:Key e l'oggetto DataTemplate non verrà applicato automaticamente.

Se si associa un oggetto ContentControl a una raccolta di Task oggetti , ContentControl non viene utilizzato automaticamente .DataTemplate Ciò è dovuto al fatto che l'associazione su un oggetto ContentControl necessita di ulteriori informazioni per distinguere se si desidera eseguire l'associazione a un'intera raccolta o a singoli oggetti. Se si ContentControl sta tenendo traccia della selezione di un ItemsControl tipo, è possibile impostare la Path proprietà dell'associazione ContentControl su "/" per indicare che si è interessati all'elemento corrente. Per un esempio, vedere Procedura: Eseguire l'associazione di una raccolta e visualizzare informazioni in base alla selezione effettuata. In caso contrario, è necessario specificare in DataTemplate modo esplicito impostando la ContentTemplate proprietà .

La DataType proprietà è particolarmente utile quando si dispone di diversi CompositeCollection tipi di oggetti dati. Per un esempio, vedere Procedura: Implementare un oggetto CompositeCollection.

Aggiunta di altri elementi al DataTemplate

I dati presentano a questo punto le informazioni necessarie. È tuttavia possibile migliorarne ulteriormente la presentazione. Per migliorare la presentazione, aggiungere un Borderoggetto , un Gridoggetto e alcuni TextBlock elementi che descrivono i dati visualizzati.


<DataTemplate x:Key="myTaskTemplate">
  <Border Name="border" BorderBrush="Aqua" BorderThickness="1"
          Padding="5" Margin="5">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
      </Grid.ColumnDefinitions>
      <TextBlock Grid.Row="0" Grid.Column="0" Text="Task Name:"/>
      <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=TaskName}" />
      <TextBlock Grid.Row="1" Grid.Column="0" Text="Description:"/>
      <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Description}"/>
      <TextBlock Grid.Row="2" Grid.Column="0" Text="Priority:"/>
      <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Path=Priority}"/>
    </Grid>
  </Border>
</DataTemplate>

Lo screenshot seguente mostra l'oggetto ListBox con questa modifica DataTemplate:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox with the modified DataTemplate.

È possibile impostare HorizontalContentAlignmentStretch su su ListBox per assicurarsi che la larghezza degli elementi occupa l'intero spazio:

<ListBox Width="400" Margin="10"
     ItemsSource="{Binding Source={StaticResource myTodoList}}"
     ItemTemplate="{StaticResource myTaskTemplate}" 
     HorizontalContentAlignment="Stretch"/>

Con la HorizontalContentAlignment proprietà impostata su Stretch, l'oggetto ListBox è ora simile al seguente:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox stretched to fit the screen horizontally.

Utilizzo di DataTrigger per applicare i valori di proprietà

La presentazione corrente non indica se un Task è un'attività domestica o di ufficio. Si tenga a mente che l'oggetto Task dispone di una proprietà TaskType di tipo TaskType, che è un'enumerazione con valori Home e Work.

Nell'esempio seguente, imposta DataTrigger l'oggetto BorderBrush dell'elemento denominato border su Yellow se la TaskType proprietà è TaskType.Home.

<DataTemplate x:Key="myTaskTemplate">
<DataTemplate.Triggers>
  <DataTrigger Binding="{Binding Path=TaskType}">
    <DataTrigger.Value>
      <local:TaskType>Home</local:TaskType>
    </DataTrigger.Value>
    <Setter TargetName="border" Property="BorderBrush" Value="Yellow"/>
  </DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>

L'applicazione presenta ora l'aspetto seguente. Le attività domestiche hanno un bordo giallo mentre le attività di ufficio hanno un bordo azzurro:

Screenshot of the Introduction to Data Templating Sample window showing the My Task List ListBox with the home and office task borders highlighted in color.

In questo esempio DataTrigger usa un Setter oggetto per impostare un valore della proprietà. Le classi trigger hanno anche le EnterActions proprietà e ExitActions che consentono di avviare un set di azioni, ad esempio le animazioni. È inoltre disponibile una MultiDataTrigger classe che consente di applicare modifiche in base a più valori di proprietà associati a dati.

Un modo alternativo per ottenere lo stesso effetto consiste nell'associare la BorderBrush proprietà alla TaskType proprietà e usare un convertitore di valori per restituire il colore in base al TaskType valore. La creazione dell'effetto precedente tramite un convertitore è una soluzione leggermente più efficiente in termini di prestazioni. La creazione di un convertitore personalizzato offre inoltre maggiore flessibilità poiché consente di usare una logica personalizzata. La scelta della tecnica dipende in definitiva dallo scenario e le preferenze personali. Per informazioni su come scrivere un convertitore, vedere IValueConverter.

Elementi di un DataTemplate

Nell'esempio precedente il trigger è stato inserito all'interno di DataTemplate usando la DataTemplate.Triggers proprietà . L'oggetto Setter del trigger imposta il valore di una proprietà di un elemento (l'elemento Border ) che si trova all'interno di DataTemplate. Tuttavia, se le proprietà Setters a cui si è interessati non sono proprietà di elementi che si trovano all'interno dell'oggetto correnteDataTemplate, potrebbe essere più adatto impostare le proprietà usando un Style oggetto per la ListBoxItem classe (se il controllo a cui si sta eseguendo l'associazione è ).ListBox Ad esempio, se vuoi Trigger animare il Opacity valore dell'elemento quando un mouse punta a un elemento, definisci i trigger all'interno di uno ListBoxItem stile. Per un esempio, vedere Introduction to Styling and Templating Sample (Introduzione all'esempio di applicazione di stili e modelli).

In generale, tenere presente che l'oggetto DataTemplate viene applicato a ognuno dei generati ListBoxItem (per altre informazioni su come e dove viene effettivamente applicato, vedere la ItemTemplate pagina). L'oggetto DataTemplate riguarda solo la presentazione e l'aspetto degli oggetti dati. Nella maggior parte dei casi, tutti gli altri aspetti della presentazione, ad esempio l'aspetto di un elemento quando viene selezionato o come ListBox dispone gli elementi, non appartengono alla definizione di un oggetto DataTemplate. Per un esempio, vedere la sezione Applicazione di stili e modelli di ItemsControl.

Scelta di un DataTemplate in base alle proprietà dell'oggetto dati

Nella sezione Proprietà DataType è stato spiegato che è possibile definire modelli di dati diversi per oggetti dati diversi. Ciò è particolarmente utile quando si dispone di tipi CompositeCollection o raccolte diversi con elementi di tipi diversi. Nella sezione Use DataTriggers to Apply Property Values (Usa DataTriggers per applicare i valori delle proprietà) è stato mostrato che se si dispone di una raccolta dello stesso tipo di oggetti dati è possibile creare un DataTemplate oggetto e quindi usare trigger per applicare le modifiche in base ai valori delle proprietà di ogni oggetto dati. I trigger consentono tuttavia di applicare valori delle proprietà o avviare animazioni ma non offrono la flessibilità necessaria per ricostruire la struttura degli oggetti dati. Alcuni scenari possono richiedere la creazione di un oggetto diverso DataTemplate per gli oggetti dati dello stesso tipo, ma con proprietà diverse.

Quando ad esempio un oggetto Task ha un valore Priority di 1, è possibile conferirgli un aspetto completamente diverso in modo che funga da avviso. In tal caso, si crea un oggetto DataTemplate per la visualizzazione degli oggetti con priorità Task alta. Aggiungere quanto segue DataTemplate alla sezione resources:

<DataTemplate x:Key="importantTaskTemplate">
  <DataTemplate.Resources>
    <Style TargetType="TextBlock">
      <Setter Property="FontSize" Value="20"/>
    </Style>
  </DataTemplate.Resources>
  <Border Name="border" BorderBrush="Red" BorderThickness="1"
          Padding="5" Margin="5">
    <DockPanel HorizontalAlignment="Center">
      <TextBlock Text="{Binding Path=Description}" />
      <TextBlock>!</TextBlock>
    </DockPanel>
  </Border>
</DataTemplate>

In questo esempio viene utilizzata la proprietà DataTemplate.Resources . Le risorse definite in tale sezione vengono condivise dagli elementi all'interno di DataTemplate.

Per fornire la logica per scegliere quale DataTemplate utilizzare in base al Priority valore dell'oggetto dati, creare una sottoclasse di DataTemplateSelector ed eseguire l'override del SelectTemplate metodo. Nell'esempio seguente il SelectTemplate metodo fornisce la logica per restituire il modello appropriato in base al valore della Priority proprietà . Il modello da restituire si trova nelle risorse dell'elemento avvolgente Window .

using System.Windows;
using System.Windows.Controls;

namespace SDKSample
{
    public class TaskListDataTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate
            SelectTemplate(object item, DependencyObject container)
        {
            FrameworkElement element = container as FrameworkElement;

            if (element != null && item != null && item is Task)
            {
                Task taskitem = item as Task;

                if (taskitem.Priority == 1)
                    return
                        element.FindResource("importantTaskTemplate") as DataTemplate;
                else
                    return
                        element.FindResource("myTaskTemplate") as DataTemplate;
            }

            return null;
        }
    }
}

Namespace SDKSample
    Public Class TaskListDataTemplateSelector
        Inherits DataTemplateSelector
        Public Overrides Function SelectTemplate(ByVal item As Object, ByVal container As DependencyObject) As DataTemplate

            Dim element As FrameworkElement
            element = TryCast(container, FrameworkElement)

            If element IsNot Nothing AndAlso item IsNot Nothing AndAlso TypeOf item Is Task Then

                Dim taskitem As Task = TryCast(item, Task)

                If taskitem.Priority = 1 Then
                    Return TryCast(element.FindResource("importantTaskTemplate"), DataTemplate)
                Else
                    Return TryCast(element.FindResource("myTaskTemplate"), DataTemplate)
                End If
            End If

            Return Nothing
        End Function
    End Class
End Namespace

È quindi possibile dichiarare il TaskListDataTemplateSelector come risorsa:

<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>

Per usare la risorsa selettore modello, assegnarla alla ItemTemplateSelector proprietà di ListBox. Chiama ListBox il SelectTemplate metodo dell'oggetto TaskListDataTemplateSelector per ognuno degli elementi della raccolta sottostante. La chiamata passa l'oggetto dati come parametro di elemento. L'oggetto DataTemplate restituito dal metodo viene quindi applicato all'oggetto dati.

<ListBox Width="400" Margin="10"
         ItemsSource="{Binding Source={StaticResource myTodoList}}"
         ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
         HorizontalContentAlignment="Stretch"/>

Con il selettore di modello sul posto, l'oggetto ListBox ora viene visualizzato come segue:

Screenshot of Introduction to Data Templating Sample window showing the My Task List ListBox with the Priority 1 tasks prominently displayed with a red border.

Questa operazione chiude la spiegazione di questo esempio. Per l'esempio completo, vedere Introduction to Data Templating Sample (Introduzione a un esempio di applicazione di modello).

Applicazione di stili e modelli a ItemsControl

Anche se ItemsControl non è l'unico tipo di controllo con cui è possibile usare , DataTemplate si tratta di uno scenario molto comune per associare un ItemsControl oggetto a una raccolta. Nella sezione What Belongs in a DataTemplate è stato illustrato che la definizione dell'utente DataTemplate deve essere interessata solo alla presentazione dei dati. Per sapere quando non è adatto per usare un DataTemplate è importante comprendere le diverse proprietà di stile e modello fornite da ItemsControl. L'esempio seguente è progettato per illustrare la funzione di ognuna di queste proprietà. In ItemsControl questo esempio è associato alla stessa Tasks raccolta dell'esempio precedente. A scopo dimostrativo, gli stili e i modelli in questo esempio sono tutti dichiarati incorporati.

<ItemsControl Margin="10"
              ItemsSource="{Binding Source={StaticResource myTodoList}}">
  <!--The ItemsControl has no default visual appearance.
      Use the Template property to specify a ControlTemplate to define
      the appearance of an ItemsControl. The ItemsPresenter uses the specified
      ItemsPanelTemplate (see below) to layout the items. If an
      ItemsPanelTemplate is not specified, the default is used. (For ItemsControl,
      the default is an ItemsPanelTemplate that specifies a StackPanel.-->
  <ItemsControl.Template>
    <ControlTemplate TargetType="ItemsControl">
      <Border BorderBrush="Aqua" BorderThickness="1" CornerRadius="15">
        <ItemsPresenter/>
      </Border>
    </ControlTemplate>
  </ItemsControl.Template>
  <!--Use the ItemsPanel property to specify an ItemsPanelTemplate
      that defines the panel that is used to hold the generated items.
      In other words, use this property if you want to affect
      how the items are laid out.-->
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <WrapPanel />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <!--Use the ItemTemplate to set a DataTemplate to define
      the visualization of the data objects. This DataTemplate
      specifies that each data object appears with the Proriity
      and TaskName on top of a silver ellipse.-->
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <DataTemplate.Resources>
        <Style TargetType="TextBlock">
          <Setter Property="FontSize" Value="18"/>
          <Setter Property="HorizontalAlignment" Value="Center"/>
        </Style>
      </DataTemplate.Resources>
      <Grid>
        <Ellipse Fill="Silver"/>
        <StackPanel>
          <TextBlock Margin="3,3,3,0"
                     Text="{Binding Path=Priority}"/>
          <TextBlock Margin="3,0,3,7"
                     Text="{Binding Path=TaskName}"/>
        </StackPanel>
      </Grid>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
  <!--Use the ItemContainerStyle property to specify the appearance
      of the element that contains the data. This ItemContainerStyle
      gives each item container a margin and a width. There is also
      a trigger that sets a tooltip that shows the description of
      the data object when the mouse hovers over the item container.-->
  <ItemsControl.ItemContainerStyle>
    <Style>
      <Setter Property="Control.Width" Value="100"/>
      <Setter Property="Control.Margin" Value="5"/>
      <Style.Triggers>
        <Trigger Property="Control.IsMouseOver" Value="True">
          <Setter Property="Control.ToolTip"
                  Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                          Path=Content.Description}"/>
        </Trigger>
      </Style.Triggers>
    </Style>
  </ItemsControl.ItemContainerStyle>
</ItemsControl>

Lo screenshot seguente mostra l'esempio dopo il rendering:

ItemsControl example screenshot

Si noti che invece di usare , ItemTemplateè possibile usare .ItemTemplateSelector Per un esempio, fare riferimento alla sezione precedente. Analogamente, invece di usare , ItemContainerStyleè possibile usare .ItemContainerStyleSelector

Altre due proprietà correlate allo stile di ItemsControl che non sono illustrate di seguito sono GroupStyle e GroupStyleSelector.

Supporto per i dati gerarchici

Sono state finora analizzate unicamente le modalità di associazione e di visualizzazione di una raccolta singola. Si dispone talvolta di una raccolta che contiene altre raccolte. La HierarchicalDataTemplate classe è progettata per essere usata con HeaderedItemsControl i tipi per visualizzare tali dati. Nell'esempio seguente ListLeagueList è un elenco di oggetti League. Ogni oggetto League ha un Name e una raccolta di oggetti Division. Ogni Division ha un Name e una raccolta di oggetti Team e ogni oggetto Team ha un Name.

<Window x:Class="SDKSample.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="HierarchicalDataTemplate Sample"
  xmlns:src="clr-namespace:SDKSample">
  <DockPanel>
    <DockPanel.Resources>
      <src:ListLeagueList x:Key="MyList"/>

      <HierarchicalDataTemplate DataType    = "{x:Type src:League}"
                                ItemsSource = "{Binding Path=Divisions}">
        <TextBlock Text="{Binding Path=Name}"/>
      </HierarchicalDataTemplate>

      <HierarchicalDataTemplate DataType    = "{x:Type src:Division}"
                                ItemsSource = "{Binding Path=Teams}">
        <TextBlock Text="{Binding Path=Name}"/>
      </HierarchicalDataTemplate>

      <DataTemplate DataType="{x:Type src:Team}">
        <TextBlock Text="{Binding Path=Name}"/>
      </DataTemplate>
    </DockPanel.Resources>

    <Menu Name="menu1" DockPanel.Dock="Top" Margin="10,10,10,10">
        <MenuItem Header="My Soccer Leagues"
                  ItemsSource="{Binding Source={StaticResource MyList}}" />
    </Menu>

    <TreeView>
      <TreeViewItem ItemsSource="{Binding Source={StaticResource MyList}}" Header="My Soccer Leagues" />
    </TreeView>

  </DockPanel>
</Window>

L'esempio mostra che con l'uso di è possibile visualizzare facilmente i dati dell'elenco HierarchicalDataTemplateche contengono altri elenchi. Lo screenshot seguente mostra l'esempio.

HierarchicalDataTemplate sample screenshot

Vedi anche