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’autres implémentations personnalisées pour des fonctionnalités liées aux propriétés telles que la détermination de la validation, les rappels effectués chaque fois que la valeur effective de la propriété change, et la non prise en compte des possibles influences extérieures sur la détermination de la valeur. Elle décrit également les scénarios où il est nécessaire de développer les comportements par défaut du système de propriétés à l’aide de ces techniques.

Prérequis

Cette rubrique part du principe 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. Pour plus d’informations sur le contexte, consultez Propriétés de dépendance personnalisées et Métadonnées de propriété de dépendance.

Rappels de validation

Les rappels de validation peuvent être attribués à une propriété de dépendance quand vous l’inscrivez initialement. Le rappel de validation ne fait pas partie des métadonnées de propriété ; il s’agit d’une entrée directe de la Register méthode. Par conséquent, une fois qu’un rappel de validation a été créé pour une propriété de dépendance, il ne peut pas être remplacé par une nouvelle implémentation.

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); }
}
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

Les rappels sont implémentés de manière à ce qu’une valeur d’objet leur soit fournie. Ils retournent true si la valeur fournie est valide pour la propriété ; sinon, ils retournent false. Il est supposé que la propriété est du type correct inscrit auprès du système de propriétés. Ainsi, 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 l’initialisation de type par valeur par défaut, la modification par programmation en appelant ou en essayant SetValuede remplacer les 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 false, une exception est levée. Les développeurs d’application doivent être prêts à gérer ces exceptions. L’une des utilisations courantes des rappels de validation consiste à valider des valeurs d’énumération, ou à contraindre des valeurs d’entiers ou des valeurs doubles quand la propriété définit des mesures qui doivent être supérieures ou égales à zéro.

Les rappels de validation sont spécifiquement prévus pour être des validateurs de classe, et non des validateurs d’instance. Les paramètres du rappel ne communiquent pas une valeur spécifique DependencyObject sur laquelle les propriétés à valider sont définies. Par conséquent, les rappels de validation ne sont pas utiles pour appliquer les dépendances « possibles » qui peuvent influencer une valeur de propriété, où la valeur propre à l’instance d’une propriété dépend de facteurs tels que des valeurs propres à l’instance d’autres propriétés ou l’état d’exécution.

Voici un exemple de code pour un scénario de rappel de validation très simple : validation d’une propriété typée comme la Double primitive n’est pas PositiveInfinity ou NegativeInfinity.

public static bool IsValidReading(object value)
{
    Double v = (Double)value;
    return (!v.Equals(Double.NegativeInfinity) && !v.Equals(Double.PositiveInfinity));
}
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

Rappels de forçage de valeurs et événements de modification de propriété

Les rappels de valeurs de coerce transmettent l’instance spécifique DependencyObject pour les propriétés, car les PropertyChangedCallback implémentations qui sont appelées par le système de propriétés chaque fois que la valeur d’une propriété de dépendance change. À l’aide de ces deux rappels en combinaison, vous pouvez créer une série de propriétés sur des éléments où les modifications d’une propriété forcent la réévaluation d’une autre propriété.

Un scénario typique d’utilisation d’un chaînage de propriétés de dépendance est quand vous définissez une propriété pilotée par l’interface utilisateur où l’élément contient une propriété pour la valeur minimale, une autre propriété pour la valeur maximale et une troisième propriété pour la valeur réelle ou actuelle. Dans ce cas, si la valeur maximale a été modifiée de telle sorte que la valeur actuelle dépasse la nouvelle valeur maximale, vous souhaiterez forcer la valeur actuelle pour qu’elle n’excède pas la nouvelle valeur maximale (et de même en ce qui concerne la valeur minimale).

Le court exemple de code ci-dessous concerne l’une des trois propriétés de dépendance qui illustrent cette relation. Il montre comment la propriété CurrentReading d’un ensemble Min/Max/Current de propriétés Reading est inscrit. Il utilise la validation comme illustré dans la section précédente.

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); }
}
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

