Rückrufe und Validierung von Abhängigkeitseigenschaften (WPF .NET)

In diesem Artikel wird beschrieben, wie Sie eine Abhängigkeitseigenschaft definieren und Rückrufe für Abhängigkeitseigenschaften implementieren. Die Rückrufe unterstützen die Wertvalidierung, die Wertkoersion und andere Logik, die erforderlich ist, wenn sich ein Eigenschaftswert ändert.

Wichtig

Der Desktopleitfaden zu .NET 7 und .NET 6 ist in Bearbeitung.

Voraussetzungen

Im Artikel wird davon ausgegangen, dass sie grundlegende Kenntnisse über Abhängigkeitseigenschaften haben und eine Übersicht über Abhängigkeitseigenschaften gelesen haben. Um den Beispielen in diesem Artikel zu folgen, ist es hilfreich, wenn Sie mit Extensible Application Markup Language (XAML) vertraut sind und wissen, wie WPF-Anwendungen geschrieben werden.

Überprüfungswertrückrufe

Validate-Value-Rückrufe bieten Eine Möglichkeit, um zu überprüfen, ob ein neuer Abhängigkeitseigenschaftswert gültig ist, bevor er vom Eigenschaftensystem angewendet wird. Dieser Rückruf löst eine Ausnahme aus, wenn der Wert die Validierungskriterien nicht erfüllt.

Validate-Value-Rückrufe können einer Abhängigkeitseigenschaft nur einmal während der Eigenschaftenregistrierung zugewiesen werden. Beim Registrieren einer Abhängigkeitseigenschaft haben Sie die Möglichkeit, einen ValidateValueCallback-Verweis auf die Register(String, Type, Type, PropertyMetadata, ValidateValueCallback)-Methode zu übergeben. Validate-Value-Rückrufe sind nicht Teil der Eigenschaftenmetadaten und können nicht überschrieben werden.

Der effektive Wert einer Abhängigkeitseigenschaft ist der angewendete Wert. Der effektive Wert wird durch Eigenschaftswertfolge bestimmt, wenn mehrere eigenschaftsbasierte Eingaben vorhanden sind. Wenn ein Validate-Value-Rückruf für eine Abhängigkeitseigenschaft registriert ist, ruft das Eigenschaftensystem seinen Validate-Value-Rückruf bei einer Wertänderung auf und übergibt den neuen Wert als Objekt. Innerhalb des Rückrufs können Sie das Wertobjekt zurück in den Typ umstellen, der beim Eigenschaftensystem registriert ist, und dann Ihre Validierungslogik dafür ausführen. Der Rückruf gibt true zurück, wenn der Wert für die Eigenschaft gültig ist, andernfalls false.

Wenn ein Überprüfungswertrückruf false zurückgibt, wird eine Ausnahme ausgelöst, und der neue Wert wird nicht angewendet. Anwendungsentwickler müssen diese Ausnahmen behandeln können. Eine häufige Verwendung von Validate-Value-Rückrufen ist das Überprüfen von Enumerationswerten oder das Einschränken numerischer Werte, wenn sie Messungen darstellen, die Grenzwerte aufweisen. Validate-Value-Rückrufe werden vom Eigenschaftensystem in verschiedenen Szenarien aufgerufen, einschließlich:

  • Objektinitialisierung, die zur Erstellungszeit einen Standardwert anwendet.
  • Programmgesteuerte Aufrufe an SetValue.
  • Metadatenüberschreibungen, die einen neuen Standardwert angeben.

Überprüfungswertrückrufe verfügen nicht über einen Parameter, der die DependencyObject-Instanz angibt, auf der der neue Wert festgelegt wird. Alle Instanzen einer DependencyObject-Freigabe haben den gleichen Überprüfungswertrückruf, sodass sie nicht verwendet werden kann, um instanzspezifische Szenarien zu überprüfen. Weitere Informationen finden Sie unter ValidateValueCallback.

Das folgende Beispiel zeigt, wie Sie verhindern können, dass eine Eigenschaft, die als Double eingegeben wird, auf PositiveInfinity oder NegativeInfinity gesetzt wird.

public class Gauge1 : Control
{
    public Gauge1() : base() { }

    // Register a dependency property with the specified property name,
    // property type, owner type, property metadata, and callbacks.
    public static readonly DependencyProperty CurrentReadingProperty =
        DependencyProperty.Register(
            name: "CurrentReading",
            propertyType: typeof(double),
            ownerType: typeof(Gauge1),
            typeMetadata: new FrameworkPropertyMetadata(
                defaultValue: double.NaN,
                flags: FrameworkPropertyMetadataOptions.AffectsMeasure),
            validateValueCallback: new ValidateValueCallback(IsValidReading));

