相依性屬性回呼和驗證Dependency Property Callbacks and Validation

本主題說明如何針對屬性相關的功能建立使用替代自訂實作的相依性屬性,例如:驗證判斷、每當屬性有效值變更時叫用的回撥,以及覆寫值決策的可能外在影響。This topic describes how to create dependency properties using alternative custom implementations for property-related features such as validation determination, callbacks that are invoked whenever the property's effective value is changed, and overriding possible outside influences on value determination. 本主題也會討論適合使用這些技術在展開預設屬性系統行為的案例。This topic also discusses scenarios where expanding on the default property system behaviors by using these techniques is appropriate.

必要條件Prerequisites

本主題假設您已了解實作相依性屬性的基本案例,以及如何將中繼資料套用到自訂相依性屬性。This topic assumes that you understand the basic scenarios of implementing a dependency property, and how metadata is applied to a custom dependency property. 如需相關內容,請參閱自訂相依性屬性相依性屬性中繼資料See Custom Dependency Properties and Dependency Property Metadata for context.

驗證回撥Validation Callbacks

第一次登錄驗證回撥時,可以將它指派給相依性屬性。Validation callbacks can be assigned to a dependency property when you first register it. 驗證回撥不屬於屬性中繼資料;它是直接輸入Register方法。The validation callback is not part of property metadata; it is a direct input of the Register method. 因此,一旦針對相依性屬性建立驗證回撥,就不能以新的實作覆寫。Therefore, once a validation callback is created for a dependency property, it cannot be overridden by a new implementation.

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

實作回撥以提供物件值。The callbacks are implemented such that they are provided an object value. 如果提供的值對屬性有效,它們會傳回 true;否則傳回 falseThey return true if the provided value is valid for the property; otherwise, they return false. 因為假設屬性是依照屬性系統登錄類型的正確類型,所以通常不會在回撥內檢查類型。It is assumed that the property is of the correct type per the type registered with the property system, so checking type within the callbacks is not ordinarily done. 屬性系統在各種不同的作業中使用回撥。The callbacks are used by the property system in a variety of different operations. 這包括初始型別初始設定的預設值,以程式設計方式變更藉由叫用SetValue,或嘗試使用提供的新預設值來覆寫中繼資料。This includes the initial type initialization by default value, programmatic change by invoking SetValue, or attempts to override metadata with new default value provided. 如果這些作業的任何一項叫用了驗證回撥,並傳回 false,則會引發例外狀況。If the validation callback is invoked by any of these operations, and returns false, then an exception will be raised. 應用程式撰寫者必須準備好處理這些例外狀況。Application writers must be prepared to handle these exceptions. 驗證回撥的常見用法是驗證列舉值,或屬性設定必須為零或大於零的度量時,限制整數或雙精度浮點數。A common use of validation callbacks is validating enumeration values, or constraining values of integers or doubles when the property sets measurements that must be zero or greater.

驗證回撥原為專用的類別驗證程式,不是執行個體驗證程式。Validation callbacks specifically are intended to be class validators, not instance validators. 回呼參數不通訊特定DependencyObject上所設定要驗證的屬性。The parameters of the callback do not communicate a specific DependencyObject on which the properties to validate are set. 因此,驗證回撥對強制執行可能影響屬性值的可能「相依性」無幫助,其中屬性的執行個體特定值是取決於其他屬性的執行個體特定值或執行階段狀態等因素。Therefore the validation callbacks are not useful for enforcing the possible "dependencies" that might influence a property value, where the instance-specific value of a property is dependent on factors such as instance-specific values of other properties, or run-time state.

以下是非常簡單之驗證回撥案例的範例程式碼: 驗證屬性的類型是Double基本不PositiveInfinityNegativeInfinityThe following is example code for a very simple validation callback scenario: validating that a property that is typed as the Double primitive is not PositiveInfinity or 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

強制轉型值回撥和屬性變更事件Coerce Value Callbacks and Property Changed Events

強制轉型值回撥會傳遞特定DependencyObject執行個體屬性,如同PropertyChangedCallback每當相依性屬性的值變更時屬性系統叫用的實作。Coerce value callbacks do pass the specific DependencyObject instance for properties, as do PropertyChangedCallback implementations that are invoked by the property system whenever the value of a dependency property changes. 搭配使用這兩種回撥,您可以在有以下特性的項目上建立一系列的屬性:一個屬性中的變更會強制另一個屬性轉型或重新評估。Using these two callbacks in combination, you can create a series of properties on elements where changes in one property will force a coercion or reevaluation of another property.

使用相依性屬性連結的一般案例,是當您有使用者介面驅動的屬性,其中項目為最小值和最大值各保留一個屬性,為實際值或目前值保留第三個屬性。A typical scenario for using a linkage of dependency properties is when you have a user interface driven property where the element holds one property each for the minimum and maximum value, and a third property for the actual or current value. 在這裡,如果以某種方式調整了最大值,以致目前的值超過新的最大值,您可能會想要將目前的值強制轉型為不大於新的最大值,且為最小值和目前值的類似關聯。Here, if the maximum was adjusted in such a way that the current value exceeded the new maximum, you would want to coerce the current value to be no greater than the new maximum, and a similar relationship for minimum to current.

以下是示範這種關係之三種相依性屬性其一的極短範例程式碼。The following is very brief example code for just one of the three dependency properties that illustrate this relationship. 本例示範如何登錄相關 *Reading 屬性的最小值/最大值/目前值集合的 CurrentReading 屬性。The example shows how the CurrentReading property of a Min/Max/Current set of related *Reading properties is registered. 它使用上一節中示範的驗證。It uses the validation as shown in the previous section.

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

