Проверка и обратные вызовы свойства зависимостей

В этом разделе описывается создание свойства зависимостей с помощью альтернативных пользовательских реализаций функций, связанных со свойствами, таких как определение проверки, обратные вызовы, которые совершаются при каждом изменении эффективного значения свойства, и переопределение возможных внешних влияний на определение значения. В этом разделе также рассматриваются сценарии, в которых подходит расширение стандартных правил для системы свойств с помощью этих методов.

Необходимые компоненты

В этом разделе предполагается, что вы понимаете основные сценарии реализации свойства зависимостей и способы применения метаданных к настраиваемому свойству зависимостей. Дополнительные сведения см. в разделах Пользовательские свойства зависимостей и Метаданные свойства зависимостей.

Обратные вызовы проверки

Обратные вызовы проверки можно назначить свойству зависимостей при первой регистрации. Обратный вызов проверки не является частью метаданных свойства. Это прямой ввод метода 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, PositiveInfinity или 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

Обратные вызовы с привязкой к значению и события изменения свойств

При обратных вызовах с привязкой к значению передается конкретный экземпляр DependencyObject для свойств, как это делают реализации PropertyChangedCallback, которые вызываются системой свойств при каждом изменении значения свойства зависимостей. Используя сочетание этих обратных вызовов, можно создать ряд свойств элементов, где изменения значения одного свойства будут вызывать принудительное изменение или пересчет значения другого свойства.

Вот типичный сценарий использования связки свойств зависимостей. Имеется свойство, управляемое пользовательским интерфейсом. Элемент содержит по одному свойству для минимального и максимального значений и третье свойство для фактического или текущего значения. Если максимальное значение будет изменено так, что текущее значение станет превышать это новое максимальное значение, следует изменить текущее значение, сделав его не более максимального. Аналогичная связь нужна и для минимального значения.

Ниже приведен пример очень краткого кода для одного из трех свойств зависимостей, иллюстрирующий эту связь. В примере показано, как регистрируется свойство CurrentReading для набора связанных свойств (максимальное, минимальное, текущее) объекта *Reading. Он использует проверку, как это показано в предыдущем разделе.

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 будут использоваться системой свойств для приведения значения в соответствие с объявленной логикой, но приведенное значение локально заданного свойства будет по-прежнему внутренне сохранять «требуемое значение». Если ограничения зависят от других значений свойств, которые могут изменяться динамически во время работы приложения, приведенные ограничения будут также изменяться динамически, а ограниченное свойство может изменить свое значение, чтобы максимально приблизиться к требуемому значению с учетом новых ограничений. Значение станет равно требуемому значению, если все ограничения будут сняты. При необходимости можно создать достаточно сложные сценарии зависимостей, если имеется, например, несколько свойств, которые циклически зависят друг от друга. В сценарии, где имеется минимальное, максимальное и текущее значения, можно, например, сделать минимальное и максимальное значения определяемыми пользователем. В этом случае может потребоваться правило приведения, определяющее, что максимальное значение должно быть всегда больше минимального или наоборот. Но если приведение включено и максимальное значение приводится к минимальному, текущее значение остается в неопределенном состоянии, так как оно зависит от них обоих и ограничено диапазоном между ними, который равен нулю. С другой стороны, если максимальное или минимальное значение настраивается, текущее значение будет стремиться "отслеживать" одно из них. Это происходит из-за того, что для текущего значения сохраняется его требуемая величина и оно пытается достичь этой величины при ослаблении ограничений.

С технической реализацией сложных зависимостей нет проблем, но они могут ухудшить производительность, если для их осуществления потребуется выполнять большой объем вычислений. Кроме того, они могут вызвать недоумение у пользователей, если будут непосредственно влиять на пользовательский интерфейс. Будьте внимательны при использовании обратных вызовов с привязкой к значению и обратных вызовов при изменении свойств и следите за тем, чтобы попытки приведения могли выполняться максимально однозначно, не вызывая "сверхограничений".

Использование CoerceValue для отмены изменений значения

Система свойства будет считать все CoerceValueCallback, возвращающие значение UnsetValue, особыми случаями. Особый случай означает, что изменение свойства, которое привело к обратному вызову CoerceValueCallback, должно быть отклонено системой свойств и система вместо этого может вернуть предыдущее значение свойства. Этот механизм можно использовать для того, чтобы убедиться, что изменения свойства, инициированные асинхронно, все еще действительны для текущего состояния объекта, и, если это не так, запретить эти изменения. Другой возможный сценарий. Вы можете выборочно запретить значения в зависимости от того, какой компонент определения значения свойства отвечает за переданное значение. Для этого можно использовать DependencyProperty, переданный в обратном вызове, а также идентификатор свойств в качестве входных данных для GetValueSource, а затем выполнить обработку ValueSource.

См. также