    // CLR wrapper with get/set accessors.
    public double CurrentReading
    {
        get => (double)GetValue(CurrentReadingProperty);
        set => SetValue(CurrentReadingProperty, value);
    }

    // Validate-value callback.
    public static bool IsValidReading(object value)
    {
        double val = (double)value;
        return !val.Equals(double.NegativeInfinity) && 
            !val.Equals(double.PositiveInfinity);
    }
}
Public Class Gauge1
    Inherits Control

    Public Sub New()
        MyBase.New()
    End Sub

    Public Shared ReadOnly CurrentReadingProperty As DependencyProperty =
        DependencyProperty.Register(
            name:="CurrentReading",
            propertyType:=GetType(Double),
            ownerType:=GetType(Gauge1),
            typeMetadata:=New FrameworkPropertyMetadata(
                defaultValue:=Double.NaN,
                flags:=FrameworkPropertyMetadataOptions.AffectsMeasure),
            validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))

    Public Property CurrentReading As Double
        Get
            Return GetValue(CurrentReadingProperty)
        End Get
        Set(value As Double)
            SetValue(CurrentReadingProperty, value)
        End Set
    End Property

    Public Shared Function IsValidReading(value As Object) As Boolean
        Dim val As Double = value
        Return Not val.Equals(Double.NegativeInfinity) AndAlso
            Not val.Equals(Double.PositiveInfinity)
    End Function

End Class
public static void TestValidationBehavior()
{
    Gauge1 gauge = new();

    Debug.WriteLine($"Test value validation scenario:");

    // Set allowed value.
    gauge.CurrentReading = 5;
    Debug.WriteLine($"Current reading: {gauge.CurrentReading}");

    try
    {
        // Set disallowed value.
        gauge.CurrentReading = double.PositiveInfinity;
    }
    catch (ArgumentException e)
    {
        Debug.WriteLine($"Exception thrown by ValidateValueCallback: {e.Message}");
    }

    Debug.WriteLine($"Current reading: {gauge.CurrentReading}");

    // Current reading: 5
    // Exception thrown by ValidateValueCallback: '∞' is not a valid value for property 'CurrentReading'.
    // Current reading: 5
}
Public Shared Sub TestValidationBehavior()
    Dim gauge As New Gauge1()

    Debug.WriteLine($"Test value validation scenario:")

    ' Set allowed value.
    gauge.CurrentReading = 5
    Debug.WriteLine($"Current reading: {gauge.CurrentReading}")

    Try
        ' Set disallowed value.
        gauge.CurrentReading = Double.PositiveInfinity
    Catch e As ArgumentException
        Debug.WriteLine($"Exception thrown by ValidateValueCallback: {e.Message}")
    End Try

    Debug.WriteLine($"Current reading: {gauge.CurrentReading}")

    ' Current reading: 5
    ' Exception thrown by ValidateValueCallback: '∞' is not a valid value for property 'CurrentReading'.
    ' Current reading 5
End Sub

Eigenschaft geänderte Rückrufe

Rückrufe mit geänderten Eigenschaften benachrichtigen Sie, wenn sich der effektive Wert einer Abhängigkeitseigenschaft geändert hat.

Rückrufe mit geänderten Eigenschaften sind Teil der Metadaten der Abhängigkeitseigenschaft. Wenn Sie von einer Klasse ableiten, die eine Abhängigkeitseigenschaft definiert, oder Ihre Klasse als Besitzer einer Abhängigkeitseigenschaft hinzufügen, können Sie die Metadaten überschreiben. Beim Außerkraftsetzen von Metadaten haben Sie die Möglichkeit, einen neuen PropertyChangedCallback-Verweis bereitzustellen. Verwenden Sie einen Rückruf mit geänderten Eigenschaften, um Logik auszuführen, die erforderlich ist, wenn sich ein Eigenschaftswert ändert.

Im Gegensatz zu Validate-Value-Rückrufen verfügen Rückrufe mit geänderten Eigenschaften über einen Parameter, der die DependencyObject-Instanz angibt, für die der neue Wert festgelegt wird. Im nächsten Beispiel wird gezeigt, wie ein Rückruf mit geänderter Eigenschaft den DependencyObject-Instanzverweis verwenden kann, um Rückrufe von Coerce-Werten auszulösen.

