Validation et rappels de propriétés de dépendance

Cette rubrique décrit comment créer des propriétés de dépendance à l'aide d'implémentations personnalisées alternatives pour des fonctionnalités associées aux propriétés telles que la détermination de validation, les rappels appelés lors de chaque modification de la valeur effective de la propriété et la substitution possible d'influences extérieures sur la détermination de valeur. Cette rubrique traite également des scénarios pour lesquels le développement des comportements du système de propriétés par défaut à l'aide de ces techniques est approprié.

Cette rubrique comprend les sections suivantes.

  • Composants requis
  • Rappels de validation
  • Forçage de rappels de valeur et événements modifiés par des propriétés
  • Scénarios avancés de forçage de type et de rappels
  • Rubriques connexes

Composants requis

Cette rubrique suppose que vous comprenez les scénarios de base de l'implémentation d'une propriété de dépendance et que vous savez comment les métadonnées sont appliquées à une propriété de dépendance personnalisée. Consultez Propriétés de dépendance personnalisées et Métadonnées de propriété de dépendance pour le contexte.

Rappels de validation

Des rappels de validation peuvent être assignés à une propriété de dépendance lorsque vous l'enregistrez pour la première fois. Le rappel de validation ne fait pas partie des métadonnées de propriété ; c'est une entrée directe de la méthode Register. Par conséquent, un rappel de validation est créé une fois pour une propriété de dépendance, il ne peut pas être substitué par une nouvelle implémentation.

Public Shared ReadOnly CurrentReadingProperty As DependencyProperty =
    DependencyProperty.Register("CurrentReading",
        GetType(Double), GetType(Gauge),
        New FrameworkPropertyMetadata(Double.NaN,
            FrameworkPropertyMetadataOptions.AffectsMeasure,
            New PropertyChangedCallback(AddressOf OnCurrentReadingChanged),
            New CoerceValueCallback(AddressOf CoerceCurrentReading)),
        New ValidateValueCallback(AddressOf IsValidReading))

Public Property CurrentReading() As Double
    Get
        Return CDbl(GetValue(CurrentReadingProperty))
    End Get
    Set(ByVal value As Double)
        SetValue(CurrentReadingProperty, value)
    End Set
End Property
public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register(
    "CurrentReading",
    typeof(double),
    typeof(Gauge),
    new FrameworkPropertyMetadata(
        Double.NaN,
        FrameworkPropertyMetadataOptions.AffectsMeasure,
        new PropertyChangedCallback(OnCurrentReadingChanged),
        new CoerceValueCallback(CoerceCurrentReading)
    ),
    new ValidateValueCallback(IsValidReading)
);
public double CurrentReading
{
  get { return (double)GetValue(CurrentReadingProperty); }
  set { SetValue(CurrentReadingProperty, value); }
}

Les rappels sont implémentés de telle sorte qu'on leur fournit une valeur d'objet. Ils retournent la valeur true si la valeur fournie est valide pour la propriété ; sinon, ils retournent la valeur false. On suppose que le type de la propriété est correct en fonction du type enregistré avec le système de propriétés, de sorte que la vérification du type dans les rappels n'est pas effectuée habituellement. Les rappels sont utilisés par le système de propriétés dans diverses opérations différentes. Cela inclut par défaut l'initialisation du type initial par valeur par défaut, la modification par programme en appelant SetValue ou des tentatives de substitution des métadonnées par la nouvelle valeur par défaut fournie. Si le rappel de validation est appelé par l'une de ces opérations, et qu'il retourne la valeur false, une exception sera alors déclenchée. Les rédacteurs d'application doivent être préparés à gérer ces exceptions. Une utilisation commune des rappels de validation consiste à valider des valeurs d'énumération, ou de contraindre des valeurs d'entiers ou de types doubles lorsque les dimensions de jeux de propriétés doivent être nulles ou supérieures à zéro.

Les rappels de validation sont spécifiquement prévus pour être des validateurs de classe, plutôt que des validateurs d'instance. Les paramètres du rappel ne communiquent pas un DependencyObject spécifique sur lequel les propriétés à valider sont définies. Par conséquent, les rappels de validation ne sont pas utiles pour mettre en vigueur les "dépendances" possibles qui peuvent influencer une valeur de propriété, lorsque la valeur spécifique à l'instance d'une propriété est dépendante de facteurs tels que les valeurs spécifiques à l'instance d'autres propriétés, ou l'état d'exécution.

