Cenni preliminari sui modelli di dati
Il modello di creazione di modelli di dati WPF consente una notevole flessibilità per definire la presentazione dei dati. I controlli WPF dispongono di funzionalità incorporate per supportare la personalizzazione della presentazione dei dati. In questo argomento viene innanzitutto illustrato come definire un oggetto DataTemplate e vengono presentate altre funzionalità di creazione di modelli di dati, quali la selezione di modelli in base a logica personalizzata e il supporto per la visualizzazione di dati gerarchici.
Nel presente argomento sono contenute le seguenti sezioni.
- Prerequisiti
- Nozioni fondamentali sui modelli di dati
- Ulteriori aggiunte a DataTemplate
- Scelta di DataTemplate in base alle proprietà dell'oggetto dati
- Applicazione di stili e modelli di ItemsControl
- Supporto per dati gerarchici
- Argomenti correlati
Prerequisiti
In questo argomento vengono illustrate le funzionalità relative ai modelli di dati, non vengono presentati concetti di associazione dati. Per informazioni sui concetti di base relativi all'associazione dati, vedere Cenni preliminari sull'associazione dati.
DataTemplate concerne la presentazione dei dati e rappresenta una delle numerose funzionalità fornite dal modello di creazione di stili e modelli WPF. Per un'introduzione al modello di creazione di stili e modelli WPF, ad esempio per informazioni sull'utilizzo di un oggetto Style per l'impostazione di proprietà sui controlli, vedere l'argomento Applicazione di stili e modelli.
È inoltre importante comprendere il concetto di Resources, essenziale per rendere riutilizzabili oggetti quali Style e DataTemplate. Per ulteriori informazioni sulle risorse, vedere Cenni preliminari sulle risorse.
Nozioni fondamentali sui modelli di dati
Nella presente sezione sono contenute le seguenti sottosezioni.
- Senza DataTemplate
- Definizione di DataTemplate semplice
- Creazione di DataTemplate come una risorsa
- Proprietà DataType
Di seguito viene fornito un esempio di associazione dati con cui viene illustrata l'importanza di DataTemplate. In questo esempio, ListBox è associato a un elenco di oggetti Task. Ogni oggetto Task dispone di TaskName (stringa), Description (stringa), Priority (int) e una proprietà di tipo TaskType, costituita da Enum con valori Home e Work.
<Window x:Class="SDKSample.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://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 DataTemplate
Senza DataTemplate, l'oggetto ListBox attualmente si presenta come segue:
In assenza di istruzioni specifiche, per impostazione predefinita ListBox chiama ToString quando tenta di visualizzare gli oggetti nell'insieme. Pertanto, se l'oggetto Task esegue l'override del metodo ToString, ListBox visualizza la rappresentazione di stringa di ogni oggetto di origine nell'insieme sottostante.
Ad esempio, se la classe Task esegue l'override del metodo ToString in questo modo, dove name è il campo per la proprietà TaskName:
Public Overrides Function ToString() As String
Return _name.ToString()
End Function
public override string ToString()
{
return name.ToString();
}
ListBox avrà l'aspetto seguente:
Ciò è tuttavia limitante e poco flessibile. Inoltre, in caso di associazione a dati XML, non sarà possibile eseguire l'override di ToString.
Definizione di DataTemplate semplice
La soluzione consiste nel definire un oggetto DataTemplate. A tale scopo è possibile impostare la proprietà ItemTemplate di ListBox su DataTemplate. Quanto viene specificato in DataTemplate diventa la struttura visiva dell'oggetto dati. L'oggetto DataTemplate seguente è abbastanza semplice. Vengono fornite istruzioni affinché ogni elemento venga visualizzato come tre elementi TextBlock in un oggetto StackPanel. Ogni elemento TextBlock è associato a una proprietà della classe Task.
<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 riportati in questo argomento sono rappresentati da un insieme di oggetti CLR. In caso di associazione a dati XML, i concetti fondamentali sono gli stessi, ma vi è una leggera differenza sintattica. Ad esempio, anziché avere Path=TaskName, è necessario impostare XPath su @TaskName (se TaskName è un attributo del nodo XML).
Ora ListBox avrà l'aspetto seguente:
Creazione di DataTemplate come una risorsa
Nell'esempio precedente è stato definito l'oggetto DataTemplate inline. È più comune definire questo oggetto nella sezione risorse in modo che possa essere riutilizzabile, come 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>
È ora possibile utilizzare myTaskTemplate come risorsa, come nell'esempio seguente:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"/>
Poiché myTaskTemplate è una risorsa, è possibile utilizzarlo in altri controlli che dispongono di una proprietà che accetta un tipo DataTemplate. Come illustrato nell'esempio precedente, per gli oggetti ItemsControl quali ListBox, si tratta della proprietà ItemTemplate. Per gli oggetti ContentControl, si tratta della proprietà ContentTemplate.
Proprietà DataType
La classe DataTemplate ha una proprietà DataType molto simile alla proprietà TargetType della classe Style. Pertanto, anziché specificare x:Key per l'oggetto DataTemplate nell'esempio 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>
Questo oggetto DataTemplate viene automaticamente applicato a tutti gli oggetti Task. Si noti che in questo caso x:Key viene impostato implicitamente. Pertanto, se si assegna a questo oggetto DataTemplate un valore x:Key, viene eseguito l'override del valore x:Key implicito e DataTemplate non viene applicato automaticamente.
Se si associa un oggetto ContentControl a un insieme di oggetti Task, ContentControl non utilizza automaticamente l'oggetto DataTemplate illustrato in precedenza. Questo comportamento è dovuto al fatto che l'associazione su un oggetto ContentControl richiede ulteriori informazioni per distinguere se si desidera effettuare l'associazione a un insieme intero o a singoli oggetti. Se l'oggetto ContentControl sta rilevando la selezione di un tipo ItemsControl, è possibile impostare la proprietà Path dell'oggetto ContentControl associato a "/" in modo da indicare che si è interessati all'elemento corrente. Per un esempio, vedere Procedura: eseguire l'associazione di un insieme e visualizzare informazioni in base alla selezione effettuata. In caso contrario, è necessario specificare in modo esplicito l'oggetto DataTemplate impostando la proprietà ContentTemplate.
La proprietà DataType è particolarmente utile quando si ha un oggetto CompositeCollection costituito da tipi diversi di oggetti dati. Per un esempio, vedere Procedura: implementare un oggetto CompositeCollection.
Ulteriori aggiunte a DataTemplate
Attualmente i dati vengono visualizzati con le informazioni necessarie, ma sono possibili miglioramenti. Di seguito verrà migliorata la presentazione con l'aggiunta di Border, Grid e alcuni elementi TextBlock 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>
Nella schermata riportato di seguito viene illustrato l'oggetto ListBox con l'oggetto DataTemplate modificato:
È possibile impostare HorizontalContentAlignment su Stretch sull'oggetto ListBox per assicurarsi che la larghezza degli elementi occupi l'intero spazio:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"
HorizontalContentAlignment="Stretch"/>
Con la proprietà HorizontalContentAlignment impostata su Stretch, ListBox ha ora l'aspetto seguente:
Utilizzo di DataTrigger per applicare i valori di proprietà
Nella presentazione corrente non viene indicato se Task è un'attività di tipo domestico o aziendale. Tenere presente che l'oggetto Task ha una proprietà TaskType di tipo TaskType che è un'enumerazione con valori Home e Work.
Nell'esempio riportato di seguito DataTrigger imposta l'oggetto BorderBrush dell'elemento denominato border su Yellow se la proprietà TaskType è 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 ha ora l'aspetto seguente. Le attività domestiche sono visualizzate con un bordo giallo, mentre quelle aziendali hanno un bordo azzurro:
In questo esempio l'oggetto DataTrigger utilizza Setter per impostare un valore di proprietà. Anche le classi trigger dispongono di proprietà EnterActions e ExitActions che consentono di avviare un insieme di azioni, ad esempio le animazioni. È inoltre disponibile una classe MultiDataTrigger che consente di applicare modifiche basate su più valori di proprietà con associazione a dati.
In alternativa, per ottenere lo stesso effetto è possibile associare la proprietà BorderBrush alla proprietà TaskType e utilizzare un convertitore di valori per restituire il colore in base al valore TaskType. La creazione dell'effetto precedente utilizzando un convertitore offre una maggiore efficienza in termini di prestazioni. Inoltre, la creazione di un convertitore personalizzato garantisce maggiore flessibilità grazie alla possibilità di utilizzare logica personalizzata. La scelta ottimale dipende in ultima analisi dallo scenario e dalle preferenze. Per informazioni su come scrivere un convertitore, vedere IValueConverter.
Elementi appartenenti a DataTemplate
Nell'esempio precedente, è stato inserito un trigger in DataTemplate utilizzando la proprietà DataTemplate.Triggers. L'oggetto Setter del trigger imposta il valore di una proprietà di un elemento (elemento Border) incluso in DataTemplate. Tuttavia, se le proprietà relative ai Setters non sono proprietà di elementi inclusi nell'oggetto DataTemplate corrente, può essere consigliabile impostare le proprietà utilizzando un oggetto Style per la classe ListBoxItem (se il controllo associato è un oggetto ListBox). Ad esempio, se si desidera che l'oggetto Trigger animi il valore Opacity dell'elemento quando si posiziona il mouse su un elemento, occorre definire trigger in uno stile ListBoxItem. Per un esempio, vedere Esempio di introduzione agli stili e ai modelli (la pagina potrebbe essere in inglese).
In genere, tenere presente che l'oggetto DataTemplate viene applicato a ogni oggetto ListBoxItem generato (per ulteriori informazioni sulle effettive modalità di applicazione, vedere la pagina relativa a ItemTemplate). L'oggetto DataTemplate riguarda esclusivamente 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 il tipo di layout applicato dall'oggetto ListBox per gli elementi, non fanno parte della definizione di DataTemplate. Per un esempio, vedere la sezione Applicazione di stili e modelli di ItemsControl.
Scelta di DataTemplate in base alle proprietà dell'oggetto dati
Nella sezione Proprietà DataType Property, è stato illustrato come definire modelli di dati diversi per diversi oggetti dati. Si tratta di una soluzione particolarmente utile quando è presente un oggetto CompositeCollection di diversi tipi o insiemi con elementi di diversi tipi. Nella sezione Utilizzo di DataTrigger per applicare valori di proprietà, è stato illustrato che se è presente un insieme dello stesso tipo di oggetti dati è possibile creare DataTemplate e utilizzare trigger per applicare modifiche in base ai valori di proprietà di ogni oggetto dati. Tuttavia, i trigger consentono di applicare valori di proprietà o di avviare animazioni, ma non offrono la flessibilità necessaria per ricostruire la struttura degli oggetti dati. In alcuni scenari può essere necessario creare un oggetto DataTemplate diverso per gli oggetti dati dello stesso tipo, ma con proprietà diverse.
Ad esempio, può essere opportuno fornire un aspetto completamente diverso a un oggetto Task quando il relativo valore Priority è pari a 1, affinché funga da avviso. In tal caso, creare un oggetto DataTemplate per la visualizzazione di oggetti Task con priorità elevata. Verrà quindi aggiunto il seguente oggetto DataTemplate alla sezione risorse:
<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>
Si noti che in questo esempio viene utilizzata la proprietà DataTemplate.Resources. Le risorse definite in questa sezione sono condivise dagli elementi in DataTemplate.
Per fornire la logica per scegliere quale DataTemplate utilizzare in base al valore Priority dell'oggetto dati, creare una sottoclasse di DataTemplateSelector ed eseguire l'override del metodo SelectTemplate. Nell'esempio seguente, il metodo SelectTemplate fornisce la logica per restituire il modello adatto basato sul valore della proprietà Priority. Il modello da restituire viene cercato nelle risorse dell'elemento Window di protezione.
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
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;
}
}
}
È possibile quindi dichiarare l'oggetto TaskListDataTemplateSelector una risorsa:
<Window.Resources>
...
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
...
</Window.Resources>
Per utilizzare la risorsa selettore di modello, assegnarlo alla proprietà ItemTemplateSelector del controllo ListBox. Il controllo ListBox chiama il metodo SelectTemplate del controllo TaskListDataTemplateSelector per ciascuno degli elementi dell'insieme sottostante. La chiamata passa l'oggetto di dati come parametro dell'elemento. L'oggetto DataTemplate restituito dal metodo viene quindi applicato a tale oggetto dati.
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
Dopo aver posizionato il selettore di modelli, ListBox ha l'aspetto seguente:
Con questo passaggio si conclude la discussione di questo esempio. Per l'esempio completo, vedere Esempio di introduzione ai modelli di dati (la pagina potrebbe essere in inglese).
Applicazione di stili e modelli di ItemsControl
Anche se ItemsControl non è l'unico tipo di controllo con cui è possibile utilizzare DataTemplate, costituisce uno scenario molto comune per l'associazione di ItemsControl a un insieme. Nella sezione Elementi appartenenti a DataTemplate è stato illustrato che la definizione di DataTemplate deve riguardare solo la presentazione dei dati. Per sapere quando non è consigliabile utilizzare DataTemplate, è importante comprendere le diverse proprietà di stile e modello fornite da ItemsControl. Nell'esempio riportato di seguito viene illustrata la funzione di ciascuna di queste proprietà. L'oggetto ItemsControl di questo esempio è associato allo stesso insieme Tasks dell'esempio precedente. A scopo dimostrativo, gli stili e i modelli di questo esempio sono tutti dichiarati inline.
<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>
Nella figura riportata di seguito viene illustrato il risultato del rendering dell'esempio:
Si noti che, anziché utilizzare ItemTemplate, è possibile utilizzare ItemTemplateSelector. Per un esempio, fare riferimento alla sezione precedente. Analogamente, anziché utilizzare ItemContainerStyle, è possibile utilizzare ItemContainerStyleSelector.
Vi sono altre due proprietà correlate allo stile di ItemsControl che non sono illustrate qui, ovvero GroupStyle e GroupStyleSelector.
Supporto per dati gerarchici
Finora sono state analizzate unicamente l'associazione e la visualizzazione di un solo insieme. Talvolta è presente un insieme che contiene altri insiemi. La classe HierarchicalDataTemplate è progettata per essere utilizzata con tipi HeaderedItemsControl per visualizzare tali dati. Nell'esempio riportato di seguito ListLeagueList è un elenco di oggetti League. Ogni oggetto League ha un oggetto Name e un insieme di oggetti Division. Ogni oggetto Division ha un oggetto Name e un insieme di oggetti Team e ciascun oggetto Team ha un oggetto Name.
<Window x:Class="SDKSample.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://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>
Nell'esempio viene illustrata la possibilità di utilizzare HierarchicalDataTemplate per visualizzare in modo semplice dati elenco che contengono altri elenchi. Di seguito è disponibile una schermata dell'esempio.
Vedere anche
Attività
Procedura: trovare elementi generati da un oggetto DataTemplate
Concetti
Ottimizzazione delle prestazioni: associazione dati
Applicazione di stili e modelli
Cenni preliminari sull'associazione dati
Panoramica sui modelli e sugli stili di intestazione delle colonne in GridView