종속성 속성 콜백 및 유효성 검사(WPF .NET)

이 문서에서는 종속성 속성을 정의하고 종속성 속성 콜백을 구현하는 방법을 설명합니다. 콜백은 속성 값을 변경할 때 필요한 값 유효성 검사, 값 강제 변환 및 기타 논리를 지원합니다.

중요

.NET 7 및 .NET 6에 관한 데스크톱 가이드 설명서는 제작 중입니다.

필수 구성 요소

이 문서에서는 독자들이 종속성 속성에 대한 기본 지식을 갖고 있으며 종속성 속성 개요를 읽었다고 가정합니다. XAML(Extensible Application Markup Language)에 익숙하고 WPF 애플리케이션을 작성하는 방법을 알고 있으면 이 문서의 예제를 따라 하는 데 도움이 됩니다.

유효성 검사 값 콜백

유효성 검사 값 콜백을 사용하면 속성 시스템에서 적용하기 전에 새 종속성 속성 값이 유효한지 확인할 수 있습니다. 이 콜백은 값이 유효성 검사 조건을 충족하지 않으면 예외를 발생합니다.

유효성 검사 값 콜백은 속성을 등록하는 동안 종속성 속성에 한 번만 할당할 수 있습니다. 종속성 속성을 등록할 때는 ValidateValueCallback 참조를 Register(String, Type, Type, PropertyMetadata, ValidateValueCallback) 메서드에 전달할 수 있습니다. 유효성 검사 값 콜백은 속성 메타데이터의 일부가 아니며 재정의할 수 없습니다.

종속성 속성의 유효 값은 적용된 값입니다. 여러 속성 기반 입력이 존재하는 경우 유효 값은 속성 값 우선 순위를 통해 결정됩니다. 종속성 속성에 대해 유효성 검사 값 콜백이 등록된 경우, 속성 시스템은 값 변경에 대한 유효성 검사 값 콜백을 호출하여 새 값을 개체로서 전달합니다. 콜백 내에서 값 개체를 속성 시스템에 등록 된 형식으로 다시 강제 변환한 다음, 유효성 검사 논리를 실행할 수 있습니다. 콜백은 값이 속성에 유효하다면 true를 반환하고, 그렇지 않으면 false를 반환합니다.

유효성 검사 값 콜백에서 false를 반환한다면 예외가 발생하고 새 값이 적용되지 않습니다. 애플리케이션 작성자는 이러한 예외를 처리할 수 있도록 준비해야 합니다. 유효성 검사 값 콜백의 일반적인 용도는 열거형 값의 유효성을 검사하거나, 제한이 있는 측정을 나타내는 경우 숫자 값을 제한하는 것입니다. 유효성 검사 값 콜백은 다음과 같은 다양한 시나리오에서 속성 시스템이 호출합니다.

  • 생성 시의 기본값을 적용하는 개체 초기화.
  • SetValue에 대한 프로그래밍 방식 호출.
  • 새 기본값을 지정하는 메타데이터 재정의.

유효성 검사 값 콜백에는 새 값이 설정된 DependencyObject 인스턴스를 지정하는 매개 변수가 없습니다. DependencyObject의 모든 인스턴스는 동일한 유효성 검사 값 콜백을 공유하므로 인스턴스별 시나리오의 유효성을 검사하는 데 사용할 수 없습니다. 자세한 내용은 ValidateValueCallback를 참조하세요.

다음 예제에서는 Double로 입력되는 속성이 PositiveInfinity 또는 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

속성 변경 콜백

속성 변경 콜백은 종속성 속성의 유효 값이 변경되면 이를 사용자에게 알립니다.

속성 변경 콜백은 종속성 속성 메타데이터의 일부입니다. 종속성 속성을 정의하는 클래스에서 파생하거나 클래스를 종속성 속성의 소유자로 추가하는 경우에는 메타데이터를 재정의할 수 있습니다. 메타데이터를 재정의할 때는 새 PropertyChangedCallback 참조를 제공할 수 있습니다. 속성 변경 콜백을 사용하여 속성 값이 변경될 때 필요한 논리를 실행합니다.

유효성 검사 값 콜백과 달리, 속성 변경 콜백에는 새 값이 설정된 DependencyObject 인스턴스를 지정하는 매개 변수가 있습니다. 다음 예제에서는 속성 변경 콜백에서 DependencyObject 인스턴스 참조를 사용하여 강제 변환 값 콜백을 트리거하는 방법을 보여 줍니다.

강제 변환 값 콜백

