Vue d'ensemble de la liaison de données (WPF.NET)

La liaison de données dans Windows Presentation Foundation (WPF) offre un moyen simple et cohérent pour les applications de présenter et d’interagir avec les données. Les éléments peuvent être liés à des données émanant de diverses sources de données sous la forme d’objets .NET et XML. Tout ContentControl tel que Button et n’importe lequel ItemsControl, par exemple ListBox et ListView, disposent de fonctionnalités intégrées pour permettre un style flexible d’éléments de données uniques ou de collections d’éléments de données. Des vues de tri, filtrage et groupage peuvent être générées sur la base des données.

La liaison de données dans WPF présente plusieurs avantages par rapport aux modèles traditionnels, notamment une prise en charge de la liaison de données par une large gamme de propriétés, une représentation IU flexible des données et une séparation nette de la logique métier de IU.

Cet article traite d’abord des concepts fondamentaux de la liaison de données WPF, puis traite ensuite de l’utilisation de la classe Binding et d’autres fonctionnalités de liaison de données.

Important

La documentation du Guide du bureau pour .NET 6 et .NET 5 (y compris .NET Core 3.1) est en cours de construction.

Qu’est-ce que la liaison de données ?

La liaison de données est le processus qui établit une connexion entre l’IU d’application et les données affichées. Si la liaison est correctement paramétrée et si les données fournissent les notifications appropriées, lorsque les données changent de valeur, les éléments qui sont liés aux données reflètent automatiquement ces changements. La liaison de données peut également signifier que si une représentation externe des données dans un élément change, les données sous-jacentes peuvent être automatiquement mises à jour pour refléter les modifications. Par exemple, si l’utilisateur modifie la valeur dans un élément TextBox, la valeur sous-jacente est automatiquement mise à jour pour refléter cette modification.

Une utilisation typique de la liaison de données consiste à placer des données de configuration locale ou de serveur dans des formulaires ou d’autres contrôles d’IU. Dans WPF, ce concept est développé pour inclure la liaison d’une large gamme de propriétés à diverses sortes de sources de données. Dans WPF, les propriétés de dépendance d’éléments peuvent être liées à des objets .BET (y compris des objets ADO.NET ou des objets associés aux services web et aux propriétés Web) et des données XML.

Concepts de liaison de données de base

Quels que soient l’élément que vous liez et la nature de votre source de données, chaque liaison suit toujours le modèle illustré par l’illustration suivante.

Diagramme montrant le modèle de liaison de données de base.

Comme présenté sur l’illustration, la liaison de données est essentiellement la passerelle entre votre cible de liaison et de votre source de liaison. L’illustration présente les concepts de liaison de données WPF essentiels suivants :

  • En règle générale, chaque liaison a quatre composants :

    • Un objet cible de liaison.
    • Une propriété cible.
    • Une source de liaison.
    • Un chemin d’accès à la valeur de la source de liaison à utiliser.

    Par exemple, si vous avez lié le contenu de TextBox à la propriété Employee.Name, vous devez configurer votre liaison comme le tableau suivant :

    Paramètre Valeur
    Cible TextBox
    Propriété cible Text
    Objet source Employee
    Chemin de la valeur de l’objet source Name
  • La propriété cible doit être une propriété de dépendance.

    La plupart des propriétés UIElement sont des propriétés de dépendance, et la plupart des propriétés de dépendance, sauf celles qui sont en lecture seule, prennent en charge la liaison de données par défaut. Seuls les types dérivés de DependencyObject peuvent définir des propriétés de dépendance. Tous les types UIElement dérivent de DependencyObject.

  • Les sources de liaison ne sont pas limitées aux objets .NET personnalisés.

    Bien que cela n’est pas spécifié sur l’illustration, il convient de noter que l’objet de source de liaison n’est pas limité à un objet .NET personnalisé. La liaison de données WPF prend en charge les données sous la forme d’objets .NET, XML et même d’objets d’élément XAML. Pour fournir quelques exemples, votre source de liaison peut être une UIElement, un objet de liste, un objet ADO.NET ou des services web ou un xmlNode qui contient vos données XML. Pour plus d’informations, consultez Vue d’ensemble de la liaison de ressources.

Il est important de vous rappeler que lorsque vous établissez une liaison, vous liez une cible de liaison à une source de liaison. Par exemple, si vous affichez des données XML sous-jacentes dans une ListBox à l’aide de la liaison de données, vous liez vos ListBox à des données XML.

Pour établir une liaison, vous utilisez l’objet Binding. Le reste de cet article traite de nombreux concepts associés, certaines des propriétés et l’utilisation de l’objet Binding.

Contexte de données

Lorsque la liaison de données est déclarée sur les éléments XAML, elles résolvent la liaison de données en examinant leur propriété immédiate DataContext. Le contexte de données est généralement l’objet source de liaison pour l’évaluation du chemin de valeur source de liaison. Vous pouvez remplacer ce comportement dans la liaison et définir une valeur d’objet source de liaison spécifique. Si la propriété DataContext de l’objet hébergeant la liaison n’est pas définie, la propriété DataContext de l’élément parent est vérifiée, et ainsi de suite, jusqu’à la racine de l’arborescence d’objets XAML. En bref, le contexte de données utilisé pour résoudre la liaison est hérité du parent, sauf si elle est définie explicitement sur l’objet.

Les liaisons peuvent être configurées pour être résolues avec un objet spécifique, par opposition à l’utilisation du contexte de données pour la résolution de liaison. La spécification d’un objet source directement est utilisée lorsque, par exemple, vous liez la couleur de premier plan d’un objet à la couleur d’arrière-plan d’un autre objet. Le contexte de données n’est pas nécessaire, car la liaison est résolue entre ces deux objets. Inversement, les liaisons qui ne sont pas liées à des objets sources spécifiques utilisent la résolution de contexte de données.

Lorsque la propriété DataContext change, toutes les liaisons susceptibles d’être affectées par le contexte de données sont réévaluées.

Direction du flux de données

Comme indiqué par la flèche de la figure précédente, le flux de données d’une liaison peut passer de la cible de liaison à la source de liaison (par exemple, la valeur source change lorsqu’un utilisateur modifie la valeur d’une TextBox) et/ou de la source de liaison à la cible de liaison (par exemple, votre contenu TextBox est mis à jour avec des modifications dans la source de liaison) si la source de liaison fournit les notifications appropriées.

Vous souhaiterez peut-être que votre application permette aux utilisateurs de modifier les données et les propager à nouveau vers l’objet source. Ou vous souhaitez peut-être ne pas permettre aux utilisateurs de mettre à jour la source de données. Vous pouvez contrôler le flux de données en définissant le Binding.Mode.

Cette figure représente les différents types de flux de données :