coerce-Wert-Rückrufe

Coerce-Value-Rückrufe bieten Ihnen die Möglichkeit, benachrichtigt zu werden, wenn sich der effektive Wert einer Abhängigkeitseigenschaft ändern wird, sodass Sie den neuen Wert anpassen können, bevor er angewendet wird. Zusätzlich zum Auslösen durch das Eigenschaftensystem können Sie Auch-Wert-Rückrufe aus Ihrem Code aufrufen.

Rückrufe von Coerce-Werten sind Teil der Metadaten der Abhängigkeitseigenschaft. Wenn Sie von einer Klasse ableiten, die eine Abhängigkeitseigenschaft definiert, oder Ihre Klasse als Besitzer einer Abhängigkeitseigenschaft hinzufügen, können Sie die Metadaten überschreiben. Beim Überschreiben der Metadaten haben Sie die Möglichkeit, einen Verweis auf eine neue CoerceValueCallback bereitzustellen. Verwenden Sie einen Coerce-Value-Rückruf, um neue Werte auszuwerten und bei Bedarf zu koerzen. Der Rückruf gibt den gekoerkten Wert zurück, wenn die Koersion aufgetreten ist, andernfalls wird der unveränderte neue Wert zurückgegeben.

Ähnlich wie bei Rückrufen mit geänderten Eigenschaften verfügen Rückrufe von Coerce-Werten über einen Parameter, der die DependencyObject-Instanz angibt, für die der neue Wert festgelegt wird. Das nächste Beispiel zeigt, wie ein Coerce-Wertrückruf einen DependencyObject-Instanzverweis auf Coerce-Eigenschaftswerte verwenden kann.

Hinweis

Standardeigenschaftswerte können nicht geziert werden. Für eine Abhängigkeitseigenschaft ist der Standardwert bei der Objektinitialisierung oder beim Löschen anderer Werte mit ClearValue festgelegt.

Rückrufe mit Coerce-Wert und geänderten Eigenschaften in Kombination

Sie können Abhängigkeiten zwischen Eigenschaften für ein Element erstellen, indem Sie Rückrufe mit Coerce-Wert und Rückrufe mit geänderten Eigenschaften in Kombination verwenden. Änderungen in einer Eigenschaft erzwingen z. B. die Koersion oder erneute Auswertung in einer anderen Abhängigkeitseigenschaft. Das nächste Beispiel zeigt ein allgemeines Szenario: drei Abhängigkeitseigenschaften, die jeweils den aktuellen Wert, den Mindestwert und den Maximalwert eines Benutzeroberflächenelements speichern. Wenn sich der Höchstwert so ändert, dass er kleiner als der aktuelle Wert ist, wird der aktuelle Wert auf den neuen Höchstwert festgelegt. Und wenn sich der Mindestwert so ändert, dass er größer als der aktuelle Wert ist, wird der aktuelle Wert auf den neuen Mindestwert festgelegt. Im Beispiel ruft die PropertyChangedCallback für den aktuellen Wert explizit die CoerceValueCallback für die minimalen und maximalen Werte auf.

public class Gauge2 : Control
{
    public Gauge2() : base() { }

    // Register a dependency property with the specified property name,
    // property type, owner type, property metadata, and callbacks.
    public static readonly DependencyProperty CurrentReadingProperty =
        DependencyProperty.Register(
            name: "CurrentReading",
            propertyType: typeof(double),
            ownerType: typeof(Gauge2),
            typeMetadata: new FrameworkPropertyMetadata(
                defaultValue: double.NaN,
                flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
                propertyChangedCallback: new PropertyChangedCallback(OnCurrentReadingChanged),
                coerceValueCallback: new CoerceValueCallback(CoerceCurrentReading)
            ),
            validateValueCallback: new ValidateValueCallback(IsValidReading)
        );

    // CLR wrapper with get/set accessors.
    public double CurrentReading
    {
        get => (double)GetValue(CurrentReadingProperty);
        set => SetValue(CurrentReadingProperty, value);
    }

    // Validate-value callback.
    public static bool IsValidReading(object value)
    {
        double val = (double)value;
        return !val.Equals(double.NegativeInfinity) && !val.Equals(double.PositiveInfinity);
    }