Les éléments suivants sont des exemples de code pour un scénario très simple de rappel de validation : valider qu'une propriété dont le type est Double primitif n'est pas PositiveInfinity ni NegativeInfinity.

Public Shared Function IsValidReading(ByVal value As Object) As Boolean
    Dim v As Double = CType(value, Double)
    Return ((Not v.Equals(Double.NegativeInfinity)) AndAlso
            (Not v.Equals(Double.PositiveInfinity)))
End Function
public static bool IsValidReading(object value)
{
    Double v = (Double)value;
    return (!v.Equals(Double.NegativeInfinity) && !v.Equals(Double.PositiveInfinity));
}

Forçage de rappels de valeur et événements modifiés par des propriétés

Le forçage des rappels de valeur passent l'instance DependencyObject spécifique pour les propriétés, comme le font des implémentations PropertyChangedCallback appelées par le système de propriétés chaque fois que la valeur d'une propriété de dépendance change. L'utilisation de ces deux rappels en combinaison vous permet de créer une série de propriétés sur les éléments lorsque les modifications dans une propriété imposeront un forçage de type ou une réévaluation d'une autre propriété.

Un scénario typique pour utiliser un chaînage de propriétés de dépendance est une propriété déterminée par l'interface où l'élément maintient une propriété à la fois pour la valeur minimale et la valeur maximale, et une troisième propriété pour la valeur réelle ou actuelle. Ici, si le maximum avait été ajusté de telle sorte que la valeur actuelle excédait le nouveau maximum, vous souhaiteriez forcer la valeur actuelle afin qu'elle ne soit pas supérieure au nouveau maximum, et une relation similaire pour le minimum par rapport à la valeur actuelle.

Ce qui suit est un exemple de code très bref pour l'une des trois propriétés de dépendance qui illustrent cette relation. L'exemple montre comment la propriété CurrentReading d'un jeu min/max/actuel de propriétés de *lecture associées est enregistrée. Il utilise la validation comme indiqué dans la section précédente.

Public Shared ReadOnly CurrentReadingProperty As DependencyProperty =
    DependencyProperty.Register("CurrentReading",
        GetType(Double), GetType(Gauge),
        New FrameworkPropertyMetadata(Double.NaN,
            FrameworkPropertyMetadataOptions.AffectsMeasure,
            New PropertyChangedCallback(AddressOf OnCurrentReadingChanged),
            New CoerceValueCallback(AddressOf CoerceCurrentReading)),
        New ValidateValueCallback(AddressOf IsValidReading))

Public Property CurrentReading() As Double
    Get
        Return CDbl(GetValue(CurrentReadingProperty))
    End Get
    Set(ByVal value As Double)
        SetValue(CurrentReadingProperty, value)
    End Set
End Property
public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register(
    "CurrentReading",
    typeof(double),
    typeof(Gauge),
    new FrameworkPropertyMetadata(
        Double.NaN,
        FrameworkPropertyMetadataOptions.AffectsMeasure,
        new PropertyChangedCallback(OnCurrentReadingChanged),
        new CoerceValueCallback(CoerceCurrentReading)
    ),
    new ValidateValueCallback(IsValidReading)
);
public double CurrentReading
{
  get { return (double)GetValue(CurrentReadingProperty); }
  set { SetValue(CurrentReadingProperty, value); }
}

Le rappel modifié par la propriété pour la valeur Actuelle est utilisé pour envoyer la modification à d'autres propriétés dépendantes, en appelant explicitement les rappels de valeurs forcés enregistrés pour ces autres propriétés :

Private Shared Sub OnCurrentReadingChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    d.CoerceValue(MinReadingProperty)
    d.CoerceValue(MaxReadingProperty)
End Sub
private static void OnCurrentReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  d.CoerceValue(MinReadingProperty);
  d.CoerceValue(MaxReadingProperty);
}

Le rappel de valeur forcée vérifie les valeurs des propriétés dont la propriété actuelle dépend potentiellement, et force la valeur actuelle si nécessaire :

Private Shared Function CoerceCurrentReading(ByVal d As DependencyObject, ByVal value As Object) As Object
    Dim g As Gauge = CType(d, Gauge)
    Dim current As Double = CDbl(value)
    If current < g.MinReading Then
        current = g.MinReading
    End If
    If current > g.MaxReading Then
        current = g.MaxReading
    End If
    Return current