Flux de données de la liaison de données

  • La liaison OneWay entraîne des modifications de la propriété source pour automatiquement mettre à jour la propriété cible, mais les modifications apportées à la propriété cible ne sont pas à nouveau propagées vers la propriété source. Ce type de liaison est approprié si le contrôle lié est implicitement en lecture seule. Par exemple, vous pouvez lier à une source comme un affichage de cotations boursières, ou il se peut que votre propriété cible ne possède aucune interface de contrôle pour apporter des modifications, comme une couleur d’arrière-plan liée aux données d’une table. S’il n’est pas nécessaire de surveiller les modifications de la propriété cible, l’utilisation du mode de liaison OneWay permet d’éviter la surcharge du mode de liaison TwoWay.

  • La liaison TwoWay engendre des modifications à la propriété source ou à la propriété cible pour mettre à jour automatiquement à l’autre. Ce type de liaison convient aux formulaires modifiables ou à d’autres scénarios d’IU entièrement interactifs. La plupart des propriétés sont par défaut liées à OneWay, mais certaines propriétés de dépendance (généralement des propriétés de contrôles modifiables par l’utilisateur, telles que TextBox.Text et checkBox.IsChecked, par défaut, sont liées à TwoWay. Un moyen de déterminer par programmation si une propriété de dépendance établit par défaut une liaison unidirectionnelle ou bidirectionnelle consiste à obtenir les métadonnées à l’aide de DependencyProperty.GetMetadata, puis à vérifier la valeur booléenne de la propriété FrameworkPropertyMetadata.BindsTwoWayByDefault.

  • OneWayToSource est l’inverse de la liaison OneWay ; il met à jour la propriété source quand la propriété cible change. Un exemple de scénario est si vous devez seulement réévaluer la valeur source à partir de l’IU.

  • La liaison OneTime n’est pas présentée sur l’illustration. Elle provoque l’initialisation par la propriété source de la propriété cible, mais elle ne propage pas les modifications ultérieures. Si le contexte de données change ou si l’objet dans le contexte des données change, la modification n'est pas répercutée dans la propriété cible. Ce type de liaison est approprié si les données sont réellement statiques ou un instantané de l’état actuel est approprié. Ce type de liaison est également utile si vous souhaitez initialiser votre propriété cible avec une valeur d’une propriété source et que le contexte de données n’est pas connu à l’avance. Ce mode est essentiellement d’une forme simplifiée de la liaison OneWay qui offre de meilleures performances dans les cas où la valeur source ne change pas.

Pour détecter les modifications sources (applicables aux liaisons OneWay et TwoWay ), la source doit implémenter un mécanisme de notification de modification de propriété approprié tel que INotifyPropertyChanged. Découvrez Comment implémenter une notification de modification de propriété (.NET Framework) pour obtenir un exemple d’implémentation INotifyPropertyChanged.

La page de la propriété Binding.Mode fournit plus d’informations sur les modes de liaison et un exemple montrant comment spécifier la direction d’une liaison.

Ce qui déclenche les mises à jour de la source

Les liaisons TwoWay ou OneWayToSource écoutent les modifications apportées à la propriété cible et les propagent à la source, appelées mise à jour de la source. Par exemple, vous pouvez modifier le texte d’une zone de texte pour modifier la valeur source sous-jacente.

Toutefois, est-ce que votre valeur source est mise à jour lorsque vous modifiez le texte ou après avoir fini de modifier le texte et que le contrôle perd le focus ? La propriété Binding.UpdateSourceTrigger détermine ce qui déclenche la mise à jour de la source. Les points des flèches droites de la figure suivante illustrent le rôle de la propriété Binding.UpdateSourceTrigger.

Diagramme montrant le rôle de la propriété UpdateSourceTrigger.

Si la valeur UpdateSourceTrigger est UpdateSourceTrigger.PropertyChanged, la valeur pointée par la flèche droite de TwoWay ou les liaisons OneWayToSource est mise à jour dès que la propriété cible change. Toutefois, si la valeur UpdateSourceTrigger est LostFocus, cette valeur est mise à jour uniquement avec la nouvelle valeur lorsque la propriété cible perd le focus.

Comme pour la propriété Mode, différentes propriétés de dépendance ont des valeurs de UpdateSourceTrigger différentes par défaut. La valeur par défaut de la plupart des propriétés de dépendance est PropertyChanged, ce qui entraîne la modification instantanée de la valeur de la propriété source lorsque la valeur de la propriété cible est modifiée. Les modifications instantanées sont correctes pour CheckBox et d’autres contrôles simples. Toutefois, pour les champs de texte, la mise à jour après chaque frappe de clavier peut réduire les performances et empêche l’utilisateur de revenir en arrière pour corriger les fautes de frappe avant de valider la nouvelle valeur. Par exemple, la propriété TextBox.Text prend par défaut la valeur UpdateSourceTrigger de LostFocus, ce qui entraîne la modification de la valeur source uniquement lorsque l’élément de contrôle perd le focus, et non lorsque la propriété TextBox.Text est modifiée. Consultez la page de propriétés UpdateSourceTrigger pour plus d’informations sur la recherche de la valeur par défaut d’une propriété de dépendance.

Le tableau suivant fournit un exemple de scénario pour chaque valeur UpdateSourceTrigger de à l’aide de TextBox comme exemple.

Valeur UpdateSourceTrigger Quand la valeur source est mise à jour Exemple de scénario pour TextBox
LostFocus (par défaut pour TextBox.Text) Lorsque le contrôle TextBox perd le focus. Une TextBox qui est associée à la logique de validation (voir la section Validation des données ci-dessous).
PropertyChanged Lorsque vous tapez dans le TextBox. Contrôles TextBox dans une fenêtre de salle de conversation.
Explicit Lorsque l’application appelle UpdateSource. Les contrôles TextBox dans un formulaire modifiable (met à jour les valeurs source uniquement lorsque l’utilisateur appuie sur le bouton Envoyer).

Pour un exemple, consultez Comment contrôler quand le texte TextBox met à jour la source (.NET Framework).

Exemple de liaison de données

Pour obtenir un exemple de liaison de données, examinez l’interface utilisateur de l’application suivante à partir de la démonstration de liaison de données , qui affiche une liste d’éléments d’enchères.

Capture d’écran d’exemple de liaison de données

L’application illustre les fonctionnalités suivantes de la liaison de données :

  • Le contenu de la ListBox est lié à une collection d’objets AuctionItem. Un objet AuctionItem a des propriétés telles que Description, StartPrice, StartDate, Category et SpecialFeatures.

  • Les données (objets AuctionItem) affichées dans la ListBox sont basées sur un modèle afin que la description et le prix actuel soient affichés pour chaque élément. Le modèle est créé à l’aide d’un DataTemplate. En outre, l’apparence de chaque élément varie selon la valeur de SpecialFeatures de l’AuctionItem affiché. Si la valeur SpecialFeatures de AuctionItem est Color, l’élément a une bordure bleue. Si la valeur est Highlight, l’élément a une bordure orange et une étoile. La section Modèles de données fournit des informations sur les modèles de données.

  • L’utilisateur peut regrouper, filtrer ou trier les données à l’aide de la CheckBoxes fournie. Dans l’image ci-dessus, Grouper par catégorie et Trier par catégorie et dateCheckBoxes sont sélectionnés. Vous avez peut-être remarqué que les données sont regroupées en fonction de la catégorie de produit et que les noms de catégorie sont dans l’ordre alphabétique. Cela est difficile à observer sur l’image, mais les éléments sont également triés par date de début dans chaque catégorie. Ce tri est effectué à l’aide d’une vue de collection. La section Lier à des collections traite des vues de collection.

  • Lorsque l’utilisateur sélectionne un élément, le ContentControl affiche les détails de l’élément sélectionné. Cette expérience est nommée Scénario maître/détail. La section Scénario maître/détail fournit des informations sur ce type de liaison.

  • Le type de la propriété StartDate est DateTime, qui retourne une date incluant l’heure à la milliseconde près. Dans cette application, un convertisseur personnalisé a été utilisé afin qu’une chaîne de date courte soit affichée. La section Conversion de données fournit des informations sur les convertisseurs.

Lorsque l’utilisateur sélectionne le bouton Ajouter un produit, le formulaire suivant s’affiche.

Page Ajouter la liste de produits

L’utilisateur peut modifier les champs dans le formulaire, consulter la liste de produits à l’aide des volets d’aperçu court et détaillé, puis sélectionnez Submit pour ajouter la nouvelle liste de produits. Tout regroupement, filtrage ou tri existant sera appliqué à la nouvelle entrée. Dans ce cas particulier, l’élément entré dans l’image ci-dessus s’affichera comme deuxième élément dans la catégorie Computer.

La logique de validation fournie dans la Date de départTextBox n’est pas présentée sur cette image. Si l’utilisateur entre une date non valide (mise en forme non valide ou date passée), l’utilisateur sera notifié avec une ToolTip et un point d’exclamation rouge en regard de la TextBox. La section Validation des données explique comment créer une logique de validation.

Avant d’aborder les différentes fonctionnalités de liaison de données décrites ci-dessus, nous couvrirons d’abord les concepts fondamentaux qui sont critiques pour la compréhension de la liaison de données WPF.

Créer une liaison

Pour récapituler quelques-uns des concepts étudiés dans les sections précédentes, vous établissez une liaison à l’aide de l’objet Binding, et chaque liaison possède généralement quatre composants : liaison cible, propriété cible, source de la liaison et un chemin d’accès à la valeur source à utiliser. Cette section explique comment configurer une liaison.

Les sources de liaison sont liées au DataContext actif pour l’élément. Les éléments héritent automatiquement de leur DataContext s’ils n’en ont pas défini explicitement un.

Prenons l’exemple suivant, dans lequel l’objet de source de liaison est une classe nommée MyData qui est définie dans l’espace de noms SDKSample. À des fins de démonstration, MyData a une propriété de chaîne nommée ColorName, dont la valeur est définie sur « Rouge ». Par conséquent, cet exemple génère un bouton avec un arrière-plan rouge.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <DockPanel.DataContext>
        <Binding Source="{StaticResource myDataSource}"/>
    </DockPanel.DataContext>
    <Button Background="{Binding Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

Pour plus d’informations sur la syntaxe de déclaration de liaison et pour obtenir des exemples montrant comment définir une liaison dans le code, consultez Vue d'ensemble des déclarations de liaison.

Si nous appliquons cet exemple à notre diagramme de base, l’illustration qui en résulte ressemble à ce qui suit. Cette figure décrit une liaison OneWay, car la propriété Arrière-plan prend en charge la liaison OneWay par défaut.

Diagramme montrant la propriété Background de liaison de données.

Vous pouvez vous demander pourquoi cette liaison fonctionne même si la propriété ColorName est de type chaîne tandis que la propriété Background est de type Brush. Cette liaison utilise une conversion de type par défaut, et nous l’évoquons dans la section Conversion de données.

Spécifier la source de liaison

Notez que dans l’exemple précédent, la source de liaison est spécifiée en définissant la propriété DockPanel.DataContext. Button hérite de la valeur DataContext de l’élément DockPanel, qui est un élément parent. Pour rappel, l’objet de source de liaison est un des quatre composants nécessaires d’une liaison. Par conséquent, sans objet de source de liaison spécifié, la liaison ne fait rien.

Il existe plusieurs façons de spécifier l’objet de source de liaison. Utiliser la propriété DataContext sur un élément parent est utile lorsque vous liez plusieurs propriétés à la même source. Cependant, il peut parfois être plus approprié de spécifier la source de liaison sur des déclarations de liaison individuelles. Pour l’exemple précédent, au lieu d’utiliser la propriété DataContext, vous pouvez spécifier la source de liaison en définissant la propriété Binding.Source directement sur la déclaration de liaison du bouton, comme dans l’exemple suivant.

<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:c="clr-namespace:SDKSample">
    <DockPanel.Resources>
        <c:MyData x:Key="myDataSource"/>
    </DockPanel.Resources>
    <Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}"
            Width="150" Height="30">
        I am bound to be RED!
    </Button>
