Архитектура WPF. Dependency Properties
Известно, что CLR дает возможность использовать в качестве членов классов свойства с параметрами или без. Например, в C# мы можем воспользоваться этим для создания get- и/или set- элементов для членов-данных и индексаторов с различным количеством параметров. Лаконичный синтаксис таких свойств весьма удобен и везде используется.
В контексте WPF ситуация обстоит таким образом, что в некоторых случаях функциональности обычных свойств недостаточно. Причины этого следующие:
- Иногда возникает необходимость наследования значений свойств в порядке отношений «родитель-ребенок» логического дерева. Например, если для родительской панели задан красный цвет фона, то такой цвет фона должны иметь и дочерние кнопки. Такая функциональность в разной степени реализована в Windows Forms и ActiveX (Ambient Properties); эти реализации довольно противоречивы, неполны и их практически невозможно использовать прикладному программисту.
- Часто у клиентов какого-либо класса возникает потребность в отслеживании событий о изменении какого-либо свойства, не имея информации о конкретном свойстве (например, нужно отслеживать значение цвета фона, не имея сведений о наличии события BackColorChanged). Одним из способов достижения этого является использование стандартного интерфейса INotifyPropertyChanged, который находится в пространстве имен System.ComponentModel и содержит единственное событие PropertyChanged, аргументы которого включают имя измененного свойства в форме обычной строки.
- Встречаются случаи, когда для реализации определенной подсистемы необходимы механизмы работы на метауровне наподобие Reflection, но очень быстрые. Тогда обычно применяются не очень-то элегантные решения типа Property Bags, сделанных на ассоциативных массивах и другие подобные вещи.
В качестве архитектурно целостного и элегантного решения этих и некоторых других проблем WPF вводит концепцию Dependency Properties (для себя я не нашел однозначно корректного перевода этого термина на русский язык, так что буду упоминать его в оригинале).
На базе Dependency Properties реализованы следующие подсистемы WPF:
- Cтили
- Data Binding
- Наследование свойств в логическом дереве
- Анимация свойств
- Расширения разметки XAML
- Layout визуальных элементов и другое
Dependency Properties можно рассматривать с двух точек зрения – как их использовать (на клиентской стороне), и как их создавать (на серверной стороне).
Для того, чтобы использовать такое свойство, вам не нужно учитывать практически ничего – для клиента оно выглядит как обычное CLR-свойство. То есть, пока вам в ваших классах не нужна функциональность, предоставляемая подсистемой Dependency Properties, вам не нужно знать об этом совсем ничего.
Для того, чтобы создать Dependency Property в своем коде, нужно знать и уметь определенные вещи. Использование Dependency Properties накладывает на дизайн вашего кода некоторые ограничения. Во-первых, класс, который содержит Dependency Property, должен быть наследником класса DependencyObject. Во-вторых, такие свойства создаются по определенному несложному шаблону. Идея этого шаблона состоит в том, чтобы сначала зарегистрировать описание вашего свойства, используя определенные статические методы классов среды, а затем предоставить доступ клиентам к этому свойству при помощи фасада из обычного CLR-свойства. Чтобы не путать вас таким описанием, приведу простой пример кода.
Видно, что на статическом уровне мы сначала регистрируем описание свойства, а затем предоставляем фасад для него при помощи обычного синтаксиса. Все довольно просто. Конечно, при разработке могут потребоваться и более сложный синтаксис регистрации, но в подавляющем большинстве случаев это выглядит так, как показано выше.
Таким образом, мы получаем видимое извне "обычное" свойство, а также тот факт, что описание этого свойства зарегистрировано во внутренних механизмах WPF и эта регистрационная информация может быть использована для поддержки описанной выше функциональности.
В заключение хочется подчеркнуть следующее. Далеко не все свойства в WPF – Dependency Properties. Этот механизм используется только там, где это нужно. Таким образом, для прикладного программиста должно стать естественным правило использовать Dependency Properties только при необходимости.