Проверка и обратные вызовы свойства зависимостей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, если полученное значение является допустимым для свойства. В противном случае — возвращают false.They 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 имеет NegativeInfinityзначение или.The 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. В примере показано, как регистрируется свойство CurrentReading для набора связанных свойств (максимальное, минимальное, текущее) объекта *Reading.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

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.

С технической реализацией сложных зависимостей нет проблем, но они могут ухудшить производительность, если для их осуществления потребуется выполнять большой объем вычислений. Кроме того, они могут вызвать недоумение у пользователей, если будут непосредственно влиять на пользовательский интерфейс.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