</DockPanel>

En dehors de définir directement la propriété DataContext sur un élément, hériter de la valeur DataContext d’un ancêtre (par exemple, le bouton dans le premier exemple) et spécifier explicitement la source de liaison en définissant la propriété Binding.Source sur la liaison (par exemple, le bouton le dernier exemple), vous pouvez également utiliser la propriété Binding.ElementName ou la propriété Binding.RelativeSource pour spécifier la source de liaison. La propriété ElementName est utile lorsque vous faites une liaison avec d’autres éléments dans votre application, comme lorsque vous utilisez un curseur pour ajuster la largeur d’un bouton. La propriété RelativeSource est utile lorsque la liaison est spécifiée dans un ControlTemplate ou un Style. Pour plus d’informations, consultez Vue d’ensemble de la liaison de ressources.

Spécification du chemin vers la valeur

Si votre source de liaison est un objet, vous utilisez le Binding.Path pour spécifier la valeur à utiliser pour votre liaison. Si vous effectuez une liaison à des données XML, vous utilisez la propriété Binding.XPath pour spécifier la valeur. Dans certains cas, il peut être nécessaire d’utiliser la propriété Path même lorsque vos données sont XML. Par exemple, si vous souhaitez accéder à la propriété Name d’un XmlNode retourné (à la suite d’une requête XPath), vous devez utiliser la propriété Path en plus de la propriété XPath.

Pour plus d’informations, consultez les propriétés Path et XPath.

Bien que nous avons souligné que le Path de la valeur à utiliser est un des quatre composants nécessaires à une liaison, dans les scénarios pour lesquels vous souhaitez lier un objet entier, la valeur à utiliser est la même que l’objet de source de liaison. Dans ce cas, il est applicable de ne pas spécifier de Path. Considérez l'exemple suivant.

<ListBox ItemsSource="{Binding}"
         IsSynchronizedWithCurrentItem="true"/>

L’exemple ci-dessus utilise la syntaxe de liaison vide : {Binding}. Dans ce cas, la ListBox hérite du DataContext d’un élément DockPanel parent (non illustré dans cet exemple). Lorsque le chemin d’accès n’est pas spécifié, le comportement par défaut consiste à lier à l’objet entier. En d’autres termes, dans cet exemple, le chemin d’accès a été omis parce que nous lions la propriété ItemsSource à l’objet entier. (Voir la section Lier à des collections pour une discussion détaillée.)

