Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
• Класс, который определяет свойство зависимости, должен иметь в своей цепочке наследования класс DependencyObject.
• Одиночное свойство зависимости представляется как открытое, статическое, допускающее только чтение поле типа DependencyProperty. По соглашению это поле именуется путем снабжения имени оболочки CLR (см. последний пункт списка) суффиксом Property.
• Переменная типа DependencyProperty регистрируется посредством вызова статического метода DependencyProperty.Register(), который обычно происходит в статическом конструкторе или встраивается в объявление переменной.
• В классе будет определено дружественное к XAML свойство CLR, которое вызывает методы, предоставляемые классом DependencyObject, для получения и установки значения.
После реализации свойства зависимости предлагают несколько мощных инструментов, которые применяются разнообразными технологиями WPF, в том числе привязкой данных, службами анимации, стилями, шаблонами и т.д. Мотивацией создания свойств зависимости было желание предоставить способ вычисления значений свойств на основе значений из других источников. Далее приведен список основных преимуществ, которые выходят далеко за рамки простой инкапсуляции данных, обеспечиваемой свойствами CLR.
• Свойства зависимости могут наследовать свои значения от определения XAML родительского элемента. Например, если в открывающем дескрипторе Window определено значение для атрибута FontSize, то все элементы управления внутри Window по умолчанию будут иметь тот же самый размер шрифта.
• Свойства зависимости поддерживают возможность получать значения, которые установлены элементами внутри их области определения XAML, например, в случае установки элементом Button свойства Dock родительского контейнера DockPanel. (Вспомните, что присоединяемые свойства делают именно это, поскольку являются разновидностью свойств зависимости.)
• Свойства зависимости позволяют инфраструктуре WPF вычислять значение на основе множества внешних значений, что может быть важно для служб анимации и привязки данных.
• Свойства зависимости предоставляют поддержку инфраструктуры для триггеров WPF (также довольно часто используемых при работе с анимацией и привязкой данных).
Имейте в виду, что во многих случаях вы будете взаимодействовать с существующим свойством зависимости способом, идентичным работе с обычным свойством CLR (благодаря оболочке CLR). В предыдущем разделе, посвященном привязке данных, вы узнали, что если необходимо установить привязку данных в коде, то должен быть вызван метод SetBinding() на целевом объекте операции и указано свойство зависимости, с которым будет работать привязка:
private void SetBindings()
{
Binding b = new Binding
{
// Зарегистрировать преобразователь, источник и путь.
Converter = new MyDoubleConverter(),
Source = this.mySB,
Path = new PropertyPath("Value")
};
// Указать свойство зависимости.
this.labelSBThumb.SetBinding(Label.ContentProperty, b);
}
Вы увидите похожий код в главе 27 во время исследования запуска анимации в коде:
// Указать свойство зависимости.
rt.BeginAnimation(RotateTransform.AngleProperty, dblAnim);
Потребность в построении специального свойства зависимости возникает только во время разработки собственного элемента управления WPF. Например, когда создается класс UserControl с четырьмя специальными свойствами, которые должны тесно интегрироваться с API-интерфейсом WPF, они должны быть реализованы с применением логики свойств зависимости.
В частности, если нужно, чтобы свойство было целью операции привязки данных или анимации, если оно обязано уведомлять о своем изменении, если свойство должно быть в состоянии работать в качестве установщика в стиле WPF или получать свои значения от родительского элемента, то возможностей обычного свойства CLR окажется не достаточно. В случае использования обычного свойства другие программисты действительно могут получать и устанавливать его значение, но если они попытаются применить такое свойство внутри контекста службы WPF, то оно не будет работать ожидаемым образом. Поскольку заранее нельзя узнать, как другие пожелают взаимодействовать со свойствами специальных классов UserControl, нужно выработать в себе привычку при построении специальных элементов управления всегда определять свойства зависимости.
Исследование существующего свойства зависимости
Прежде чем вы научитесь создавать специальные свойства зависимости, давайте рассмотрим внутреннюю реализацию свойства Height класса FrameworkElement. Ниже приведен соответствующий код (с комментариями):
// FrameworkElement "является" DependencyObject.
public class FrameworkElement : UIElement, IFrameworkInputElement,
IInputElement, ISupportInitialize, IHaveResources, IQueryAmbient
{
...
// Статическое поле только для чтения типа DependencyProperty.
public static readonly DependencyProperty HeightProperty;
// Поле DependencyProperty часто регистрируется
// в статическом конструкторе класса.
static FrameworkElement()
{
...
HeightProperty = DependencyProperty.Register(
"Height",
typeof(double),
typeof(FrameworkElement),
new FrameworkPropertyMetadata((double) 1.0 / (double) 0.0,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(FrameworkElement.OnTransformDirty)),
new ValidateValueCallback(FrameworkElement.IsWidthHeightValid));
}
// Оболочка CLR, реализованная с использованием
// унаследованных методов GetValue()/SetValue().
public double Height
{
get { return (double) base.GetValue(HeightProperty); }
set { base.SetValue(HeightProperty, value); }
}
}
Как видите, по сравнению с обычными свойствами CLR свойства зависимости требуют немалого объема дополнительного кода. В реальности зависимость может оказаться даже еще более сложной, чем показано здесь (к счастью, многие реализации проще свойства Height).
В первую очередь вспомните, что если в классе необходимо определить свойство зависимости, то он должен иметь в своей цепочке наследования DependencyObject, т.к. именно этот класс определяет методы GetValue() и SetValue(), применяемые в оболочке CLR. Из-за того, что класс FrameworkElement "является" DependencyObject, указанное требование удовлетворено.
Далее вспомните, что сущность, где действительно хранится значение свойства (значение double в случае Height), представляется как открытое, статическое, допускающее только чтение поле типа DependencyProperty. По соглашению имя этого свойства должно всегда формироваться из имени связанной оболочки CLR с добавлением суффикса Property:
public static readonly DependencyProperty HeightProperty;
Учитывая, что свойства зависимости объявляются как статические поля, они обычно создаются (и регистрируются) внутри статического конструктора класса. Объект DependencyProperty создается посредством вызова статического метода DependencyProperty.Register(). Данный метод имеет множество перегруженных версий, но в случае свойства Height он вызывается следующим образом:
HeightProperty = DependencyProperty.Register(
"Height",
typeof(double),
typeof(FrameworkElement),
new FrameworkPropertyMetadata((double)0.0,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(FrameworkElement.OnTransformDirty)),
new ValidateValueCallback(FrameworkElement.IsWidthHeightValid));
Первым аргументом, передаваемым методу DependencyProperty.Register(), является имя обычного свойства CLR класса (Height), а второй аргумент содержит информацию о типе данных, который его инкапсулирует (double). Третий аргумент указывает информацию о типе класса, которому принадлежит свойство (FrameworkElement). Хотя