Использование свойств зависимости

76

Свойства зависимости необходимы самым разным средствам WPF. Тем не менее, все эти средства имеют две ключевых возможности, поддерживаемых каждым свойством зависимости — это уведомление об изменении и динамическое разрешение значений.

Как ни странно, свойства зависимости не генерируют автоматически события, чтобы дать знать об изменении значения свойства. Вместо этого они запускают защищенный метод OnPropertyChangedCallback(). Он передает информацию двум службам WPF (привязка данных и триггеры) и вызывает метод PropertyChangedCallback, если он определен. Другими словами, если вы хотите выполнить действие в случае изменения свойства, у вас есть два варианта: можно создать привязку, которая использует значение свойства, или написать триггер, который автоматически изменяет другое свойство или запускает анимацию. Однако свойства зависимости не дают обобщенный способ запуска некоторого кода в ответ на изменение свойства.

При работе с созданным вами элементом управления можно воспользоваться механизмом обратного вызова свойства, чтобы реагировать на изменения свойства и даже генерировать событие. Многие обычные элементы управления используют этот прием для свойств, которые соответствуют информации, заданной пользователем. Например, элемент TextBox имеет событие TextChanged, a ScrollBar — событие ValueChanged. Элемент управления может реализовывать подобную функцию с помощью объекта PropertyChangedCallback, однако по соображениям производительности эта возможность в свойствах зависимости закрыта от обычного доступа.

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

Такое поведение и объясняет название этих свойств — по сути, свойство зависимости зависит от нескольких поставщиков свойств, каждый из которых имеет свой уровень приоритета. При извлечении значения из свойства система свойств WPF выполняет ряд действий, которые дают окончательное значение. Сначала она определяет базовое значение свойства, учитывая следующие факторы, перечисленные в порядке возрастания приоритета:

Как показывает этот список, при непосредственном присваивании значения переопределяется целая иерархия значений. Иначе значение берется из ближайшего применимого элемента выше в списке.

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

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

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

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

Совместно используемые свойства зависимости

Некоторые классы совместно используют одно и то же свойство зависимости, даже если они имеют отдельные иерархии классов. Например, TextBlock.FontFamily и Control.FontFamily указывают на одно и то же статическое свойство зависимости, которое определено в свойстве TextElement.FontFamilyProperty класса TextElement. Статический конструктор TextElement регистрирует свойство, а статические конструкторы TextBlock и Control просто повторно используют его, вызывая метод DependencyProperty.AddOwner():

TextBlock.FontFamilyProperty =
   TextElement.FontFmamilyProperty.AddOwner(typeof(TextBlock));

Такую технологию можно применять при создании собственных пользовательских классов (если нужное свойство еще не определено в базовом классе — иначе вы получите его готовым). Можно также использовать перегрузку метода AddOwner(), что позволит определить обратный вызов проверки и новый объект FrameworkPropertyMetadata, который будет применяться только к этому новому использованию свойства зависимости.

Повторное использование свойств зависимости может привести в WPF к некоторым странным побочным эффектам, особенно в стилях. Например, если применить стиль для автоматического задания свойства TextBlock.FontFamily, то это повлияет и на свойство Control.FontFamily, поскольку "за кулисами" оба класса используют одно и то же свойство зависимости

Прикрепляемые свойства зависимости

Прикрепляемое свойство (attached property) — это свойство зависимости, которым управляет система свойств WPF. Его отличительной чертой является тот факт, что прикрепляемое свойство применяется к классу, отличному от того, в котором оно определено.

Наиболее характерный пример прикрепляемых свойств можно найти в контейнерах компоновки. Например, класс Grid определяет прикрепляемые свойства Row и Column, которые задаются для содержащихся элементов и показывают их расположение. Точно так же класс DockPanel определяет прикрепляемое свойство Dock, a Canvas — прикрепляемые свойства Left, Right, Тор и Bottom.

Для определения прикрепляемого свойства используется метод RegisterAttached(), а не Register(). Вот пример регистрации свойства Grid.Row:

FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(
   0, new PropertyChangedCallback(Grid.OnCellAttachedPropertyChanged));

Grid.RowProperty = DependencyProperty.RegisterAttached("Row", typeof(int),
typeof(Grid), metadata, new ValidateValueCallback(Grid.IsIntValueNotNegative));   

Как и при использовании обычного свойства зависимости, здесь также можно определить объект FrameworkPropertyMetadata и ValidateValueCallback.

При создании прикрепляемого свойства оболочка свойства .NET не определяется. Это связано с тем, что прикрепляемые свойства могут быть заданы в любом объекте зависимости. Например, свойство Grid.Row может быть задано в объекте Grid (если один объект Grid вложен в другой) или в каком-то другом элементе. Вообще-то свойство Grid.Row может быть задано в элементе, даже если это не экземпляр Grid — и даже если в дереве объектов вообще нет ни одного объекта Grid.

Вместо применения оболочки свойства .NET для прикрепляемых свойств требуется пара статических методов, которые могут быть вызваны для установки и получения значения свойства. Эти методы используют знакомые вам методы SetValue() и GetValue() (унаследованные от класса DependencyObject). Статические методы должны иметь имена наподобие SetИмяСвойства() и GetИмяСвойства().

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