En plus de la liaison à une collection, ce scénario est utile lorsque vous souhaitez lier un objet entier plutôt qu’une seule propriété d’un objet. Par exemple, si votre objet source est simplement de type String et que vous souhaitez lier la chaîne elle-même. Un autre scénario courant est lorsque vous souhaitez lier un élément à un objet avec plusieurs propriétés.

Vous devrez peut-être appliquer une logique personnalisée afin que les données soient significatives pour votre propriété cible liée aux données. La logique personnalisée peut être sous la forme d’un convertisseur personnalisé (si la conversion de type par défaut n’existe pas). Consultez Conversion de données pour plus d’informations sur les convertisseurs.

Binding et BindingExpression

Avant d’entrer dans d’autres fonctionnalités et utilisations de la liaison de données, il est utile d’introduire la classe BindingExpression. Comme vous l’avez vu dans les sections précédentes, la classe Binding est la classe de haut niveau pour la déclaration d’une liaison. Elle fournit de nombreuses propriétés qui vous permettent de spécifier les caractéristiques d’une liaison. Une classe connexe, BindingExpression, est l’objet sous-jacent qui gère la connexion entre la source et la cible. Une liaison contient toutes les informations qui peuvent être partagées entre plusieurs expressions de liaison. Une BindingExpression est une expression d’instance qui ne peut pas être partagée et contient toutes les informations d’instance du Binding.

Prenons l’exemple suivant, où myDataObject est une instance de la classe MyData, myBinding est l’objet Binding source et MyData est une classe définie qui contient une propriété de chaîne nommée ColorName. Cet exemple lie le contenu texte de myText, une instance de TextBlock, à ColorName.

// Make a new source
var myDataObject = new MyData();
var myBinding = new Binding("ColorName")
{
    Source = myDataObject
};

// Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding);
' Make a New source
Dim myDataObject As New MyData
Dim myBinding As New Binding("ColorName")
myBinding.Source = myDataObject

' Bind the data source to the TextBox control's Text dependency property
myText.SetBinding(TextBlock.TextProperty, myBinding)

Vous pouvez utiliser le même objet myBinding pour créer d’autres liaisons. Par exemple, vous pouvez utiliser l’objet myBinding pour lier le contenu textuel d’une case à cocher à ColorName. Dans ce scénario, il y aura deux instances de BindingExpression qui partageront l’objet myBinding.

Un objet BindingExpression est retourné en appelant GetBindingExpression sur un objet lié aux données. Les articles suivants illustrent certaines des utilisations de la classe BindingExpression :

Conversion de données

Dans la section Créer une liaison, le bouton est rouge, car sa propriété Background est liée à une propriété de chaîne avec la valeur « Rouge ». Cette valeur de chaîne fonctionne, car un convertisseur de type est présent sur le type Brush pour convertir la valeur de chaîne en Brush.

L’ajout de ces informations à la figure de la section Créer une liaison ressemble à ceci.

Diagramme montrant la propriété Default de liaison de données.

Toutefois, que se passe-t-il si, au lieu d’avoir une propriété de type chaîne, votre objet de source de liaison a une propriété couleur de type Color ? Dans ce cas, pour que la liaison fonctionne, vous devrez d’abord changer la valeur de la propriété couleur quelque chose que la propriété Background accepte. Vous devez créer un convertisseur personnalisé en implémentant l’interface IValueConverter, comme dans l’exemple suivant.

[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Color color = (Color)value;
        return new SolidColorBrush(color);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}
<ValueConversion(GetType(Color), GetType(SolidColorBrush))>
Public Class ColorBrushConverter
    Implements IValueConverter
    Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
        Dim color As Color = CType(value, Color)
        Return New SolidColorBrush(color)
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
        Return Nothing
    End Function
End Class

Consultez la rubrique IValueConverter (éventuellement en anglais) pour plus d'informations.

Maintenant, le convertisseur personnalisé est utilisé au lieu de la conversion par défaut, et notre diagramme ressemble à ceci.

Diagramme montrant la convertisseur personnalisé de liaison de données.

Pour rappel, des conversions par défaut peuvent être disponibles si des convertisseurs de type sont présents dans le type lié. Ce comportement dépendra des convertisseurs de type sont disponibles dans la cible. En cas de doute, créez votre propre convertisseur.

Voici quelques scénarios classiques où il est préférable d’implémenter un convertisseur de données :

  • Vos données devraient s’afficher différemment, selon la culture. Par exemple, vous souhaitez peut-être implémenter un convertisseur de devise ou un convertisseur de date/heure de calendrier en fonction des conventions utilisées dans une culture particulière.

  • Les données utilisées ne sont pas nécessairement destinées à modifier la valeur du texte d’une propriété, mais plutôt à modifier une autre valeur, comme la source d’une image ou la couleur ou le style du texte affiché. Les convertisseurs peuvent être utilisés ici en convertissant la liaison d’une propriété qui peut ne pas sembler appropriée, comme la liaison d’un champ de texte à la propriété Background d’une cellule de tableau.

  • Plusieurs contrôles ou propriétés de contrôles sont liés aux mêmes données. Dans ce cas, la liaison principale peut afficher uniquement le texte, tandis que les autres liaisons gèrent des problèmes d’affichage spécifiques, mais utilisent toujours la même liaison comme informations source.

  • Une propriété cible a une collection de liaisons, appelée MultiBinding. Pour MultiBinding, vous utilisez un IMultiValueConverter personnalisé pour produire une valeur finale à partir des valeurs des liaisons. Par exemple, la couleur peut être calculée à partir des valeurs rouge, vert et bleu, qui peuvent être des valeurs à partir d’objets de source de liaison identiques ou différents. Consultez MultiBinding des exemples et des informations.

Lier à des collections

Un objet de source de liaison peut être traité comme un objet unique dont les propriétés contiennent des données, ou comme une collection de données d’objets polymorphiques souvent regroupées ensemble (par exemple, le résultat d’une requête sur une base de données). Jusqu’à présent, nous n’avons abordé que la liaison à des objets uniques. Toutefois, la liaison à une collection de données est un scénario courant. Par exemple, un scénario courant consiste à utiliser un ItemsControl tel qu’un ListBox, ListViewou TreeView pour afficher une collection de données, comme dans l’application affichée dans la section Présentation de la liaison de données.

Heureusement, notre diagramme de base est toujours applicable. Si vous liez un ItemsControl à une collection, le diagramme ressemble à ceci.

Diagramme montrant l’objet ItemsControl de liaison de données.

Comme illustré dans ce diagramme, pour lier un ItemsControl à un objet de collection, la propriété ItemsControl.ItemsSource est la propriété à utiliser. Vous pouvez considérer ItemsSource comme le contenu du ItemsControl. La liaison est OneWay due au fait que la propriété ItemsSource prend en charge la liaison OneWay par défaut.

