Validação e retornos de chamada de propriedade de dependência (WPF .NET)

Este artigo descreve como definir uma propriedade de dependência e implementar retornos de chamada de propriedade de dependência. Os retornos de chamada oferecem suporte à validação de valor, coerção de valor e outra lógica necessária quando um valor de propriedade é alterado.

Importante

A documentação do Guia da Área de Trabalho para .NET 7 e .NET 6 está em construção.

Pré-requisitos

O artigo pressupõe um conhecimento básico das propriedades de dependência e que você leu Visão geral das propriedades de dependência. Para seguir os exemplos neste artigo, é útil se você estiver familiarizado com XAML (Extensible Application Markup Language) e souber como escrever aplicativos WPF.

Retornos de chamada de valor de validação

Os retornos de chamada de valor de validação fornecem uma maneira de verificar se um novo valor de propriedade de dependência é válido antes de ser aplicado pelo sistema de propriedades. Esse retorno de chamada gera uma exceção se o valor não atender aos critérios de validação.

Os retornos de chamada de valor de validação só podem ser atribuídos a uma propriedade de dependência uma vez, durante o registro de propriedade. Ao registrar uma propriedade de dependência, você tem a opção de passar uma ValidateValueCallback referência para o Register(String, Type, Type, PropertyMetadata, ValidateValueCallback) método. Os retornos de chamada de valor de validação não fazem parte dos metadados da propriedade e não podem ser substituídos.

O valor efetivo de uma propriedade de dependência é seu valor aplicado. O valor efetivo é determinado por meio da precedência do valor da propriedade quando existem várias entradas baseadas em propriedade. Se um retorno de chamada de valor validado for registrado para uma propriedade de dependência, o sistema de propriedades invocará seu retorno de chamada de valor de validação na alteração de valor, passando o novo valor como um objeto. No retorno de chamada, você pode converter o objeto de valor de volta para o tipo registrado no sistema de propriedades e, em seguida, executar sua lógica de validação nele. O retorno de chamada retorna true se o valor for válido para a propriedade, caso contrário false.

Se um retorno de chamada de valor de validação retornar false, uma exceção será gerada e o novo valor não será aplicado. Criadores de aplicativo devem estar preparados para tratar essas exceções. Um uso comum de retornos de chamada de valor de validação é validar valores de enumeração ou restringir valores numéricos quando eles representam medidas que têm limites. Os retornos de chamada de valor de validação são chamados pelo sistema de propriedades em diferentes cenários, incluindo:

  • Inicialização de objeto, que aplica um valor padrão no momento da criação.
  • Chamadas programáticas para SetValue.
  • Substituições de metadados que especificam um novo valor padrão.

Os retornos de chamada de valor de validação não têm um parâmetro que especifique a DependencyObject instância na qual o novo valor é definido. Todas as instâncias de um DependencyObject compartilham o mesmo retorno de chamada de valor validado, portanto, ele não pode ser usado para validar cenários específicos da instância. Para obter mais informações, consulte ValidateValueCallback.

O exemplo a seguir mostra como impedir que uma propriedade, digitada como , seja definida como DoublePositiveInfinity ou NegativeInfinity.

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

Retornos de chamada alterados por propriedade

Os retornos de chamada alterados por propriedade notificam quando o valor efetivo de uma propriedade de dependência é alterado.

Os retornos de chamada alterados por propriedade fazem parte dos metadados da propriedade de dependência. Se você derivar de uma classe que define uma propriedade de dependência ou adicionar sua classe como proprietário de uma propriedade de dependência, poderá substituir os metadados. Ao substituir metadados, você tem a opção de fornecer uma nova PropertyChangedCallback referência. Use um retorno de chamada alterado por propriedade para executar a lógica necessária quando um valor de propriedade é alterado.

Ao contrário dos retornos de chamada de valor de validação, os retornos de chamada alterados por propriedade têm um parâmetro que especifica a DependencyObject instância na qual o novo valor é definido. O próximo exemplo mostra como um retorno de chamada alterado por propriedade pode usar a referência de instância para disparar retornos de chamada de DependencyObject valor de coerção.

