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

Le modèle de création de modèles de données WPF offre une grande souplesse pour définir la présentation des données. Les contrôles WPF possèdent des fonctionnalités intégrées permettant de prendre en charge la personnalisation de la présentation des données. Cette rubrique montre tout d’abord comment définir une DataTemplate autre fonctionnalité de création de modèles de données, comme la sélection de modèles basés sur une logique personnalisée et la prise en charge de l’affichage des données hiérarchiques.

Prérequis

Cette rubrique porte sur les fonctionnalités de création de modèles de données ; elle ne constitue pas une introduction aux concepts de liaison de données. Pour plus d’informations sur les concepts de base de la liaison de données, consultez la Vue d’ensemble de la liaison de données.

DataTemplate est à propos de la présentation des données et est l’une des nombreuses fonctionnalités fournies par le modèle de style et de création de modèles WPF. Pour obtenir une présentation du modèle de style et de création de modèles WPF, par exemple comment utiliser un Style pour définir des propriétés sur des contrôles, consultez la rubrique Style et Création de modèles.

En outre, il est important de comprendre Resources, qui sont essentiellement ce qui permet aux objets tels que Style et DataTemplate d’être réutilisables. Pour plus d’informations sur les ressources, consultez la page Ressources XAML.

Informations de base sur la création de modèles de données

Pour illustrer pourquoi DataTemplate il est important, passons en revue un exemple de liaison de données. Dans cet exemple, nous avons un ListBox objet lié à une liste d’objets Task . Chaque objet Task a un TaskName (chaîne), une Description (chaîne), une Priority (entier) et une propriété de type TaskType, qui est un Enum avec des valeurs Home et 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>

Sans DataTemplate

Sans un DataTemplate, notre ListBox apparence actuelle ressemble à ceci :

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

Ce qui se passe, c’est que sans instructions spécifiques, les ListBox appels ToString par défaut lors de la tentative d’affichage des objets dans la collection. Par conséquent, si l’objet Task remplace la ToString méthode, l’objet ListBox affiche la représentation sous-jacente de chaque objet source.

Par exemple, si la classe Task remplace la méthode ToString de cette façon, où name correspond au champ de la propriété TaskName :

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

Ensuite, le ListBox résultat ressemble à ce qui suit :

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

Toutefois, cet affichage est restrictif et rigide. En outre, si vous effectuez une liaison à des données XML, vous ne pouvez pas remplacer ToString.

Définir un DataTemplate simple

La solution consiste à définir un DataTemplate. Une façon de le faire est de définir la ItemTemplate propriété de l’objet ListBox sur un DataTemplate. Ce que vous spécifiez dans votre DataTemplate devient la structure visuelle de votre objet de données. Ce qui suit DataTemplate est assez simple. Nous donnons des instructions indiquant que chaque élément apparaît sous la forme de trois TextBlock éléments dans un StackPanel. Chaque TextBlock élément est lié à une propriété de la 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>

Les données sous-jacentes des exemples de cette rubrique sont une collection d’objets CLR. Si vous effectuez une liaison à des données XML, les concepts fondamentaux sont identiques, mais il existe une légère différence syntactique. Par exemple, au lieu d’avoir Path=TaskName, vous devez définir @TaskNameXPath sur (s’il s’agit TaskName d’un attribut de votre nœud XML).

Maintenant, notre ListBox apparence ressemble à ce qui suit :

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

Créer un DataTemplate comme ressource

Dans l’exemple ci-dessus, nous avons défini l’inline DataTemplate . Il est plus courant de le définir dans la section des 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 comme ressource, comme dans l’exemple suivant :

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

Étant donné qu’il myTaskTemplate s’agit d’une ressource, vous pouvez désormais l’utiliser sur d’autres contrôles qui ont une propriété qui prend un DataTemplate type. Comme indiqué ci-dessus, pour ItemsControl les objets, tels que le ListBox, il s’agit de la ItemTemplate propriété. Pour ContentControl les objets, il s’agit de la ContentTemplate propriété.

La propriété DataType

La DataTemplate classe a une DataType propriété très similaire à la TargetType propriété de la Style classe. Par conséquent, au lieu de spécifier une x:Key pour l’exemple DataTemplate ci-dessus, vous pouvez effectuer les opérations suivantes :

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

Cette opération DataTemplate est appliquée automatiquement à tous les Task objets. Notez que, dans ce cas, la x:Key est définie implicitement. Par conséquent, si vous affectez cette DataTemplatex:Key valeur, vous substituez l’implicite x:Key et le DataTemplate ne serait pas appliqué automatiquement.