Guide d’implémentation des collections

Vous pouvez énumérer n’importe quelle collection qui implémente l’interface IEnumerable. Toutefois, pour configurer des liaisons dynamiques afin que les insertions ou les suppressions dans la collection mettent à jour l’IU automatiquement, la collection doit implémenter l’interface INotifyCollectionChanged. Cette interface expose un événement qui doit être déclenché chaque fois que la collection sous-jacente est modifiée.

WPF fournit la classe ObservableCollection<T>, qui est une implémentation intégrée d’une collection de données qui expose l’interface INotifyCollectionChanged. Pour prendre en charge le transfert des valeurs de données des les objets source vers les objets cible, chaque objet dans votre collection qui prend en charge des propriétés pouvant être liées doit aussi implémenter l’interface INotifyPropertyChanged. Pour plus d’informations, consultez Vue d’ensemble de la liaison de ressources.

Avant d’implémenter votre propre collection, envisagez d’utiliser ObservableCollection<T> ou l’une des classes de collection existantes, telles que List<T>, Collection<T>et BindingList<T>, entre autres. Si vous avez un scénario avancé et souhaitez implémenter votre propre collection, envisagez d’utiliser IList, qui fournit une collection non générique d’objets accessibles individuellement par index et assure donc les meilleures performances.

Affichages de collection

Une fois votre ItemsControl lié à une collection de données, vous souhaiterez peut-être trier, filtrer ou regrouper les données. Pour ce faire, vous utilisez des vues de collection, qui sont des classes qui implémentent l’interface ICollectionView.

Que sont les vues de collection ?

Une vue de collection est une couche sur une collection de sources de liaison qui vous permet de parcourir et d’afficher la collection source sur la base de requêtes de tri, de filtrage et de groupage, sans avoir à modifier la collection source sous-jacente elle-même. Une vue de collection maintient également un pointeur vers l’élément actuel dans la collection. Si la collection source implémente l’interface INotifyCollectionChanged, les modifications déclenchées par l’événement CollectionChanged sont propagées aux vues.

Étant donné que les vues ne modifient pas les collections source sous-jacentes, chaque collection source peut avoir plusieurs vues associées. Par exemple, vous pouvez avoir une collection d’objets Task. À l’aide de vues, vous pouvez afficher ces mêmes données de différentes façons. Par exemple, sur le côté gauche de votre page, vous souhaitez peut-être afficher les tâches triées par priorité et, sur le côté droit, les regrouper par domaine.

Comment créer une vue

Un moyen de créer et utiliser une vue consiste à instancier l’objet de vue directement et de l’utiliser comme source de liaison. Par exemple, considérez l’application de démonstration de liaison de données présentée dans la section Qu’est-ce que la liaison de données ?. L’application est implémentée de sorte que la ListBox se lie à une vue sur la collection de données plutôt qu’à la collection de données directement. L’exemple suivant est extrait de l’application de démonstration de liaison de données . La classe CollectionViewSource est le proxy XAML d’une classe qui hérite de CollectionView. Dans cet exemple particulier, la Source de la vue est liée à la collection AuctionItems (de type ObservableCollection<T>) de l’objet d’application actuel.

<Window.Resources>
    <CollectionViewSource 
      Source="{Binding Source={x:Static Application.Current}, Path=AuctionItems}"   
      x:Key="listingDataView" />
</Window.Resources>

La ressource listingDataView sert ensuite de source de liaison pour les éléments de l’application, comme la ListBox.

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />

Pour créer une autre vue pour la même collection, vous pouvez créer une autre instance de CollectionViewSource et lui donner un autre nom x:Key.

Le tableau suivant présente les types de données de vue qui sont créés comme vue de collection par défaut ou par CollectionViewSource en fonction du type de la collection source.

Type de collection source Type de vue de collection Notes
IEnumerable Type interne basé sur CollectionView Impossible de grouper les éléments.
IList ListCollectionView Le plus rapide.
IBindingList BindingListCollectionView

Utilisation d’une vue par défaut

La spécification d’une vue de collection comme source de liaison est un moyen de créer et utiliser une vue de collection. WPF crée également une vue de collection par défaut pour chaque collection utilisée comme source de liaison. Si vous liez directement à une collection, WPF crée une liaison sur sa vue par défaut. Cette vue par défaut est partagée par toutes les liaisons à la même collection, ainsi une modification apportée à une vue par défaut par un code ou contrôle lié (par exemple avec un tri ou une modification du pointeur d’élément actuel, dont nous parlons plus loin) se répercute dans toutes les autres liaisons à la même collection.

Pour obtenir la vue par défaut, vous utilisez la méthode GetDefaultView. Pour un exemple, consultez Obtenir la vue par défaut d'une collection de données (.NET Framework).

Vues de collection avec ADO.NET DataTables

Pour améliorer les performances, les vues de collection pour les objets DataTable ou DataView ADO.NET délèguent le tri et le filtrage à DataView, ce qui entraîne le partage et le filtrage du tri entre toutes les vues de collection de la source de données. Pour permettre à chaque vue de collection de trier et filtrer indépendamment, initialisez chaque vue de collection avec son propre objet DataView.

Tri

Comme mentionné précédemment, les vues peuvent appliquer un ordre de tri à une collection. Comme cela existe dans la collection sous-jacente, vos données peuvent avoir ou non un ordre inhérent pertinent. La vue sur la collection vous permet d’imposer un ordre, ou de modifier l’ordre par défaut, en fonction de critères de comparaison que vous fournissez. Comme il s’agit d’une vue de données côté client, un scénario courant est que l’utilisateur peut souhaiter trier les colonnes des données tabulaires par la valeur à laquelle la colonne correspond. À l’aide de vues, ce tri défini par l’utilisateur peut être appliqué, à nouveau sans apporter de modifications à la collection sous-jacente ou même avoir à redemander le contenu de la collection. Pour un exemple, consultez Trier une colonne GridView lors d'un clic sur un en-tête (.NET Framework).

L’exemple suivant illustre la logique de tri de la CheckBox « Trier par catégorie et date » de l’application dans la section Qu’est-ce que la liaison de données ?.

private void AddSortCheckBox_Checked(object sender, RoutedEventArgs e)
{
    // Sort the items first by Category and then by StartDate
    listingDataView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));
    listingDataView.SortDescriptions.Add(new SortDescription("StartDate", ListSortDirection.Ascending));
}
Private Sub AddSortCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    ' Sort the items first by Category And then by StartDate
    listingDataView.SortDescriptions.Add(New SortDescription("Category", ListSortDirection.Ascending))
    listingDataView.SortDescriptions.Add(New SortDescription("StartDate", ListSortDirection.Ascending))
End Sub

Filtrage

Les vues peuvent également appliquer un filtre à une collection, afin que l’affichage affiche uniquement un certain sous-ensemble de la collection complète. Vous pouvez filtrer sur une condition dans les données. Par exemple, comme effectué par l’application dans la section Qu’est-ce que la liaison de données ?, la CheckBox « Afficher uniquement les bonnes affaires » contient la logique permettant de filtrer les articles coûtant 25 $ ou plus. Le code suivant est exécuté pour définir ShowOnlyBargainsFilter comme gestionnaire d’événements Filter lorsque cette CheckBox est sélectionnée.

