Vue d'ensemble des modèles de données

Mise à jour : novembre 2007

Grâce aux modèles de données WPF, vous disposez d'une grande souplesse pour définir la présentation de vos données. Les contrôles WPF intègrent des fonctionnalités assurant la prise en charge de la personnalisation de la présentation des données. Cette rubrique explique d'abord comment définir un DataTemplate, puis présente d'autres fonctionnalités liées aux modèles de 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 styles et les modèles 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 :

Capture d'écran : exemple de modèle de données

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 override string ToString()
{
    return name.ToString();
}

ListBox se présente alors comme suit :

Capture d'écran : exemple de modèle de données

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 :

Capture d'écran : exemple de modèle de données

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 "/" afin d'indiquer que l'élément actif vous intéresse. 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 DataTemplate de manière explicite 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é :

Capture d'écran : exemple de modèle de données

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 :

Capture d'écran : exemple de modèle de données

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 :

Capture d'écran : exemple de modèle de données

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. Pour obtenir un exemple similaire, consultez Comment : alterner la couleur d'arrière-plan pour les lignes d'un ListView. 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 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.

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

namespace SDKSample
{
    public class TaskListDataTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate
            SelectTemplate(object item, DependencyObject container)
        {
            if (item != null && item is Task)
            {
                Task taskitem = item as Task;
                Window window = Application.Current.MainWindow;

                if (taskitem.Priority == 1)
                    return
                        window.FindResource("importantTaskTemplate") as DataTemplate;
                else
                    return
                        window.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 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 :

Capture d'écran : exemple de modèle de données

La description de cet exemple est à présent terminée. Pour l'exemple complet, consultez 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 :

Capture d'écran : exemple de ItemsControl

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 objet Division possède un Name et une collection d'objets Team. Enfin, 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.

Capture d'écran : exemple de HierarchicalDataTemplate

Pour l'exemple complet, consultez Affichage de données hiérarchiques, 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