강제 변환 값 콜백을 사용하면 종속성 속성의 유효 값이 변경되기 직전에 이를 통보받아, 새 값을 적용하기 전에 조정할 수 있습니다. 속성 시스템에서 트리거하기도 하지만, 코드에서 강제 변환 값 콜백을 호출할 수 있습니다.

강제 변환 값 콜백은 종속성 속성 메타데이터의 일부입니다. 종속성 속성을 정의하는 클래스에서 파생하거나 클래스를 종속성 속성의 소유자로 추가하는 경우에는 메타데이터를 재정의할 수 있습니다. 메타데이터를 재정의할 때는 참조를 새 CoerceValueCallback에 제공할 수 있습니다. 강제 변환 값 콜백을 사용하여 새 값을 평가하고 필요한 경우 강제 변환합니다. 콜백은 강제 변환이 발생하면 강제 변환된 값을 반환하고, 그렇지 않으면 변경되지 않은 새 값을 반환합니다.

속성 변경 콜백과 마찬가지로, 강제 변환 값 콜백에는 새 값이 설정된 DependencyObject 인스턴스를 지정하는 매개 변수가 있습니다. 다음 예제에서는 강제 변환 값 콜백에서 DependencyObject 인스턴스 참조를 사용하여 속성 값을 강제 변환하는 방법을 보여줍니다.

참고

기본 속성 값은 강제 변환할 수 없습니다. 종속성 속성은 개체 초기화 시 또는 ClearValue를 사용하여 다른 값을 지울 때 기본값을 설정합니다.

강제 변환 값 및 속성 변경 콜백의 조합

강제 변환 콜백 및 속성 변경 콜백 조합을 사용하여 요소의 속성 간 종속성을 만들 수 있습니다. 예를 들어 한 속성을 변경하면 다른 종속성 속성에서의 강제 변환이나 재평가가 강제됩니다. 다음 예제에서는 일반적인 시나리오인, 각각 UI 요소의 현재 값, 최소값 및 최대값을 저장하는 세 가지 종속성 속성을 보여 줍니다. 최대값이 변경되어 현재 값 보다 작지만, 현재 값이 새 최대값으로 설정됩니다. 그리고 최소값이 현재 값 보다 큰 값으로 변경되는 경우 현재 값이 새 최소값으로 설정 됩니다. 이 예제에서는 현재 값의 PropertyChangedCallback이 최소값 및 최대값의 CoerceValueCallback을 명시적으로 호출합니다.

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

고급 콜백 시나리오

제약 조건 및 원하는 값

종속성 속성의 로컬로 설정한 값이 강제 변환을 통해 변경되면, 변경되지 않은 로컬로 설정한 값은 원하는 값으로 유지됩니다. 강제 변환이 다른 속성 값을 기반으로 하는 경우, 속성 시스템은 이러한 다른 값이 변경될 때마다 자동으로 강제 변환을 다시 평가합니다. 강제 변환의 제약 조건 내에서 속성 시스템은 원하는 값에 가장 가까운 값을 적용합니다. 강제 변환 조건이 더 이상 적용되지 않는다면, 속성 시스템은 더 높은 우선 순위 값이 활성화되지 않았다고 가정하여 원하는 값을 복원합니다. 다음 예제에서는 현재 값, 최소값 및 최대값 시나리오에서의 강제 변환을 테스트합니다.

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

여러 속성이 서로 종속되는 관계가 되면 시나리오가 상당히 복잡해질 수 있습니다. 엄밀히 말하면, 복잡한 종속성은 잦은 재평가 때문에 성능이 저하된다는 점 외에는 아무 문제도 없습니다. 또한 UI에 노출되는 복잡한 종속성은 사용자의 혼동을 유발할 수 있습니다. PropertyChangedCallbackCoerceValueCallback을 최대한 명확하게 처리하고, 과도하게 제한하지 마세요.

값 변경 취소

CoerceValueCallback에서 UnsetValue를 반환하면 속성 값 변경을 거부할 수 있습니다. 이 메커니즘은 속성 값 변경이 비동기적으로 시작되었지만, 적용될 때는 현재 개체 상태에 유효하지 않은 경우에 유용합니다. 발생한 위치에 따라 값 변경을 선택적으로 표시하지 않는 경우에도 유용합니다. 다음 예제에서는 CoerceValueCallback에서 GetValueSource 메서드를 호출하며, 이 메서드는 새 값의 소스를 식별하는 BaseValueSource 열거형이 있는 ValueSource 구조를 반환합니다.

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

참고 항목