Проверка свойств
47WPF --- Основа WPF --- Проверка свойств
При определении любого свойства необходимо учитывать возможность неверного задания его значения. Работая с обычными свойствами .NET, можно попытаться перехватить этот момент в методе установки значения. Но в случае свойств зависимости этот способ неприменим, т.к. свойство можно установить напрямую, с помощью метода SetValue() из системы свойств WPF.
Вместо этого в WPF предусмотрены два способа защиты от неверно установленных значений:
- ValidateValueCallback
Этот обратный вызов может принимать или отбрасывать новые значения. Обычно он применяется для обнаружения очевидных ошибок, которые нарушают ограничения свойства. Его можно передать в качестве аргумента при вызове метода DependencyProperty.Register().
- CoerceValueCallback
Этот обратный вызов может изменять введенные значения на более приемлемые. Обычно он применяется для обработки конфликтов между значениями свойств зависимости, установленных для одного и того же объекта. Такие значения могут быть верны порознь, но противоречить друг другу. Для использования этого обратного вызова передайте его в качестве аргумента конструктора при создании объекта FrameworkPropertyMetadata, который затем передается методу DependencyProperty.Register().
Вот как работают все эти части, когда приложение пытается установить значение свойства зависимости:
Вначале метод CoerceValueCallback получает возможность изменить полученное значение (обычно чтобы оно не противоречило значениям других свойств) или возвратить значение DependencyProperty.UnsetValue, которое вообще запрещает применение значения.
Затем запускается метод ValidateValueCallback. Он возвращает true, что означает принятие значения как верного, или false, что означает отказ от применения значения. В отличие от CoerceValueCallback, метод ValidateValueCallback не имеет доступа к самому объекту, в котором выполняется попытка изменения свойства — то есть он не может анализировать значения других свойств.
И, наконец, если оба предыдущих этапа закончились успешно, запускается метод PropertyChangedCallback. В это время можно сгенерировать событие изменения, если нужно обеспечить уведомление других классов.
Обратный вызов проверки
Как уже было сказано, метод 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 гарантируют, что их свойства можно устанавливать в любой последовательности, и это никак не повлияет на их поведение.