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:
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:
Questa soluzione risulta tuttavia limitante e poco flessibile. Inoltre, se si esegue il binding ai dati XML, non sarà possibile eseguire l'override ToString
di .
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:
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:
È 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:
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:
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:
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:
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.
Vedi anche
.NET Desktop feedback