End Function
private static object CoerceCurrentReading(DependencyObject d, object value)
{
  Gauge g = (Gauge)d;
  double current = (double)value;
  if (current < g.MinReading) current = g.MinReading;
  if (current > g.MaxReading) current = g.MaxReading;
  return current;
}
RemarqueRemarque

Les valeurs par défaut de propriétés ne sont pas forcées.Une valeur de propriété égale à la valeur par défaut peut se produire si une valeur de propriété possède encore sa valeur par défaut initiale ou à la suite de l'effacement d'autres valeurs avec ClearValue.

Les rappels modifiés par propriété et de valeur forcée font partie des métadonnées de propriété. Par conséquent, vous pouvez modifier les rappels pour une propriété de dépendance particulière comme il existe sur un type que vous dérivez de celui qui possède la propriété de dépendance, en substituant les métadonnées pour cette propriété sur votre type.

Scénarios avancés de forçage de type et de rappels

Contraintes et valeurs souhaitées

Les rappels CoerceValueCallback seront utilisés par le système de propriétés pour forcer une valeur conformément à la logique vous déclarez, mais une valeur forcée d'une propriété localement définie conservera encore en interne une "valeur désirée". Si les contraintes sont basées sur d'autres valeurs de propriété qui peuvent changer dynamiquement pendant la durée de vie de l'application, les contraintes de type de forçage sont aussi modifiées dynamiquement, et la propriété contrainte peut modifier sa valeur pour se rapprocher le plus possible de la valeur désirée en fonction des nouvelles contraintes. La valeur deviendra la valeur désirée si toutes les contraintes sont levées. Vous pouvez introduire potentiellement quelques scénarios de dépendance relativement compliqués si vous avez plusieurs propriétés dépendantes les unes des autres de façon circulaire. Par exemple, dans le scénario Min/Max/Actuel, vous pourriez choisir de faire définir Minimum et Maximum par l'utilisateur. Si c'est le cas, vous devrez peut-être faire en sorte que le Maximum soit toujours supérieur au Minimum et vice versa. Cependant, si ce forçage de type est actif, et si Maximum est forcé sur Minimum, cela laisse la valeur Actuelle dans un état non définissable, car elle est à la fois dépendante des deux valeurs et contrainte dans la plage qui sépare les valeurs, laquelle est nulle. Ensuite, si Maximum ou Minimum est ajusté, la valeur Actuelle paraîtra "suivre" l'une des valeurs car la valeur Actuelle souhaitée est encore stockée et tente d'atteindre la valeur désirée tandis que les contraintes sont relâchées.

Rien n'est techniquement incorrect avec les dépendances complexes, mais elles peuvent entraîner une légère dégradation des performances si elles ont besoin de nombreuses réévaluations et elles peuvent également troubler les utilisateurs si elles affectent directement l'interface utilisateur. Soyez prudent avec les rappels modifiés par propriété et de valeur forcée et assurez-vous que le forçage de type que vous tentez peut être traité aussi clairement que possible, et n'entraîne pas de forçage excessif.

Utilisation de CoerceValue pour annuler les modifications de valeur

Le système de propriétés traitera tout CoerceValueCallback qui retourne la valeur UnsetValue comme un cas spécial. Ce cas spécial signifie que la modification de propriété, qui a produit l'appel de CoerceValueCallback, doit être rejetée par le système de propriétés et que ce dernier doit signaler à la place la valeur précédente de la propriété. Ce mécanisme peut être utile pour vérifier que les modifications apportées à une propriété, qui avaient été initialisées de façon asynchrone, sont encore valides pour l'état d'objet actuel et pour supprimer les modifications dans le cas contraire. Un autre scénario possible est que vous pouvez supprimer une valeur de manière sélective en fonction du composant de détermination de valeur de propriété responsable de la valeur signalée. Pour ce faire, vous pouvez utiliser le DependencyProperty passé dans le rappel et l'identificateur de propriété comme entrée pour GetValueSource, puis traiter le ValueSource.

Voir aussi

Concepts

Vue d'ensemble des propriétés de dépendance

Métadonnées de propriété de dépendance

Propriétés de dépendance personnalisées