Retornos de chamada com valor de coerce

Os retornos de chamada de valor de coerce fornecem uma maneira de você ser notificado quando o valor efetivo de uma propriedade de dependência está prestes a mudar, para que você possa ajustar o novo valor antes que ele seja aplicado. Além de ser acionado pelo sistema de propriedades, você pode invocar retornos de chamada de valor de coerce do seu código.

Os retornos de chamada de valor de coerce fazem parte dos metadados da propriedade de dependência. Se você derivar de uma classe que define uma propriedade de dependência ou adicionar sua classe como proprietário de uma propriedade de dependência, poderá substituir os metadados. Ao substituir os metadados, você tem a opção de fornecer uma referência a um novo CoerceValueCallbackarquivo . Use um retorno de chamada de valor de coerção para avaliar novos valores e coagi-los quando necessário. O retorno de chamada retorna o valor coercitivo se a coerção ocorreu, caso contrário, ele retorna o novo valor inalterado.

Semelhante aos retornos de chamada alterados por propriedade, os retornos de chamada de valor de coerção têm um parâmetro que especifica a DependencyObject instância na qual o novo valor é definido. O próximo exemplo mostra como um retorno de chamada de valor de coerce pode usar uma DependencyObject referência de instância para coagir valores de propriedade.

Observação

Os valores de propriedade padrão não podem ser coagidos. Uma propriedade de dependência tem seu valor padrão definido na inicialização do objeto ou quando você limpa outros valores usando ClearValueo .

Retornos de chamada com valor de coerce e alteração de propriedade em combinação

Você pode criar dependências entre propriedades em um elemento, usando retornos de chamada de valor de coerção e retornos de chamada alterados de propriedade em combinação. Por exemplo, alterações em uma propriedade forçam a coerção ou reavaliação em outra propriedade de dependência. O próximo exemplo mostra um cenário comum: três propriedades de dependência que armazenam, respectivamente, o valor atual, o valor mínimo e o valor máximo de um elemento da interface do usuário. Se o valor máximo for alterado para que seja menor que o valor atual, o valor atual será definido como o novo valor máximo. E, se o valor mínimo for alterado para que seja maior que o valor atual, o valor atual será definido como o novo valor mínimo. No exemplo, o para o valor atual invoca explicitamente o PropertyChangedCallbackCoerceValueCallback para os valores mínimo e máximo.

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

Cenários avançados de retorno de chamada

Restrições e valores desejados

Se um valor definido localmente de uma propriedade de dependência for alterado por meio de coerção, o valor definido localmente inalterado será mantido como o valor desejado. Se a coerção for baseada em outros valores de propriedade, o sistema de propriedade reavaliará dinamicamente a coerção sempre que esses outros valores mudarem. Dentro das restrições da coerção, o sistema de propriedade aplicará um valor mais próximo do valor desejado. Se a condição de coerção não se aplicar mais, o sistema de propriedades restaurará o valor desejado — supondo que nenhum valor de precedência mais alto esteja ativo. O exemplo a seguir testa a coerção no cenário de valor atual, valor mínimo e valor máximo.

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

Cenários de dependência bastante complexos podem ocorrer quando você tem várias propriedades que dependem umas das outras de maneira circular. Tecnicamente, não há nada de errado com dependências complexas, exceto que um grande número de reavaliações pode reduzir o desempenho. Além disso, dependências complexas expostas na interface do usuário podem confundir os usuários. Trate PropertyChangedCallback e da forma mais inequívoca possível, e CoerceValueCallback não se sobrecarregue.

Cancelar alterações de valor

Ao retornar UnsetValue de um CoerceValueCallback, você pode rejeitar uma alteração de valor de propriedade. Esse mecanismo é útil quando uma alteração de valor de propriedade é iniciada de forma assíncrona, mas quando é aplicada não é mais válida para o estado atual do objeto. Outro cenário pode ser suprimir seletivamente uma alteração de valor com base em sua origem. No exemplo a seguir, o chama o GetValueSourceCoerceValueCallback método, que retorna uma estrutura com uma BaseValueSourceValueSource enumeração que identifica a origem do novo valor.

// 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

Confira também