依赖项属性回调和验证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基元PositiveInfinity的属性是否为或NegativeInfinityThe 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. 如果属性值仍具有初始默认值, 或者通过使用ClearValue来清除其他值, 则可能会出现等于默认值的属性值。A 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

属性系统将使用回调来强制按照您声明的逻辑强制值,但本地设置属性的强制值在内部仍将保留"所需的值"。CoerceValueCallbackThe 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输入ValueSource, 然后处理。To 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