Проверка свойств

47

При определении любого свойства необходимо учитывать возможность неверного задания его значения. Работая с обычными свойствами .NET, можно попытаться перехватить этот момент в методе установки значения. Но в случае свойств зависимости этот способ неприменим, т.к. свойство можно установить напрямую, с помощью метода SetValue() из системы свойств WPF.

Вместо этого в WPF предусмотрены два способа защиты от неверно установленных значений:

ValidateValueCallback

Этот обратный вызов может принимать или отбрасывать новые значения. Обычно он применяется для обнаружения очевидных ошибок, которые нарушают ограничения свойства. Его можно передать в качестве аргумента при вызове метода DependencyProperty.Register().

CoerceValueCallback

Этот обратный вызов может изменять введенные значения на более приемлемые. Обычно он применяется для обработки конфликтов между значениями свойств зависимости, установленных для одного и того же объекта. Такие значения могут быть верны порознь, но противоречить друг другу. Для использования этого обратного вызова передайте его в качестве аргумента конструктора при создании объекта FrameworkPropertyMetadata, который затем передается методу DependencyProperty.Register().

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

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

Как уже было сказано, метод DependencyProperty.Register() принимает необязательный параметр с обратным вызовом проверки:

MarginProperty = DependencyProperty.Register("Margin",
   typeof(Thickness), typeof(FrameworkElement), metadata,
   new ValidateValueCallback(FrameworkElement.IsMarginValid));

Его можно использовать для выполнения проверки, которая обычно помещается в процедуру установки значения свойства. Этот обратный вызов должен указывать на метод, который принимает объект в качестве параметра и возвращает логическое значение. Значение true означает, что объект верен, a false — что неверен, и его следует отбросить.

Проверка свойства FrameworkElement.Margin не представляет особого интереса, т.к. она зависит от внутреннего метода Thickness.IsValid(). Этот метод проверяет верность объекта Thickness в текущем контексте (когда он представляет краевое поле). Например, можно создать полностью корректный объект Thickness, который все-таки не годится для установки ширины поля — допустим, из-за отрицательных размеров. И если объект Thickness не может представлять краевое поле, то свойство IsMarginValid возвращает false.

У обратных вызовов проверки есть одно ограничение: они являются статическими методами и поэтому не имеют доступа к проверяемому объекту. В вашем распоряжении имеется лишь применяемое значение. Конечно, это облегчает их повторное использование, но делает невозможным создание процедуры проверки, которая должна учитывать другие свойства. Классический пример — элемент со свойствами Maximum и Minimum.

Понятно, что нельзя присваивать Maximum значение, которое меньше Minimum. Но такую проверку невозможно выполнить в обратном вызове проверки, т.к. при каждом вызове доступно только одно свойство.

Эту проблему рекомендуется решать с помощью приведения значений. Приведение — это шаг, который выполняется перед проверкой и позволяет изменить значение, чтобы оно стало более приемлемым (например, увеличить значение Maximum, чтобы оно стало не меньше Minimum), или вообще запретить изменение. Шаг приведения выполняется с помощью другого обратного вызова, прикрепленного к объекту FrameworkPropertyMetadata.

Обратный вызов приведения

Метод CoerceValueCallback вызывается с помощью объекта FrameworkPropertyMetadata. Вот, например:

FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.CoerceValueCallback = new CoerceValueCallback(CoerceMaximum);
DependencyProperty.Register("Maximum", typeof(double),
   typeof(RangeBase), metadata);

Метод CoerceValueCallback позволяет обрабатывать зависящие друг от друга свойства. К примеру, у объектов ScrollBar имеются свойства Maximum, Minimum и Value; все они унаследованы от класса RangeBase. Один из способов сохранить их согласованность — использование приведения свойств. Например, при установке значения Maximum должно быть выполнено такое приведение, чтобы это значение было не меньше Minimum:

private static object CoerceMaximum(DependencyObject d, object value)
{
   RangeBase basel = (RangeBase)d;
   if (((double) value) < basel.Minimum)
   {
      return basel.Minimum;
   }
return value;
}

Другими словами, если значение, применяемое к свойству Maximum, меньше, чем Minimum, то используется значение Minimum, а не применяемое значение. Обратите внимание: методу CoerceValueCallback передаются два параметра: применяемое значение и объект, к которому оно применяется.

Аналогичное приведение можно выполнить при установке значения Value: оно не должно выходить за границы, определяемые значениями Minimum и Maximum. Свойство Minimum вообще не выполняет приведение значения. Вместо этого при его изменении выполняется метод PropertyChangedCallback, который запускает приведение значений Maximum и Value.

В результате получается, что при применении конфликтующих значений наибольший приоритет имеет Minimum, затем Maximum (с возможной корректировкой по значению Minimum), а затем — Value (с возможной корректировкой по значениям Maximum и Minimum).

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

Пройди тесты
Лучший чат для C# программистов