    // Property-changed callback.
    private static void OnCurrentReadingChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        depObj.CoerceValue(MinReadingProperty);
        depObj.CoerceValue(MaxReadingProperty);
    }

    // Coerce-value callback.
    private static object CoerceCurrentReading(DependencyObject depObj, object value)
    {
        Gauge2 gauge = (Gauge2)depObj;
        double currentVal = (double)value;
        currentVal = currentVal < gauge.MinReading ? gauge.MinReading : currentVal;
        currentVal = currentVal > gauge.MaxReading ? gauge.MaxReading : currentVal;
        return currentVal;
    }

    // Register a dependency property with the specified property name,
    // property type, owner type, property metadata, and callbacks.
    public static readonly DependencyProperty MaxReadingProperty = DependencyProperty.Register(
        name: "MaxReading",
        propertyType: typeof(double),
        ownerType: typeof(Gauge2),
        typeMetadata: new FrameworkPropertyMetadata(
            defaultValue: double.NaN,
            flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
            propertyChangedCallback: new PropertyChangedCallback(OnMaxReadingChanged),
            coerceValueCallback: new CoerceValueCallback(CoerceMaxReading)
        ),
        validateValueCallback: new ValidateValueCallback(IsValidReading)
    );

    // CLR wrapper with get/set accessors.
    public double MaxReading
    {
        get => (double)GetValue(MaxReadingProperty);
        set => SetValue(MaxReadingProperty, value);
    }

    // Property-changed callback.
    private static void OnMaxReadingChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        depObj.CoerceValue(MinReadingProperty);
        depObj.CoerceValue(CurrentReadingProperty);
    }

    // Coerce-value callback.
    private static object CoerceMaxReading(DependencyObject depObj, object value)
    {
        Gauge2 gauge = (Gauge2)depObj;
        double maxVal = (double)value;
        return maxVal < gauge.MinReading ? gauge.MinReading : maxVal;
    }

    // Register a dependency property with the specified property name,
    // property type, owner type, property metadata, and callbacks.
    public static readonly DependencyProperty MinReadingProperty = DependencyProperty.Register(
    name: "MinReading",
    propertyType: typeof(double),
    ownerType: typeof(Gauge2),
    typeMetadata: new FrameworkPropertyMetadata(
        defaultValue: double.NaN,
        flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
        propertyChangedCallback: new PropertyChangedCallback(OnMinReadingChanged),
        coerceValueCallback: new CoerceValueCallback(CoerceMinReading)
    ),
    validateValueCallback: new ValidateValueCallback(IsValidReading));

    // CLR wrapper with get/set accessors.
    public double MinReading
    {
        get => (double)GetValue(MinReadingProperty);
        set => SetValue(MinReadingProperty, value);
    }

    // Property-changed callback.
    private static void OnMinReadingChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        depObj.CoerceValue(MaxReadingProperty);
        depObj.CoerceValue(CurrentReadingProperty);
    }

    // Coerce-value callback.
    private static object CoerceMinReading(DependencyObject depObj, object value)
    {
        Gauge2 gauge = (Gauge2)depObj;
        double minVal = (double)value;
        return minVal > gauge.MaxReading ? gauge.MaxReading : minVal;
    }
}
Public Class Gauge2
    Inherits Control

    Public Sub New()
        MyBase.New()
    End Sub

    ' Register a dependency property with the specified property name,
    ' property type, owner type, property metadata, And callbacks.
    Public Shared ReadOnly CurrentReadingProperty As DependencyProperty =
        DependencyProperty.Register(
            name:="CurrentReading",
            propertyType:=GetType(Double),
            ownerType:=GetType(Gauge2),
            typeMetadata:=New FrameworkPropertyMetadata(
                defaultValue:=Double.NaN,
                flags:=FrameworkPropertyMetadataOptions.AffectsMeasure,
                propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnCurrentReadingChanged),
                coerceValueCallback:=New CoerceValueCallback(AddressOf CoerceCurrentReading)),
            validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))

    ' CLR wrapper with get/set accessors.
    Public Property CurrentReading As Double
        Get
            Return GetValue(CurrentReadingProperty)
        End Get
        Set(value As Double)
            SetValue(CurrentReadingProperty, value)
        End Set
    End Property

    ' Validate-value callback.
    Public Shared Function IsValidReading(value As Object) As Boolean
        Dim val As Double = value
        Return Not val.Equals(Double.NegativeInfinity) AndAlso Not val.Equals(Double.PositiveInfinity)
    End Function

    ' Property-changed callback.
    Private Shared Sub OnCurrentReadingChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
        depObj.CoerceValue(MinReadingProperty)
        depObj.CoerceValue(MaxReadingProperty)
    End Sub

    ' Coerce-value callback.
    Private Shared Function CoerceCurrentReading(depObj As DependencyObject, value As Object) As Object
        Dim gauge As Gauge2 = CType(depObj, Gauge2)
        Dim currentVal As Double = value
        currentVal = If(currentVal < gauge.MinReading, gauge.MinReading, currentVal)
        currentVal = If(currentVal > gauge.MaxReading, gauge.MaxReading, currentVal)
        Return currentVal
    End Function

    Public Shared ReadOnly MaxReadingProperty As DependencyProperty =
        DependencyProperty.Register(
        name:="MaxReading",
        propertyType:=GetType(Double),
        ownerType:=GetType(Gauge2),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=Double.NaN,
            flags:=FrameworkPropertyMetadataOptions.AffectsMeasure,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnMaxReadingChanged),
            coerceValueCallback:=New CoerceValueCallback(AddressOf CoerceMaxReading)),
        validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))

    ' CLR wrapper with get/set accessors.
    Public Property MaxReading As Double
        Get
            Return GetValue(MaxReadingProperty)
        End Get
        Set(value As Double)
            SetValue(MaxReadingProperty, value)
        End Set
    End Property

    ' Property-changed callback.
    Private Shared Sub OnMaxReadingChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
        depObj.CoerceValue(MinReadingProperty)
        depObj.CoerceValue(CurrentReadingProperty)
    End Sub

    ' Coerce-value callback.
    Private Shared Function CoerceMaxReading(depObj As DependencyObject, value As Object) As Object
        Dim gauge As Gauge2 = CType(depObj, Gauge2)
        Dim maxVal As Double = value
        Return If(maxVal < gauge.MinReading, gauge.MinReading, maxVal)
    End Function

    ' Register a dependency property with the specified property name,
    ' property type, owner type, property metadata, And callbacks.
    Public Shared ReadOnly MinReadingProperty As DependencyProperty =
        DependencyProperty.Register(
        name:="MinReading",
        propertyType:=GetType(Double),
        ownerType:=GetType(Gauge2),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=Double.NaN,
            flags:=FrameworkPropertyMetadataOptions.AffectsMeasure,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnMinReadingChanged),
            coerceValueCallback:=New CoerceValueCallback(AddressOf CoerceMinReading)),
        validateValueCallback:=New ValidateValueCallback(AddressOf IsValidReading))

    ' CLR wrapper with get/set accessors.
    Public Property MinReading As Double
        Get
            Return GetValue(MinReadingProperty)
        End Get
        Set(value As Double)
            SetValue(MinReadingProperty, value)
        End Set
    End Property

    ' Property-changed callback.
    Private Shared Sub OnMinReadingChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
        depObj.CoerceValue(MaxReadingProperty)
        depObj.CoerceValue(CurrentReadingProperty)
    End Sub

    ' Coerce-value callback.
    Private Shared Function CoerceMinReading(depObj As DependencyObject, value As Object) As Object
        Dim gauge As Gauge2 = CType(depObj, Gauge2)
        Dim minVal As Double = value
        Return If(minVal > gauge.MaxReading, gauge.MaxReading, minVal)
    End Function

