相依性屬性回呼和驗證

本主題說明如何針對屬性相關的功能建立使用替代自訂實作的相依性屬性,例如:驗證判斷、每當屬性有效值變更時叫用的回撥,以及覆寫值決策的可能外在影響。 本主題也會討論適合使用這些技術在展開預設屬性系統行為的案例。

必要條件

本主題假設您已了解實作相依性屬性的基本案例,以及如何將中繼資料套用到自訂相依性屬性。 如需相關內容,請參閱自訂相依性屬性相依性屬性中繼資料

驗證回撥

第一次登錄驗證回撥時,可以將它指派給相依性屬性。 驗證回呼不是屬性中繼資料的一部分;它是 方法的 Register 直接輸入。 因此,一旦針對相依性屬性建立驗證回撥,就不能以新的實作覆寫。

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

實作回撥以提供物件值。 如果提供的值對屬性有效,它們會傳回 true;否則傳回 false。 因為假設屬性是依照屬性系統登錄類型的正確類型,所以通常不會在回撥內檢查類型。 屬性系統在各種不同的作業中使用回撥。 這包括預設的初始類型初始化、叫用 SetValue 的程式設計變更,或嘗試以提供新的預設值覆寫中繼資料。 如果這些作業的任何一項叫用了驗證回撥,並傳回 false,則會引發例外狀況。 應用程式撰寫者必須準備好處理這些例外狀況。 驗證回撥的常見用法是驗證列舉值,或屬性設定必須為零或大於零的度量時,限制整數或雙精度浮點數。

驗證回撥原為專用的類別驗證程式,不是執行個體驗證程式。 回呼的參數不會傳達要驗證的屬性設定的特定 DependencyObject 。 因此,驗證回撥對強制執行可能影響屬性值的可能「相依性」無幫助,其中屬性的執行個體特定值是取決於其他屬性的執行個體特定值或執行階段狀態等因素。

以下是非常簡單之驗證回呼案例的範例程式碼:驗證類型為 Double 基本類型的屬性不是 PositiveInfinityNegativeInfinity

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

強制轉型值回撥和屬性變更事件

Coerce 值回呼會傳遞屬性的特定 DependencyObject 實例,就像每當相依性屬性的值變更時由屬性系統叫用的實作一樣 PropertyChangedCallback 。 搭配使用這兩種回撥,您可以在有以下特性的項目上建立一系列的屬性:一個屬性中的變更會強制另一個屬性轉型或重新評估。

使用相依性屬性連結的一般案例,是當您有使用者介面驅動的屬性,其中項目為最小值和最大值各保留一個屬性,為實際值或目前值保留第三個屬性。 在這裡,如果以某種方式調整了最大值,以致目前的值超過新的最大值,您可能會想要將目前的值強制轉型為不大於新的最大值,且為最小值和目前值的類似關聯。

以下是示範這種關係之三種相依性屬性其一的極短範例程式碼。 本例示範如何登錄相關 *Reading 屬性的最小值/最大值/目前值集合的 CurrentReading 屬性。 它使用上一節中示範的驗證。

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

目前值的屬性變更回撥藉由明確叫用已為其他屬性登錄的強制轉型值回撥,用來將變更轉送至其他相依的屬性:

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

強制轉型值回撥會檢查目前屬性可能相依的屬性值,如有必要會強制轉型目前的值︰

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

注意

不會強制轉型屬性的預設值。 如果屬性值仍有其初始預設值,或透過使用 清除其他值,可能會發生等於預設值的 ClearValue 屬性值。

強制轉型值和屬性變更回撥是屬性中繼資料的一部分。 因此,您可以覆寫您類型上該屬性的中繼資料,變更特定相依性屬性的回撥,因為它存在於擁有相依性屬性之衍生來源的類型。

進階的強制型轉和回撥案例

條件約束和所需的值

CoerceValueCallback屬性系統會使用回呼,根據您宣告的邏輯來強制值,但本機集合屬性的強制值仍會在內部保留「所需的值」。 如果條件約束是以在應用程式存留期間可能動態變更的其他屬性值為基礎,則強制型轉條件約束也會動態變更,而受條件約束的屬性可以變更其值,以儘量接近新條件約束指定的所需值。 如果提取所有條件約束,則值會成為所需的值。 如有多個屬性彼此循環相依,您可能會造成相當複雜的相依性情況。 例如,在最小值/最大值/目前值的案例中,您可以選擇讓使用者能設定最小值和最大值。 如果這樣,您可能需要強制轉型「最大值」一律大於「最小值」,反之亦然。 但若該強制型轉為作用中,且「最大值」強制轉型成「最小值」,會讓目前值處於無法設定的狀態,因為它相依於兩者,且受條件約束在值的範圍內,也就是零。 然後,如果調整「最大值」或「最小值」,目前值就似乎會「遵循」其中一個值,因為因為當條件約束放寬時,目前值的所需值仍會儲存並嘗試達到所需的值。

複雜相依性沒有任何技術問題,但如果它們需要大量重新評估,對效能會略有損害,如果直接影響到 UI 也會造成使用者困擾。 請小心使用屬性變更和強制轉型值回撥,確定嘗試中的強制轉型能盡可能明確處理,不會「過度約束」。

使用 CoerceValue 取消值變更

屬性系統會將傳回值 UnsetValue 的任何 CoerceValueCallback 視為特殊案例。 這個特殊案例表示屬性系統應該拒絕導致 CoerceValueCallback 呼叫 的屬性變更,而屬性系統應該改為報告屬性擁有的任何先前值。 這項機制很有用,可用來檢查過去以非同步方式初始化的屬性變更,現在是否對目前的物件狀態仍然有效,如果無效,則隱藏這些變更。 另一個可能的情況是,您可以看哪一個屬性值決定元件負責要回報的值,選擇性地隱藏值。 若要這樣做,您可以使用 DependencyProperty 傳入的 回呼和屬性識別碼作為 的 GetValueSource 輸入,然後處理 ValueSource

另請參閱