目前值的屬性變更回撥藉由明確叫用已為其他屬性登錄的強制轉型值回撥,用來將變更轉送至其他相依的屬性:The property changed callback for Current is used to forward the change to other dependent properties, by explicitly invoking the coerce value callbacks that are registered for those other properties:

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

強制轉型值回撥會檢查目前屬性可能相依的屬性值,如有必要會強制轉型目前的值︰The coerce value callback checks the values of properties that the current property is potentially dependent upon, and coerces the current value if necessary:

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

注意

不會強制轉型屬性的預設值。Default values of properties are not coerced. 如果屬性值仍有其初始的預設值,或清除與其他值,可能會發生屬性值等於預設值ClearValueA property value equal to the default value might occur if a property value still has its initial default, or through clearing other values with ClearValue.

強制轉型值和屬性變更回撥是屬性中繼資料的一部分。The coerce value and property changed callbacks are part of property metadata. 因此,您可以覆寫您類型上該屬性的中繼資料,變更特定相依性屬性的回撥,因為它存在於擁有相依性屬性之衍生來源的類型。Therefore, you can change the callbacks for a particular dependency property as it exists on a type that you derive from the type that owns the dependency property, by overriding the metadata for that property on your type.

進階的強制型轉和回撥案例Advanced Coercion and Callback Scenarios

條件約束和所需的值Constraints and Desired Values

CoerceValueCallback屬性系統強制值符合您宣告時,邏輯,但強制轉型的本機設定值使用回撥屬性仍然會保留 「 所需的值 」 在內部。The CoerceValueCallback callbacks will be used by the property system to coerce a value in accordance to the logic you declare, but a coerced value of a locally set property will still retain a "desired value" internally. 如果條件約束是以在應用程式存留期間可能動態變更的其他屬性值為基礎,則強制型轉條件約束也會動態變更,而受條件約束的屬性可以變更其值,以儘量接近新條件約束指定的所需值。If the constraints are based on other property values that may change dynamically during the application lifetime, the coercion constraints are changed dynamically also, and the constrained property can change its value to get as close to the desired value as possible given the new constraints. 如果提取所有條件約束,則值會成為所需的值。The value will become the desired value if all constraints are lifted. 如有多個屬性彼此循環相依,您可能會造成相當複雜的相依性情況。You can potentially introduce some fairly complicated dependency scenarios if you have multiple properties that are dependent on one another in a circular manner. 例如,在最小值/最大值/目前值的案例中,您可以選擇讓使用者能設定最小值和最大值。For instance, in the Min/Max/Current scenario, you could choose to have Minimum and Maximum be user settable. 如果這樣,您可能需要強制轉型「最大值」一律大於「最小值」,反之亦然。If so, you might need to coerce that Maximum is always greater than Minimum and vice versa. 但若該強制型轉為作用中,且「最大值」強制轉型成「最小值」,會讓目前值處於無法設定的狀態,因為它相依於兩者,且受條件約束在值的範圍內,也就是零。But if that coercion is active, and Maximum coerces to Minimum, it leaves Current in an unsettable state, because it is dependent on both and is constrained to the range between the values, which is zero. 然後,如果調整「最大值」或「最小值」,目前值就似乎會「遵循」其中一個值,因為因為當條件約束放寬時,目前值的所需值仍會儲存並嘗試達到所需的值。Then, if Maximum or Minimum are adjusted, Current will seem to "follow" one of the values, because the desired value of Current is still stored and is attempting to reach the desired value as the constraints are loosened.

複雜相依性沒有任何技術問題,但如果它們需要大量重新評估,對效能會略有損害,如果直接影響到 UI 也會造成使用者困擾。There is nothing technically wrong with complex dependencies, but they can be a slight performance detriment if they require large numbers of reevaluations, and can also be confusing to users if they affect the UI directly. 請小心使用屬性變更和強制轉型值回撥,確定嘗試中的強制轉型能盡可能明確處理,不會「過度約束」。Be careful with property changed and coerce value callbacks and make sure that the coercion being attempted can be treated as unambiguously as possible, and does not "overconstrain".

使用 CoerceValue 取消值變更Using CoerceValue to Cancel Value Changes

屬性系統會將任何CoerceValueCallback的傳回值UnsetValue視為特殊案例。The property system will treat any CoerceValueCallback that returns the value UnsetValue as a special case. 表示屬性變更,導致這種特殊案例CoerceValueCallback呼叫應該由屬性系統,遭到拒絕,且屬性系統應該改為回報屬性有任何先前的值。This special case means that the property change that resulted in the CoerceValueCallback being called should be rejected by the property system, and that the property system should instead report whatever previous value the property had. 這項機制很有用,可用來檢查過去以非同步方式初始化的屬性變更,現在是否對目前的物件狀態仍然有效,如果無效,則隱藏這些變更。This mechanism can be useful to check that changes to a property that were initiated asynchronously are still valid for the current object state, and suppress the changes if not. 另一個可能的情況是,您可以看哪一個屬性值決定元件負責要回報的值,選擇性地隱藏值。Another possible scenario is that you can selectively suppress a value depending on which component of property value determination is responsible for the value being reported. 若要這樣做,您可以使用DependencyProperty做為輸入傳遞回撥和屬性的識別項GetValueSource,然後再處理ValueSourceTo do this, you can use the DependencyProperty passed in the callback and the property identifier as input for GetValueSource, and then process the ValueSource.

另請參閱See also