Vue d'ensemble des modèles de données
Le modèle de modélisation des données de WPF vous offre une grande souplesse dans la définition de la présentation de vos données. Les contrôles WPF ont des fonctionnalités intégrées qui prennent en charge la personnalisation de la présentation des données. Cette rubrique montre d'abord comment définir un DataTemplate, puis présente d'autres fonctionnalités de modélisation des données, comme la sélection de modèles en fonction d'une logique personnalisée et la prise en charge de l'affichage de données hiérarchiques.
Cette rubrique comprend les sections suivantes.
- Composants requis
- Notions de base des modèles de données
- Ajout d'informations au DataTemplate
- Sélection d'un DataTemplate en fonction des propriétés de l'objet de données
- Styles et modèles d'un ItemsControl
- Prise en charge des données hiérarchiques
- Rubriques connexes
Composants requis
Cette rubrique se concentre sur les fonctionnalités liées aux modèles de données et ne constitue pas une introduction aux concepts de liaison de données. Pour plus d'informations sur les concepts de liaison de données de base, consultez Vue d'ensemble de la liaison de données.
DataTemplate concerne la présentation de données et constitue l'une des nombreuses fonctionnalités fournies par les modèles de style et de modélisation de WPF. Pour obtenir une introduction sur les styles et modèles WPF, comme l'utilisation de Style pour définir les propriétés de contrôles, consultez la rubrique Application d'un style et création de modèles.
De plus, il est important de comprendre les éléments Resources, qui permettent essentiellement de réutiliser les objets tels que Style et DataTemplate. Pour plus d'informations sur les ressources, consultez Vue d'ensemble des ressources.
Notions de base des modèles de données
Cette section comprend les sous-sections suivantes.
- Sans DataTemplate
- Définition d'un DataTemplate simple
- Création du DataTemplate en tant que ressource
- Propriété DataType
Cette section explique l'importance de DataTemplate à l'aide d'un exemple de liaison de données. Dans cet exemple, un ListBox est lié à une liste d'objets Task. Chaque objet Task contient un TaskName (chaîne), un Description (chaîne), un Priority (entier) et une propriété du type TaskType, qui correspond à un Enum comportant les valeurs Home et 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>
Sans DataTemplate
Sans DataTemplate, ListBox se présente actuellement comme suit :
Sans instruction spécifique, ListBox appelle par défaut ToString lors de l'affichage des objets dans la collection. Par conséquent, si l'objet Task substitue la méthode ToString, ListBox affiche la représentation sous forme de chaîne de chaque objet source dans la collection sous-jacente.
Par exemple, si la classe Task substitue la méthode ToString, où name correspond au champ de la propriété TaskName :
Public Overrides Function ToString() As String
Return _name.ToString()
End Function
public override string ToString()
{
return name.ToString();
}
ListBox se présente alors comme suit :
Toutefois, ce principe est restrictif et rigide. En outre, si vous créez une liaison avec des données XML, vous ne pouvez pas substituer ToString.
Définition d'un DataTemplate simple
La solution consiste à définir un DataTemplate. Pour ce faire, vous pouvez définir la propriété ItemTemplate de ListBox sur DataTemplate. La spécification de DataTemplate détermine la structure visuelle de l'objet de données. Le DataTemplate suivant est assez simple. Selon les instructions, chaque élément apparaît en tant que trois éléments TextBlock dans StackPanel. Chaque élément TextBlock est lié à une propriété de la 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>
Les données sous-jacentes des exemples présentés dans cette rubrique correspondent à une collection d'objets CLR. Si vous créez une liaison avec des données XML, les concepts fondamentaux sont identiques, mais il existe une petite différence au niveau de la syntaxe. Par exemple, au lieu de Path=TaskName, vous définiriez XPath sur @TaskName (si TaskName correspond à un attribut du nœud XML).
À présent, ListBox se présente comme suit :
Création du DataTemplate en tant que ressource
Dans l'exemple ci-dessus, nous avons défini DataTemplate inline. Il est plus courant de le définir dans la section de ressources afin d'en faire un objet réutilisable, comme dans l'exemple suivant :
<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>
Vous pouvez à présent utiliser myTaskTemplate en tant que ressource, comme dans l'exemple suivant :
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"/>
Comme myTaskTemplate est une ressource, vous pouvez à présent l'utiliser dans d'autres contrôles dont la propriété est un type DataTemplate. Comme indiqué ci-dessus, pour les objets ItemsControl tels que ListBox, il s'agit de la propriété ItemTemplate. Pour les objets ContentControl, il s'agit de la propriété ContentTemplate.
Propriété DataType
La classe DataTemplate comporte une propriété DataType similaire à la propriété TargetType de la classe Style. Par conséquent, au lieu de spécifier x:Key pour DataTemplate dans l'exemple ci-dessus, vous pouvez effectuer l'opération suivante :
<DataTemplate DataType="{x:Type local:Task}">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
Ce DataTemplate est automatiquement appliqué à tous les objets Task. Notez que dans ce cas, x:Key est défini de manière implicite. Par conséquent, si vous affectez une valeur x:Key à DataTemplate, vous substituez le x:Key implicite et DataTemplate n'est pas appliqué automatiquement.
Si vous liez ContentControl à une collection d'objets Task, ContentControl n'utilise pas automatiquement le DataTemplate ci-dessus. En effet, la liaison de ContentControl nécessite davantage d'informations pour déterminer si vous souhaitez créer une liaison avec une collection complète ou avec des objets donnés. Si ContentControl effectue le suivi de la sélection d'un type ItemsControl, vous pouvez définir la propriété Path de ContentControl qui crée une liaison avec "/" pour indiquer que vous vous intéressez à l'élément actif. Pour obtenir un exemple, consultez Comment : effectuer une liaison à une collection et afficher des informations basées sur la sélection. Dans le cas contraire, vous devez spécifier explicitement DataTemplate en définissant la propriété ContentTemplate.
La propriété DataType s'avère tout particulièrement utile lorsque vous disposez d'un CompositeCollection présentant différents types d'objets de données. Pour obtenir un exemple, consultez Comment : implémenter une classe CompositeCollection.
Ajout d'informations au DataTemplate
Les données apparaissent actuellement avec les informations nécessaires, mais il est possible d'apporter des améliorations. Pour améliorer la présentation, ajoutons un Border, un Grid et des éléments TextBlock qui décrivent les données affichées.
<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>
La capture d'écran suivante affiche ListBox avec le DataTemplate modifié :
Nous pouvons définir HorizontalContentAlignment sur Stretch dans ListBox afin de garantir que la largeur des éléments occupe tout l'espace disponible :
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplate="{StaticResource myTaskTemplate}"
HorizontalContentAlignment="Stretch"/>
La propriété HorizontalContentAlignment étant définie sur Stretch, ListBox se présente comme suit :
Utiliser DataTrigger pour appliquer des valeurs de propriété
La présentation actuelle n'indique pas si Task correspond à une tâche privée (Home) ou professionnelle (Work). Souvenez-vous que l'objet Task comporte une propriété TaskType du type TaskType, c'est-à-dire une énumération avec les valeurs Home et Work.
Dans l'exemple suivant, DataTrigger définit le BorderBrush de l'élément nommé border sur Yellow si la propriété TaskType correspond à 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>
À présent, l'application se présente comme suit. Les tâches privées apparaissent avec une bordure jaune, tandis que les tâches professionnelles s'affichent avec une bordure cyan :
Dans cet exemple, DataTrigger utilise Setter pour définir une valeur de propriété. Les classes de déclencheur comportent également les propriétés EnterActions et ExitActions qui permettent de démarrer un jeu d'actions telles que des animations. De plus, une classe MultiDataTrigger permet également d'appliquer des modifications en fonction de plusieurs valeurs de propriétés liées aux données.
Il est également possible d'obtenir le même effet en liant la propriété BorderBrush à la propriété TaskType et en utilisant un convertisseur de valeur pour retourner la couleur d'après la valeur TaskType. La création de l'effet précité à l'aide d'un convertisseur est un peu plus efficace en termes de performances. En outre, la création d'un convertisseur personnel procure davantage de souplesse, car vous fournissez votre propre logique. Enfin, la technique choisie dépend de votre scénario et de vos préférences. Pour plus d'informations sur l'écriture d'un convertisseur, consultez IValueConverter.
Éléments d'un DataTemplate
Dans l'exemple précédent, nous avons placé le déclencheur dans DataTemplate à l'aide de la propriété DataTemplate.Triggers. Le Setter du déclencheur définit la valeur d'une propriété d'un élément (Border) situé dans DataTemplate. Toutefois, si les propriétés liées aux éléments Setters ne sont pas des propriétés d'éléments situés dans le DataTemplate actuel, il peut s'avérer préférable de définir les propriétés à l'aide d'un Style destiné à la classe ListBoxItem (si le contrôle lié est un ListBox). Par exemple, si vous souhaitez que Trigger anime la valeur Opacity de l'élément lorsqu'un élément est pointé par la souris, vous définissez des déclencheurs dans un style ListBoxItem. Pour obtenir un exemple, consultez la rubrique Introduction aux styles et aux modèles, exemple.
De manière générale, n'oubliez pas que le DataTemplate est appliqué à chaque ListBoxItem généré (pour plus d'informations sur son application, consultez la page ItemTemplate). Le DataTemplate ne concerne que la présentation et l'apparence des objets de données. Dans la plupart des cas, tous les autres aspects de présentation, comme l'aspect d'un élément sélectionné ou la disposition des éléments par ListBox, ne font pas partie de la définition d'un DataTemplate. Pour obtenir un exemple, consultez la section Styles et modèles d'un ItemsControl.
Sélection d'un DataTemplate en fonction des propriétés de l'objet de données
Dans la section Propriété DataType, vous avez appris qu'il est possible de définir divers modèles de données en fonction des différents objets de données. Cela s'avère particulièrement utile lorsque vous disposez d'un CompositeCollection de types différents ou de collections comportant des éléments de types différents. Dans la section Utiliser DataTrigger pour appliquer des valeurs de propriété, nous avons montré que si vous disposez d'une collection du même type d'objets de données, vous pouvez créer un DataTemplate, puis utiliser des déclencheurs pour appliquer des modifications en fonction des valeurs de propriétés de chaque objet de données. Toutefois, les déclencheurs vous permettent d'appliquer des valeurs de propriétés ou de démarrer des animations, mais ils ne permettent pas de reconstruire la structure des objets de données. Certains scénarios peuvent nécessiter la création d'un autre DataTemplate pour les objets de données d'un même type, mais comportant des propriétés différentes.
Par exemple, lorsqu'un objet Task a une valeur Priority égale à 1, vous pouvez modifier son aspect pour qu'il fasse office d'alerte. Dans ce cas, vous créez un DataTemplate pour l'affichage des objets Task de haute priorité. Ajoutons le DataTemplate suivant à la section de ressources :
<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>
Notez que cet exemple utilise la propriété DataTemplate.Resources. Les ressources définies dans cette section sont partagées par les éléments dans DataTemplate.
Pour fournir une logique permettant de choisir le modèle DataTemplate à utiliser en fonction de la valeur Priority de l'objet de données, créez une sous-classe de DataTemplateSelector et substituez la méthode SelectTemplate. Dans l'exemple suivant, la méthode SelectTemplate fournit une logique pour retourner le modèle approprié en fonction de la valeur de la propriété Priority. Le modèle à retourner est recherché dans les ressources de l'élément Window enveloppant.
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;
}
}
}
Vous pouvez ensuite déclarer TaskListDataTemplateSelector en tant que ressource :
<Window.Resources>
...
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
...
</Window.Resources>
Pour utiliser la ressource de sélecteur de modèle, assignez-la à la propriété ItemTemplateSelector de la méthode ListBox. ListBox appelle la méthode SelectTemplate de TaskListDataTemplateSelector pour chacun des éléments de la collection sous-jacente. L'appel passe l'objet de données en tant que paramètre de l'élément. Le DataTemplate retourné par la méthode est alors appliqué à cet objet de données.
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
Une fois le sélecteur de modèle en place, le ListBox se présente comme suit :
La description de cet exemple est à présent terminée. Pour obtenir l'exemple complet, consultez la rubrique Introduction aux modèles de données, exemple.
Styles et modèles d'un ItemsControl
ItemsControl n'est pas le seul type de contrôle avec lequel vous pouvez utiliser DataTemplate. Toutefois, il s'agit d'un scénario très souvent utilisé pour lier un ItemsControl à une collection. Dans la section Éléments d'un DataTemplate, nous avons vu que la définition de DataTemplate ne doit concerner que la présentation de données. Pour connaître les cas où il n'est pas recommandé d'utiliser DataTemplate, il est important de comprendre les différentes propriétés de style et de modèle fournies par ItemsControl. L'exemple suivant est conçu pour illustrer la fonction de chacune de ces propriétés. Dans cet exemple, ItemsControl est lié à la même collection Tasks que dans l'exemple précédent. À des fins de démonstration, les styles et modèles dans cet exemple sont tous déclarés 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>
La capture d'écran suivante illustre le rendu de l'exemple :
Notez qu'au lieu de ItemTemplate, vous pouvez utiliser ItemTemplateSelector. Consultez la section précédente pour obtenir un exemple. De même, au lieu de ItemContainerStyle, vous pouvez utiliser ItemContainerStyleSelector.
GroupStyle et GroupStyleSelector sont deux autres propriétés de ItemsControl qui sont liées au style mais qui ne sont pas montrées ici.
Prise en charge des données hiérarchiques
Jusqu'à présent, nous avons uniquement présenté la liaison et l'affichage d'une collection unique. Toutefois, une collection peut parfois contenir d'autres collections. La classe HierarchicalDataTemplate est conçue pour être utilisée avec les types HeaderedItemsControl pour afficher ces données. Dans l'exemple suivant, ListLeagueList est une liste d'objets League. Chaque objet League comporte un Name et une collection d'objets Division. Chaque Division a un Name et une collection d'objets Team et chaque objet Team a un 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>
L'exemple indique que grâce à HierarchicalDataTemplate, vous pouvez facilement afficher des données de liste qui contiennent d'autres listes. La capture d'écran suivante illustre l'exemple.
Voir aussi
Tâches
Comment : rechercher des éléments générés par DataTemplate
Concepts
Optimisation des performances : liaison de données
Application d'un style et création de modèles
Vue d'ensemble de la liaison de données
Vue d'ensemble des modèles et styles d'en-tête de colonne GridView