private void AddFilteringCheckBox_Checked(object sender, RoutedEventArgs e)
{
    if (((CheckBox)sender).IsChecked == true)
        listingDataView.Filter += ListingDataView_Filter;
    else
        listingDataView.Filter -= ListingDataView_Filter;
}
Private Sub AddFilteringCheckBox_Checked(sender As Object, e As RoutedEventArgs)
    Dim checkBox = DirectCast(sender, CheckBox)

    If checkBox.IsChecked = True Then
        AddHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    Else
        RemoveHandler listingDataView.Filter, AddressOf ListingDataView_Filter
    End If
End Sub

Le gestionnaire d’événements ShowOnlyBargainsFilter présente l’implémentation suivante.

private void ListingDataView_Filter(object sender, FilterEventArgs e)
{
    // Start with everything excluded
    e.Accepted = false;

    // Only inlcude items with a price less than 25
    if (e.Item is AuctionItem product && product.CurrentPrice < 25)
        e.Accepted = true;
}
Private Sub ListingDataView_Filter(sender As Object, e As FilterEventArgs)

    ' Start with everything excluded
    e.Accepted = False

    Dim product As AuctionItem = TryCast(e.Item, AuctionItem)

    If product IsNot Nothing Then

        ' Only include products with prices lower than 25
        If product.CurrentPrice < 25 Then e.Accepted = True

    End If

End Sub

Si vous utilisez l’une des classes CollectionView directement au lieu de CollectionViewSource, utilisez la propriété Filter pour spécifier un rappel. Pour obtenir un exemple, consultez Filtrer les données d'une vue (.NET Framework).

Regroupement

À l’exception de la classe interne qui affiche une collection IEnumerable, toutes les vues de collection prennent en charge le regroupement, qui permet à l’utilisateur de partitionner la collection dans la vue de collection en groupes logiques. Les groupes peuvent être explicites, si l’utilisateur fournit une liste de groupes, ou implicites, si les groupes sont générés dynamiquement en fonction des données.

L’exemple suivant illustre la logique de la CheckBox « Grouper par catégorie ».

// This groups the items in the view by the property "Category"
var groupDescription = new PropertyGroupDescription();
groupDescription.PropertyName = "Category";
listingDataView.GroupDescriptions.Add(groupDescription);
' This groups the items in the view by the property "Category"
Dim groupDescription = New PropertyGroupDescription()
groupDescription.PropertyName = "Category"
listingDataView.GroupDescriptions.Add(groupDescription)

Pour un autre exemple de groupement, consultez Grouper des éléments dans un ListView implémentant un GridView (.NET Framework).

Pointeurs d’élément actuel

Les vues prennent également en charge la notion d’élément actuel. Vous pouvez naviguer parmi les objets dans une vue de collection. Lorsque vous naviguez, vous déplacez un pointeur d’élément qui vous permet de récupérer l’objet qui existe à cet emplacement précis de la collection. Pour un exemple, consultez Naviguer dans les objets d'un CollectionView de données (.NET Framework).

Étant donné que WPF crée une liaison à une collection à l’aide d’une vue (une vue que vous spécifiez, ou la vue par défaut de la collection), toutes les liaisons à des collections ont un pointeur d’élément actuel. Lors de la liaison à une vue, la barre oblique (« / ») dans une valeur Path désigne l’élément actuel de la vue. Dans l’exemple suivant, le contexte de données est une vue de collection. La première ligne lie à la collection. La deuxième ligne lie à l’élément actuel dans la collection. La troisième ligne lie à la propriété Description de l’élément actuel dans la collection.

<Button Content="{Binding }" />
<Button Content="{Binding Path=/}" />
<Button Content="{Binding Path=/Description}" />

La syntaxe de barre oblique et de propriété peut également être empilée pour parcourir une hiérarchie de collections. L’exemple suivant lie l’élément actuel d’une collection nommée Offices, qui est une propriété de l’élément actuel de la collection source.

<Button Content="{Binding /Offices/}" />

Le pointeur d’élément actuel peut être affecté par tout tri ou filtrage appliqué à la collection. Le tri conserve le pointeur d’élément actuel sur le dernier élément sélectionné, mais la vue de collection est désormais restructurée autour du tri. (Peut-être que l’élément sélectionné était au début de la liste avant, mais maintenant l’élément sélectionné peut être quelque part au milieu.) Le filtrage conserve l’élément sélectionné si cette sélection reste en mode après le filtrage. Sinon, le pointeur d’élément actuel est défini sur le premier élément de la vue de collection filtrée.

Scénario de liaison maître/détail

La notion d’un élément actuel est utile non seulement pour le parcours d’éléments dans une collection, mais également pour le scénario de liaison maître/détail. Considérez à nouveau l’IU d’application dans la section Qu’est-ce que la liaison de données ?. Dans cette application, la sélection dans le ListBox détermine le contenu affiché dans le ContentControl. Pour le placer d’une autre manière, lorsqu’un élément ListBox est sélectionné, le ContentControl affiche les détails de l’élément sélectionné.

Vous pouvez implémenter le scénario maître/détail simplement en ayant deux contrôles ou plus liés à la même vue. L’exemple suivant de la démonstration de liaison de données montre le balisage de l'ListBox et le ContentControl que vous voyez sur l’interface utilisateur de l’application dans la section Présentation de la liaison de données.

<ListBox Name="Master" Grid.Row="2" Grid.ColumnSpan="3" Margin="8" 
         ItemsSource="{Binding Source={StaticResource listingDataView}}" />
<ContentControl Name="Detail" Grid.Row="3" Grid.ColumnSpan="3"
                Content="{Binding Source={StaticResource listingDataView}}"
                ContentTemplate="{StaticResource detailsProductListingTemplate}" 
                Margin="9,0,0,0"/>

Notez que les deux contrôles sont liés à la même source, la ressource statique listingDataView (voir la définition de cette ressource dans la section Guide de création d’une vue). Cette liaison fonctionne car lorsqu’un objet singleton (le ContentControl dans ce cas) est lié à un affichage de collection, il est automatiquement lié à la CurrentItem de la vue. Les objets CollectionViewSource synchronisent automatiquement la devise et la sélection. Si votre contrôle de liste n’est pas lié à un objet CollectionViewSource comme dans cet exemple, vous devez définir sa propriété IsSynchronizedWithCurrentItem sur true pour que cela fonctionne.

Pour obtenir des exemples, consultez Effectuer une liaison à une collection et afficher des informations basées sur la sélection (.NET Framework) et Utiliser le modèle maître/détail avec des données hiérarchiques (.NET Framework).

Vous avez peut-être remarqué que l’exemple ci-dessus utilisait un modèle. En fait, les données ne seraient pas affichées comme nous le souhaitons sans l’utilisation de modèles (celui utilisé explicitement par le ContentControl et celui utilisé implicitement par la ListBox). Nous passons maintenant aux modèles de données dans la section suivante.