Si vous liez un ContentControl à une collection d’objets Task , il ContentControl n’utilise pas automatiquement les éléments ci-dessus DataTemplate . Cela est dû au fait que la liaison sur un ContentControl besoin d’informations supplémentaires permet de distinguer si vous souhaitez établir une liaison à une collection entière ou aux objets individuels. Si vous ContentControl effectuez le suivi de la sélection d’un ItemsControl type, vous pouvez définir la Path propriété de la ContentControl liaison sur «/ » pour indiquer que vous êtes intéressé par l’élément actif. Vous trouverez un exemple sur la page Effectuer une liaison à une collection et afficher des informations en fonction de la sélection. Dans le cas contraire, vous devez spécifier la DataTemplate valeur explicitement en définissant la ContentTemplate propriété.

La DataType propriété est particulièrement utile lorsque vous disposez CompositeCollection de différents types d’objets de données. Vous trouverez un exemple sur la page Implémenter une CompositeCollection.

Ajouter d’autres informations au DataTemplate

Actuellement, les données s’affichent avec les informations nécessaires, mais on peut sans problème aller plus loin. Nous allons améliorer la présentation en ajoutant un Border, un , et Gridcertains TextBlock éléments 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 montre l’opération ListBox modifiée DataTemplate:

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

Nous pouvons définir HorizontalContentAlignmentStretch la valeur sur la ListBox zone pour vous assurer que la largeur des éléments occupe l’espace entier :

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

Avec la HorizontalContentAlignment propriété définie sur Stretch, le ListBox résultat ressemble maintenant à ceci :

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

Utiliser DataTrigger pour appliquer des valeurs de propriété

La présentation actuelle n’indique pas si une Task est une tâche privée ou une tâche professionnelle. N’oubliez pas que l’objet Task a une propriété TaskType de type TaskType, qui est une énumération avec les valeurs Home et Work.

Dans l’exemple suivant, le DataTrigger jeu de l’élément BorderBrush nommé borderYellow si la TaskType propriété est 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>

Notre application se présente maintenant ainsi. Les tâches privées apparaissent avec une bordure jaune et les tâches professionnelles avec une bordure cyan :

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

Dans cet exemple, l’exemple DataTrigger utilise un Setter pour définir une valeur de propriété. Les classes de déclencheur ont également les propriétés et ExitActions les EnterActions propriétés qui vous permettent de démarrer un ensemble d’actions telles que des animations. En outre, il existe également une MultiDataTrigger classe qui vous permet d’appliquer des modifications en fonction de plusieurs valeurs de propriété liées aux données.

Une autre façon d’obtenir le même effet consiste à lier la BorderBrush propriété à la TaskType propriété et à utiliser un convertisseur de valeur pour retourner la couleur en fonction de la TaskType valeur. Du point de vue des performances, il est légèrement plus efficace d’utiliser un convertisseur pour produire cet effet. En outre, le fait de créer votre propre convertisseur vous offre plus de souplesse, car vous fournissez votre propre logique. En somme, la technique choisie dépend du scénario et des préférences. Pour plus d’informations sur l’écriture d’un convertisseur, consultez IValueConverter.

Que contient un DataTemplate ?

Dans l’exemple précédent, nous avons placé le déclencheur dans la propriété à l’aide DataTemplate de la DataTemplate.Triggers propriété. Le Setter déclencheur définit la valeur d’une propriété d’un élément (l’élément Border ) qui se trouve dans le DataTemplate. Toutefois, si les propriétés que vous Setters êtes concernées ne sont pas des propriétés d’éléments qui se trouvent dans le courant DataTemplate, il peut être plus approprié de définir les propriétés à l’aide d’une Style valeur correspondant à la ListBoxItem classe (si le contrôle que vous liez est un ListBox). Par exemple, si vous souhaitez animer la Opacity valeur de l’élément lorsqu’une souris pointe vers un élément, vous définissez Trigger des déclencheurs dans un ListBoxItem style. Vous trouverez un exemple sur la page Présentation d’un exemple de création de style et de modèle.

En règle générale, gardez à l’esprit que l’application DataTemplate est appliquée à chacun des éléments générés ListBoxItem (pour plus d’informations sur la façon et l’emplacement d’application, consultez la ItemTemplate page.). Votre DataTemplate préoccupation concerne uniquement la présentation et l’apparence des objets de données. Dans la plupart des cas, tous les autres aspects de la présentation, tels que l’apparence d’un élément lorsqu’il est sélectionné ou la façon dont les ListBox éléments sont exposés, n’appartiennent pas à la définition d’un DataTemplate. Vous trouverez un exemple dans la section Création de styles et de modèles pour un ItemsControl.