End Class

Erweiterte Rückrufszenarien

Einschränkungen und gewünschte Werte

Wenn ein lokal festgelegter Wert einer Abhängigkeitseigenschaft durch Koersion geändert wird, wird der unveränderte lokal festgelegte Wert als gewünschter Wert beibehalten. Wenn die Koersion auf anderen Eigenschaftswerten basiert, wertet das Eigenschaftensystem die Koersion dynamisch neu aus, wenn sich diese anderen Werte ändern. Innerhalb der Einschränkungen der Koersion wendet das Eigenschaftensystem einen Wert an, der dem gewünschten Wert am nächsten ist. Sollte die Koersionsbedingung nicht mehr angewendet werden, stellt das Eigenschaftssystem den gewünschten Wert wieder her, vorausgesetzt, dass kein höherer Rangwert aktiv ist. Im folgenden Beispiel wird die Koersion im aktuellen Szenario für Wert, Mindestwert und Maximalwert getestet.

public static void TestCoercionBehavior()
{
    Gauge2 gauge = new()
    {
        // Set initial values.
        MinReading = 0,
        MaxReading = 10,
        CurrentReading = 5
    };

    Debug.WriteLine($"Test current/min/max values scenario:");

    // Current reading is not coerced.
    Debug.WriteLine($"Current reading: " +
        $"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");

    // Current reading is coerced to max value.
    gauge.MaxReading = 3;
    Debug.WriteLine($"Current reading: " +
        $"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");

    // Current reading is coerced, but tracking back to the desired value.
    gauge.MaxReading = 4;
    Debug.WriteLine($"Current reading: " +
        $"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");

    // Current reading reverts to the desired value.
    gauge.MaxReading = 10;
    Debug.WriteLine($"Current reading: " +
        $"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");

    // Current reading remains at the desired value.
    gauge.MinReading = 5;
    gauge.MaxReading = 5;
    Debug.WriteLine($"Current reading: " +
        $"{gauge.CurrentReading} (min: {gauge.MinReading}, max: {gauge.MaxReading})");

    // Current reading: 5 (min=0, max=10)
    // Current reading: 3 (min=0, max=3)
    // Current reading: 4 (min=0, max=4)
    // Current reading: 5 (min=0, max=10)
    // Current reading: 5 (min=5, max=5)
}
Public Shared Sub TestCoercionBehavior()

    ' Set initial values.
    Dim gauge As New Gauge2 With {
        .MinReading = 0,
        .MaxReading = 10,
        .CurrentReading = 5
    }

    Debug.WriteLine($"Test current/min/max values scenario:")

    ' Current reading is not coerced.
    Debug.WriteLine($"Current reading: " &
        $"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")

    ' Current reading is coerced to max value.
    gauge.MaxReading = 3
    Debug.WriteLine($"Current reading: " &
        $"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")

    ' Current reading is coerced, but tracking back to the desired value.
    gauge.MaxReading = 4
    Debug.WriteLine($"Current reading: " &
        $"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")

    ' Current reading reverts to the desired value.
    gauge.MaxReading = 10
    Debug.WriteLine($"Current reading: " &
        $"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")

    ' Current reading remains at the desired value.
    gauge.MinReading = 5
    gauge.MaxReading = 5
    Debug.WriteLine($"Current reading: " &
        $"{gauge.CurrentReading} (min={gauge.MinReading}, max={gauge.MaxReading})")

    ' Current reading: 5 (min=0, max=10)
    ' Current reading: 3 (min=0, max=3)
    ' Current reading: 4 (min=0, max=4)
    ' Current reading: 5 (min=0, max=10)
    ' Current reading: 5 (min=5, max=5)