Le rappel de modification de propriété pour Current est utilisé pour transférer la modification à d’autres propriétés dépendantes, en appelant explicitement les rappels de forçage de valeur inscrits pour ces autres propriétés :

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

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

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;
}
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

Remarque

Les valeurs par défaut des 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é a toujours sa valeur par défaut initiale, ou en supprimant d’autres valeurs avec ClearValue.

Les rappels de forçage de valeur et de modification de propriété font partie des métadonnées de propriété. Ainsi, vous pouvez changer les rappels pour une propriété de dépendance particulière telle qu’elle existe sur un type dérivant du type qui possède la propriété de dépendance, en substituant les métadonnées de cette propriété sur votre type.

Scénarios de forçage et de rappel avancés

Contraintes et valeurs souhaitées

Les CoerceValueCallback rappels sont utilisés par le système de propriétés pour forcer une valeur conformément à la logique que vous déclarez, mais une valeur coerced d’une propriété définie localement conserve toujours une « valeur souhaitée » en interne. Si les contraintes sont basées sur d’autres valeurs de propriété qui peuvent changer de manière dynamique pendant la durée de vie de l’application, les contraintes de forçage sont aussi changées de manière dynamique, et la propriété contrainte peut modifier sa valeur pour qu’elle soit le plus proche possible de la valeur souhaitée étant donné les nouvelles contraintes. La valeur devient la valeur souhaitée si toutes les contraintes sont levées. Vous pouvez introduire potentiellement certains scénarios de dépendance relativement compliqués si vous avez plusieurs propriétés qui dépendent les unes des autres de manière circulaire. Par exemple, dans le scénario Min/Max/Current, vous pouvez choisir de faire en sorte que Minimum et Maximum soient définissables par l’utilisateur. Dans ce cas, vous devrez peut-être forcer la propriété Maximum pour qu’elle soit toujours supérieure à Minimum, et inversement. Mais si cette contrainte est active, et que la valeur de Maximum est forcée sur celle de Minimum, cela laisse Current dans un état non définissable, car cette valeur dépend des deux autres et est limitée à la plage comprise entre ces deux valeurs, qui est égale à zéro. Ensuite, si Maximum ou Minimum est ajustée, Current paraîtra « suivre » l’une des valeurs, car la valeur souhaitée de Current sera encore stockée et tentera d’atteindre la valeur souhaitée à mesure que les contraintes sont assouplies.

Il n’y a rien de techniquement incorrect avec les dépendances complexes, mais elles peuvent réduire légèrement les performances si elles nécessitent un grand nombre de réévaluations, et peuvent également être déconcertantes pour les utilisateurs si elles affectent directement l’interface utilisateur. Soyez prudent avec les rappels de forçage de valeur et les rappels de modification de propriété, et vérifiez que la contrainte tentée peut être traitée aussi clairement que possible et ne pas « surcontraindre ».

Utilisation de CoerceValue pour annuler des modifications de valeur

Le système de propriétés traite les valeurs CoerceValueCallback qui retournent la valeur UnsetValue comme un cas spécial. Ce cas spécial signifie que le changement de propriété qui a entraîné l’appel CoerceValueCallback doit être rejeté par le système de propriétés, et que le système de propriétés doit plutôt signaler la valeur précédente que la propriété avait. Ce mécanisme peut être utile pour vérifier que les modifications d’une propriété qui ont été lancées de façon asynchrone sont toujours valides pour l’état actuel de l’objet, et pour supprimer les modifications si ce n’est pas le cas. Un autre scénario possible consiste à supprimer sélectivement une valeur en fonction du composant de détermination de la valeur de propriété responsable du signalement de la valeur. Pour ce faire, vous pouvez utiliser le DependencyProperty passage dans le rappel et l’identificateur de propriété comme entrée, GetValueSourcepuis traiter le ValueSource.

Voir aussi