Определение и регистрация свойств зависимости

56

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

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

Сначала нужно определить объект, который будет представлять свойство. Это экземпляр класса DependencyProperty. Информация о свойстве должна быть доступна постоянно и, возможно, даже другим классам (как обычно для элементов WPF). По этой причине объект DependencyProperty следует определить как статическое поле в связанном классе.

Например, класс FrameworkElement определяет свойство Margin, доступное всем элементам. Конечно, Margin — это свойство зависимости. Это означает, что оно определяется в классе FrameworkElement следующим образом:

public class FrameworkElement : UIElement, ...
{
   public static readonly DependencyProperty MarginProperty;
}

Принято соглашение, что поле, представляющее свойство зависимости, имеет имя обычного свойства с добавлением слова Property в конце. Таким образом можно отделить определение свойства зависимости от имени самого свойства. Поле определено с ключевым словом readonly — это означает, что его значение можно задать только в статическом конструкторе для класса FrameworkElement, но это уже следующий шаг.

Регистрация свойства зависимости

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

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

В следующем фрагменте кода приведен пример создания DependencyProperty. Здесь класс FrameworkElement использует статический конструктор для инициализации MarginProperty:

static FrameworkElement()
{
   FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(
      new Thickness(), FrameworkPropertyMetadataOptions.AffectsMeasure);
   MarginProperty = DependencyProperty.Register("Margin",
      typeof(Thickness), typeof(FrameworkElement), metadata,
      new ValidateValueCallback (FrameworkElement.IsMarginValid));
}

Регистрация свойства зависимости осуществляется в два этапа. Сначала создается объект FrameworkPropertyMetadata, который указывает, какие службы вы хотите использовать со свойством зависимости (например, поддержку привязки данных, анимацию и ведение журнала). Затем свойство регистрируется, для чего вызывается метод DependencyProperty.Register(). Здесь нужно определить несколько ключевых ингредиентов:

С первыми тремя ингредиентами все вроде бы ясно. Более интересными являются объект FrameworkPropertyMetadata и обратный вызов проверки.

Объект FrameworkPropertyMetadata используется для настройки дополнительных возможностей создаваемого свойства зависимости. Большая часть свойств класса FrameworkPropertyMetadata представляет собой обычные логические флаги, которые устанавливаются для активации этих возможностей (по умолчанию все эти флаги имеют значения false). Но некоторые из них являются обратными вызовами, которые указывают на пользовательские методы, созданные для выполнения конкретных задач. Одно из таких свойств — FrameworkPropertyMetadata.DefaultValue — устанавливает стандартное значение, которое WPF будет применять при первоначальной инициализации свойства. Ниже приведены все свойства FrameworkPropertyMetadata:

Свойства класса FrameworkPropertyMetadata
Имя Описание
AffectsArrange, AffectsMeasure, AffectsParentArrange, AffectsParentMeasure Если имеет значение true, то свойство зависимости может влиять на расположение смежных элементов (или родительского элемента) во время этапа измерения и этапа расстановки в операции компоновки. Например, свойство зависимости Margin заносит в AffectsMeasure значение true — это означает, что при изменении полей элементов контейнер компоновки должен повторить этап измерения, чтобы определить новое размещение элементов
AffectsRender Если имеет значение true, то свойство зависимости может влиять на внешний вид элемента, что требует перерисовки элемента
BindsTwoWayByDefault Если имеет значение true, то свойство зависимости будет использовать не одностороннюю (по умолчанию), а двухстороннюю привязку данных. Однако при создании привязки можно явно указать ее поведение
Inherits Если имеет значение true, то значение свойства зависимости распространяется по дереву элементов и может наследоваться вложенными элементами. Например, наследуемым свойством зависимости является Font: если указать его для элемента самого высокого уровня, то оно наследуется вложенными элементами, если не будет явно перекрыто собственными параметрами шрифта
IsAnimationProhibited Если имеет значение true, то свойство зависимости нельзя использовать в анимации
IsNotDataBindable Если имеет значение true, то значение свойства зависимости нельзя устанавливать в выражении привязки
Journal Если имеет значение true, то в страничном приложении значение свойства зависимости будет сохранено в журнале (история посещенных страниц)
SubPropertiesDoNotAffectRender Если имеет значение true, то WPF не будет выполнять перерисовку объекта при изменении одного из его подсвойств (свойства свойства)
DefaultUpdateSourceTrigger Устанавливает стандартное значение для свойства Binding.UpdateSourceTrigger, когда это свойство используется в выражении привязки. Свойство UpdateSourceTrigger определяет момент применения изменений привязанного значения. Свойство UpdateSourceTrigger можно установить вручную при создании привязки
DefaultValue Устанавливает стандартное значение для свойства зависимости
CoerceValueCallback Обеспечивает обратный вызов, который пытается "исправить" значение свойства перед его проверкой
PropertyChangedCallbacк Обеспечивает обратный вызов, который выполняется при изменении значения свойства
Пройди тесты
Лучший чат для C# программистов