End Sub

Relativ komplexe Abhängigkeitsszenarien können auftreten, wenn Sie über mehrere Eigenschaften verfügen, die zirkuläre voneinander abhängig sind. Technisch gesehen ist an komplexen Abhängigkeiten nichts auszusetzen, außer dass eine große Anzahl von Neuauswertungen die Leistung beeinträchtigen kann. Außerdem können komplexe Abhängigkeiten, die auf der Benutzeroberfläche verfügbar gemacht werden, Benutzer verwirren. Behandeln Sie PropertyChangedCallback und CoerceValueCallback so eindeutig wie möglich, und schränken Sie sie nicht übermäßig ein.

Ändern des Werts abbrechen

Wenn Sie UnsetValue von einem CoerceValueCallback zurückgeben, können Sie eine Änderung des Eigenschaftswerts ablehnen. Dieser Mechanismus ist nützlich, wenn eine Eigenschaftswertänderung asynchron initiiert wird, aber wenn sie angewendet wird, für den aktuellen Objektzustand nicht mehr gültig ist. Ein weiteres Szenario kann sein, eine Wertänderung selektiv zu unterdrücken, je nach Ursprung. Im folgenden Beispiel ruft CoerceValueCallback die GetValueSource-Methode auf, die eine ValueSource-Struktur mit einer BaseValueSource-Enumeration zurückgibt, die die Quelle des neuen Werts identifiziert.

// Coerce-value callback.
private static object CoerceCurrentReading(DependencyObject depObj, object value)
{
    // Get value source.
    ValueSource valueSource = 
        DependencyPropertyHelper.GetValueSource(depObj, CurrentReadingProperty);

    // Reject any property value change that's a locally set value.
    return valueSource.BaseValueSource == BaseValueSource.Local ? 
        DependencyProperty.UnsetValue : value;
}
' Coerce-value callback.
Private Shared Function CoerceCurrentReading(depObj As DependencyObject, value As Object) As Object
    ' Get value source.
    Dim valueSource As ValueSource =
        DependencyPropertyHelper.GetValueSource(depObj, CurrentReadingProperty)

    ' Reject any property value that's a locally set value.
    Return If(valueSource.BaseValueSource = BaseValueSource.Local, DependencyProperty.UnsetValue, value)
End Function

Weitere Informationen