Modèles de données

Sans l’utilisation de modèles de données, notre IU d’application dans la section Exemple de la liaison de données ? se présente comme suit :

Démo de liaison de données sans modèles de données

Comme indiqué dans l’exemple de la section précédente, le contrôle ListBox et le ContentControl sont liés à l’objet de collection entier (ou plus spécifiquement, la vue sur l’objet de collection) d’AuctionItems. Sans instructions spécifiques sur l’affichage de la collection de données, la ListBox affiche une représentation de chaîne de chaque objet dans la collection sous-jacente, et le ContentControl affiche une représentation de chaîne de l’objet auquel il est lié.

Pour résoudre ce problème, l’application définit DataTemplates. Comme illustré dans l’exemple de la section précédente, la ContentControl utilise explicitement le modèle de données detailsProductListingTemplate . Le contrôle ListBox utilise implicitement le modèle de données suivant lors de l’affichage des objets AuctionItem dans la collection.

<DataTemplate DataType="{x:Type src:AuctionItem}">
    <Border BorderThickness="1" BorderBrush="Gray"
            Padding="7" Name="border" Margin="3" Width="500">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20"/>
                <ColumnDefinition Width="86"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Polygon Grid.Row="0" Grid.Column="0" Grid.RowSpan="4"
                     Fill="Yellow" Stroke="Black" StrokeThickness="1"
                     StrokeLineJoin="Round" Width="20" Height="20"
                     Stretch="Fill"
                     Points="9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7"
                     Visibility="Hidden" Name="star"/>

            <TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,8,0"
                       Name="descriptionTitle"
                       Style="{StaticResource smallTitleStyle}">Description:</TextBlock>
            
            <TextBlock Name="DescriptionDTDataType" Grid.Row="0" Grid.Column="2"
                       Text="{Binding Path=Description}"
                       Style="{StaticResource textStyleTextBlock}"/>

            <TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,8,0"
                       Name="currentPriceTitle"
                       Style="{StaticResource smallTitleStyle}">Current Price:</TextBlock>
            
            <StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
                <TextBlock Text="$" Style="{StaticResource textStyleTextBlock}"/>
                <TextBlock Name="CurrentPriceDTDataType"
                           Text="{Binding Path=CurrentPrice}" 
                           Style="{StaticResource textStyleTextBlock}"/>
            </StackPanel>
        </Grid>
    </Border>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Color</src:SpecialFeatures>
            </DataTrigger.Value>
            <DataTrigger.Setters>
                <Setter Property="BorderBrush" Value="DodgerBlue" TargetName="border" />
                <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
                <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
                <Setter Property="BorderThickness" Value="3" TargetName="border" />
                <Setter Property="Padding" Value="5" TargetName="border" />
            </DataTrigger.Setters>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=SpecialFeatures}">
            <DataTrigger.Value>
                <src:SpecialFeatures>Highlight</src:SpecialFeatures>
            </DataTrigger.Value>
            <Setter Property="BorderBrush" Value="Orange" TargetName="border" />
            <Setter Property="Foreground" Value="Navy" TargetName="descriptionTitle" />
            <Setter Property="Foreground" Value="Navy" TargetName="currentPriceTitle" />
            <Setter Property="Visibility" Value="Visible" TargetName="star" />
            <Setter Property="BorderThickness" Value="3" TargetName="border" />
            <Setter Property="Padding" Value="5" TargetName="border" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

En utilisant ces deux DataTemplates, l’interface utilisateur obtenue est celle représentée dans la section Qu’est-ce que la liaison de données ?. Comme vous pouvez le voir sur cette capture d’écran, en plus de vous permettre de placer des données dans vos contrôles, les DataTemplate vous permettent de définir des éléments visuels agréables pour vos données. Par exemple, les DataTrigger sont utilisés dans l’exemple DataTemplate ci-dessus, afin que les AuctionItem avec la valeur SpecialFeatures définie sur HighLight s’affichent avec une bordure orange et une étoile.

Pour plus d’informations sur les modèles de données, consultez Vue d’ensemble des modèles de données (.NET Framework).

Validation des données

La plupart des applications qui acceptent l’entrée d’utilisateur doivent avoir une logique de validation pour vérifier que l’utilisateur a entré les informations attendues. Les contrôles de validation peuvent reposer sur un type, une plage, un format ou autres conditions requises spécifiques à l’application. Cette section traite du fonctionnement de la validation des données dans le WPF.

Associer des règles de validation à une liaison

Le modèle de liaison de données WPF vous permet d’associer ValidationRules à votre objet Binding. Par exemple, l’exemple suivant lie une propriété TextBox nommée StartPrice et ajoute un objet ExceptionValidationRule à la propriété Binding.ValidationRules.

<TextBox Name="StartPriceEntryForm" Grid.Row="2"
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <ExceptionValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Un objet ValidationRule vérifie si la valeur d’une propriété est valide. WPF a deux types d’objets intégrés ValidationRule :

Vous pouvez également créer votre propre règle de validation en dérivant de la classe ValidationRule et en implémentant la méthode Validate. L’exemple suivant montre la règle utilisée par la Add Product Listing « Date de début » TextBox dans la section Qu’est-ce que la liaison de données ?.

public class FutureDateRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        // Test if date is valid
        if (DateTime.TryParse(value.ToString(), out DateTime date))
        {
            // Date is not in the future, fail
            if (DateTime.Now > date)
                return new ValidationResult(false, "Please enter a date in the future.");
        }
        else
        {
            // Date is not a valid date, fail
            return new ValidationResult(false, "Value is not a valid date.");
        }

        // Date is valid and in the future, pass
        return ValidationResult.ValidResult;
    }
}
Public Class FutureDateRule
    Inherits ValidationRule

    Public Overrides Function Validate(value As Object, cultureInfo As CultureInfo) As ValidationResult

        Dim inputDate As Date

        ' Test if date is valid
        If Date.TryParse(value.ToString, inputDate) Then

            ' Date is not in the future, fail
            If Date.Now > inputDate Then
                Return New ValidationResult(False, "Please enter a date in the future.")
            End If

        Else
            ' // Date Is Not a valid date, fail
            Return New ValidationResult(False, "Value is not a valid date.")
        End If

        ' Date is valid and in the future, pass
        Return ValidationResult.ValidResult

    End Function

End Class

La StartDateEntryFormTextBox utilise cette FutureDateRule, comme illustré dans l’exemple suivant.

<TextBox Name="StartDateEntryForm" Grid.Row="3"
         Validation.ErrorTemplate="{StaticResource validationTemplate}" 
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged" 
                 Converter="{StaticResource dateConverter}" >
            <Binding.ValidationRules>
                <src:FutureDateRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Étant donné que la valeur UpdateSourceTrigger est PropertyChanged, le moteur de liaison met à jour la valeur source sur chaque séquence de touches, ce qui signifie qu’il vérifie également chaque règle de la collection ValidationRules sur chaque séquence de touches. Nous reviendrons plus tard là-dessus dans la section Processus de validation.