Choisir un DataTemplate en fonction des propriétés de l’objet de données

Dans la section La propriété DataType, nous avons expliqué que vous pouvez définir différents modèles de données pour différents objets de données. Cela est particulièrement utile lorsque vous avez un CompositeCollection type ou des collections différents avec des éléments de différents types. Dans la section Utiliser DataTriggers pour appliquer des valeurs de propriété, nous avons montré que si vous avez une collection du même type d’objets de données que vous pouvez créer, DataTemplate puis utilisez des déclencheurs pour appliquer des modifications en fonction des valeurs de propriété de chaque objet de données. Toutefois, si les déclencheurs vous permettent d’appliquer des valeurs de propriété ou de lancer des animations, ils ne vous donnent pas la possibilité de reconstruire la structure de vos objets de données. Certains scénarios peuvent nécessiter la création d’un autre DataTemplate type pour les objets de données qui sont de même type, mais qui ont des propriétés différentes.

Par exemple, lorsque un objet Task a une valeur Priority égale à 1, vous avez la possibilité de lui donner un aspect différent qui vous servira d’alerte. Dans ce cas, vous créez un DataTemplate pour l’affichage des objets de haute priorité Task . Ajoutons ce qui suit DataTemplate à la section 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>

Cet exemple utilise la propriété DataTemplate.Resources . Les ressources définies dans cette section sont partagées par les éléments dans le DataTemplate.

Pour fournir une logique pour choisir laquelle DataTemplate utiliser en fonction de la Priority valeur de l’objet de données, créez une sous-classe de DataTemplateSelector la méthode et remplacez la SelectTemplate méthode. Dans l’exemple suivant, la SelectTemplate méthode fournit une logique pour retourner le modèle approprié en fonction de la valeur de la Priority propriété. Le modèle à retourner se trouve dans les ressources de l’élément enveling 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

On peut ensuite déclarer le TaskListDataTemplateSelector comme ressource :

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

Pour utiliser la ressource de sélecteur de modèle, affectez-la à la ItemTemplateSelector propriété du ListBox. Appelle ListBox la SelectTemplate méthode de chacun TaskListDataTemplateSelector des éléments de la collection sous-jacente. L’appel passe l’objet de données en paramètre d’élément. L’objet DataTemplate de données retourné par la méthode est ensuite appliqué à cet objet de données.

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

Avec le sélecteur de modèle en place, le ListBox présent apparaît comme suit :

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

Ainsi se conclut notre étude de cet exemple. Vous trouverez l’exemple complet sur la page Présentation d’un exemple de création de modèles de données.

Création de styles et de modèles pour un ItemsControl

Même si le ItemsControl type de contrôle n’est pas le seul avec lequel vous pouvez utiliser, DataTemplate il s’agit d’un scénario très courant pour lier un ItemsControl à une collection. Dans la section Qu’appartient un DataTemplate , nous avons discuté que la définition de vos DataTemplate données ne devrait être concernée que par la présentation des données. Pour savoir quand il n’est pas approprié d’utiliser un modèle DataTemplate , il est important de comprendre les différentes propriétés de style et de modèle fournies par le ItemsControl. L’exemple suivant est conçu pour illustrer la fonction de chacune de ces propriétés. L’exemple ItemsControl suivant est lié à la même Tasks collection que dans l’exemple précédent. À des fins de démonstration, les styles et les modèles de 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>

Voici une capture d’écran du rendu de l’exemple :

ItemsControl example screenshot

Notez qu’au lieu d’utiliser le ItemTemplate, vous pouvez utiliser le ItemTemplateSelector. Vous trouverez un exemple dans la section précédente. De même, au lieu d’utiliser le ItemContainerStyle, vous avez la possibilité d’utiliser le ItemContainerStyleSelector.

Deux autres propriétés liées au style de celles ItemsControl qui ne sont pas affichées ici sont GroupStyle et GroupStyleSelector.

Prise en charge des données hiérarchiques

Jusqu’à présent, nous avons seulement vu comment lier et afficher une collection unique. Une collection peut contenir d’autres collections. La HierarchicalDataTemplate classe est conçue pour être utilisée avec HeaderedItemsControl des types pour afficher ces données. Dans l’exemple suivant, ListLeagueList est une liste d’objets League. Chaque objet League a 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="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’exemple montre qu’avec l’utilisation de , vous pouvez facilement afficher des données de HierarchicalDataTemplateliste qui contiennent d’autres listes. Voici une capture d’écran de l’exemple.

HierarchicalDataTemplate sample screenshot

Voir aussi