Fourniture de commentaires visuels

Si l’utilisateur entre une valeur non valide, vous souhaiterez peut-être fournir des commentaires sur l’erreur sur l’IU d’application. Une façon de fournir ces commentaires consiste à définir la propriété jointe Validation.ErrorTemplate sur une ControlTemplate personnalisée. Comme indiqué dans la sous-section précédente, l'StartDateEntryFormTextBox utilise un ErrorTemplate appelé validationTemplate. L’exemple suivant montre la définition de validationTemplate.

<ControlTemplate x:Key="validationTemplate">
    <DockPanel>
        <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
        <AdornedElementPlaceholder/>
    </DockPanel>
</ControlTemplate>

L’élément AdornedElementPlaceholder spécifie où le contrôle orné doit être placé.

En outre, vous pouvez également utiliser un ToolTip pour afficher le message d’erreur. LesStartDateEntryForm et StartPriceEntryFormTextBox utilisent le style textStyleTextBox, qui crée un ToolTip qui affiche le message d’erreur. L’exemple suivant montre la définition de textStyleTextBox. La propriété jointe Validation.HasError est true lorsqu’une ou plusieurs liaisons sur les propriétés de l’élément lié sont en erreur.

<Style x:Key="textStyleTextBox" TargetType="TextBox">
    <Setter Property="Foreground" Value="#333333" />
    <Setter Property="MaxLength" Value="40" />
    <Setter Property="Width" Value="392" />
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" 
                    Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={RelativeSource Self}}" />
        </Trigger>
    </Style.Triggers>
</Style>

Avec l’élément personnalisé ErrorTemplate et le ToolTip, startDateEntryFormTextBox ressemble à ce qui suit lorsqu’il existe une erreur de validation.

Erreur de validation de la liaison de données pour la date

Si votre Binding a associé des règles de validation, mais que vous ne spécifiez pas de ErrorTemplate sur le contrôle lié, une ErrorTemplate par défaut sera utilisée pour avertir les utilisateurs en cas d’erreur de validation. La ErrorTemplate par défaut est un modèle de contrôle qui définit une bordure rouge dans la couche ornement. Avec la valeur par défaut ErrorTemplate et le ToolTip, l’interface utilisateur de StartPriceEntryFormTextBox ressemble à ce qui suit lorsqu’il existe une erreur de validation.

Erreur de validation de la liaison de données pour le prix

Pour obtenir un exemple montrant comment fournir une logique pour valider tous les contrôles dans une boîte de dialogue, consultez la section Boîtes de dialogue personnalisées dans Vue d'ensemble des boîtes de dialogue.

Processus de validation

La validation se produit généralement lorsque la valeur d’une cible est transférée à la propriété de source de liaison. Ce transfert se produit sur les liaison TwoWay et OneWayToSource. Pour rappel, ce qui entraîne une mise à jour de la source dépend de la valeur de la propriété UpdateSourceTrigger, comme décrit dans la section Ce qui déclenche les mises à jour de la source.

La section suivante décrit le processus de validation. Si une erreur de validation ou un autre type d’erreur se produit à tout moment au cours de ce processus, le processus est interrompu :

  1. Le moteur de liaison vérifie s’il existe des objets ValidationRule personnalisés définis dont la ValidationStep est définie sur RawProposedValue pour cette Binding, auquel cas il appelle la méthode Validate sur chaque ValidationRule jusqu’à ce qu’une d’entre elles rencontre une erreur ou jusqu’à ce que tous les passent.

  2. Le moteur de liaison appelle alors le convertisseur, s’il en existe un.

  3. Si le convertisseur réussit, le moteur de liaison vérifie s’il existe des objets ValidationRule personnalisés définis dont la ValidationStep est définie sur ConvertedProposedValue pour cette Binding, auquel cas il appelle la méthode Validate sur chaque ValidationRule dont ValidationStep est fixé sur ConvertedProposedValue jusqu’à ce qu’une d’entre elles rencontre une erreur ou jusqu’à ce que tous les passent.

  4. Le moteur de liaison définit la propriété source.

  5. Le moteur de liaison vérifie s’il existe des objets ValidationRule personnalisés définis dont la ValidationStep est définie sur UpdatedValue pour cette Binding, auquel cas il appelle la méthode Validate sur chaque ValidationRule dont ValidationStep est fixé sur UpdatedValue jusqu’à ce qu’une d’entre elles rencontre une erreur ou jusqu’à ce que tous les passent. Si un DataErrorValidationRule est associé à une liaison et que son ValidationStep est défini sur la valeur par défaut, UpdatedValue, le DataErrorValidationRule est vérifié à ce stade. À ce stade, toute liaison sur laquelle ValidatesOnDataErrors fixé sur true est activée.

  6. Le moteur de liaison vérifie s’il existe des objets ValidationRule personnalisés définis dont la ValidationStep est définie sur CommittedValue pour cette Binding, auquel cas il appelle la méthode Validate sur chaque ValidationRule dont ValidationStep est fixé sur CommittedValue jusqu’à ce qu’une d’entre elles rencontre une erreur ou jusqu’à ce que tous les passent.

Si un ValidationRule ne passe pas à tout moment tout au long de ce processus, le moteur de liaison crée un objet ValidationError et l’ajoute à la collection Validation.Errors de l’élément lié. Avant que le moteur de liaison exécute les objets ValidationRule à une étape donnée, il supprime toute ValidationError qui a été ajoutée à la propriété jointe Validation.Errors de l’élément lié pendant cette étape. Par exemple, si un ValidationRule dont ValidationStep est fixé sur UpdatedValue a échoué, la prochaine fois que le processus de validation se produit, le moteur de liaison supprime ce ValidationError immédiatement avant qu’il appelle un ValidationRule dont ValidationStep est défini sur UpdatedValue.

Lorsque Validation.Errors n’est pas vide, la propriété jointe Validation.HasError de l’élément est définie sur true. En outre, si la propriété NotifyOnValidationError du Binding est définie sur true, le moteur de liaison déclenche l’événement Validation.Error attaché sur l’élément.

Notez également qu’un transfert de valeur valide dans une des deux directions (cible vers source ou source vers cible) efface la propriété jointe Validation.Errors.

Si la liaison a une ExceptionValidationRule associée ou si la propriété ValidatesOnExceptions est définie sur true et qu’une exception est levée lorsque le moteur de liaison définit la source, le moteur de liaison vérifie s’il existe un UpdateSourceExceptionFilter. Vous pouvez utiliser le rappel UpdateSourceExceptionFilter pour fournir un gestionnaire personnalisé pour la gestion des exceptions. Si un UpdateSourceExceptionFilter n’est pas spécifié sur le Binding, le moteur de liaison crée une exception ValidationErroret l’ajoute à la collection Validation.Errors de l’élément lié.

Mécanisme de débogage

Vous pouvez définir la propriété jointe PresentationTraceSources.TraceLevel sur un objet lié à une liaison pour recevoir des informations sur l’état d’une liaison spécifique.

Voir aussi