Главная » WPF » Система свойств WPF

0

Проанализировав все это, команда разработчиков WPF выделила три службы, которые должна поддерживать система свойств:

Уведомления об изменении. Отслеживание зависимостей. Выражения.

На первый взгляд, кажется, что этого маловато, но мы рассуждали следующим образом: если мы можем узнать, что некое свойство изменилось, и понимаем, ка кие свойства  затрагиваются этим изменением,  то сумеем реализовать сложные выражения,   которые  поддерживают многие  другие  механизмы  (наследование, стили и т.д.). С помощью этих трех краеугольных  камней мы сможем смоделиро вать и другие службы в системе свойств. Единственное, что нигде не документи ровано, – это выражения.

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

Разреженное хранилище. Стилизация.

Анимация. Связывание. Присоединенные свойства.

Существует базовый класс, который инкапсулирует все это поведение доста

точно общим способом:

public class DependencyObject : DispatcherObject {

public DependencyObject();

public void ClearValue(DependencyProperty dp); public object GetValue(DependencyProperty dp); protected virtual void OnPropertyChanged

(PropertyChangedEventArgs e);

public void SetValue(DependencyProperty dp, object value);

// многие методы опущены, чтобы не смущать читателя

}

Здесь  показаны  три основные  функции (получить, установить  и очистить)  и обратный  вызов  (уведомление об изменении). Объект  DependencyProperty од нозначно  идентифицирует свойство; его можно считать программным  именем свойства.

Класс DependencyObject предоставляет разреженное хранилище. Это сущест венно: для свойств, определенных с помощью системы свойств WPF, нет никаких накладных  расходов на каждый экземпляр, если только для них не задано значе ние,  отличное  от подразумеваемого по умолчанию.  Разреженному хранилищу было уделено много внимания при проектировании системы свойств, поскольку мы понимали,  что каркас будет интенсивно  обращаться  к свойствам (только  для управления рендерингом  текста имеется  более 40 свойств!),  так что любые нак ладные расходы на уровне экземпляра приведут к падению производительности. Исходный текст класса DependencyObject выглядит  примерно  так:

public class DependencyObject : DispatcherObject { IDictionary<DependencyProperty,object> _values =

new Dictionary<DependencyProperty, object>();

public DependencyObject();

public void ClearValue(DependencyProperty dp) {

_values.RemoveValue(dp);

}

public object GetValue(DependencyProperty dp) {

if (_values.ContainsKey(dp)) return _values[dp];

return dp.DefaultValue;

}

public void SetValue(DependencyProperty dp, object value) {

_values[dp] = value;

}

}

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

Преобразовать  код  класса   Widget,   так  чтобы  он  пользовался  системой свойств, тривиально:

public class Widget : DependencyObject {

public static readonly DependencyProperty StyleProperty = DependencyProperty.Register(«Style»,

typeof(Style), typeof(Widget));

public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register(«Background»,

typeof(Color), typeof(Widget));

public static readonly DependencyProperty ParentProperty = DependencyProperty.Register(«Parent»,

typeof(Widget),

typeof(Widget));

}

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

.NET система свойств. Система зависимых свойств спроектирована с целью до

полнить, а не заменить то, что уже есть в .NET:

public class Widget : DependencyObject {

public static readonly DependencyProperty StyleProperty = …; public static readonly DependencyProperty BackgroundProperty = …; public static readonly DependencyProperty ParentProperty = …;

public Style Style {

get { return (Style)GetValue(StyleProperty); }

set { SetValue(StyleProperty, value); }

}

public Color Background {

get { return (Color)GetValue(BackgroundProperty); }

set { SetValue(BackgroundProperty, value); }

}

public Widget Parent {

get { return (Widget)GetValue(ParentProperty); }

set { SetValue(ParentProperty, value); }

}

}

Метаданные

В .NET уже есть понятие метаданных – мы можем пометить атрибутом любой член, тип или сборку. Но у системы имеются ограничения, и одно из них – про изводительность. Для системы зависимых  свойств высокая  производительность операций и выявление некоторых аспектов свойства критически важны, и в част ности по этой причине мы решили, что при объявлении зависимого  свойства не обходимо сообщать его тип (чтобы не прибегать к отражению).

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

Кроме того, метаданные хранятся в объекте PropertyMetadata. Хотя информация в классе DependencyProperty фиксирована, мы вольны определять  новые свойства для типа производного от PropertyMetadata. Класс PropertyMetadata позволяет контроли ровать дополнительные метаданные, описывающие поведение свойства (DefaultValue и IsReadOnly), глобально подписываться на извещения об изменениях (PropertyChangedCallback) и выполнять приведение значений (CoerceValueCallback).

DefaultValue определяет,  какое значение вернуть, если свойство не было уста новлено  явно. Обычно  возвращается значение  по умолчанию  для соответствую щего типа данных (null для всех ссылочных типов, 0 для числовых, false для буле вского и т.д.). IsReadOnly говорит, что свойство предназначено только для чтения. В .NET запрещение изменять поля проверяется на нижнем уровне верификатором промежуточного языка, который гарантирует,  что полю, предназначенному толь ко для чтения,  значение  присваивается ровно  один раз внутри  конструктора. В системе зависимых свойств гарантия слабее; требуется лишь, чтобы значение свойству мог присваивать лишь тип владелец (или экземпляр этого типа).

Что такое глобальное извещение об изменении свойства, понять не так уж слож но, а вот о приведении значений  стоит поговорить  подробнее. Проще всего разоб раться в этом на примере элемента  управления Slider. Этот элемент поддерживает три свойства: Minimum,  Maximum  и Value. Требуется,  чтобы Value всегда лежало между Minimum и Maximum, но ведь задавать значения свойств можно в любом по рядке. Чтобы не нарушать граничных  условий  и в то же время не жертвовать  гиб костью, мы должны разрешить запись в свойство Value любого значения, но при не обходимости  привести его в пределы диапазона между Minimum  и Maximum.

Первым  шагом  реализации  приведения  значения  является  определение  вы

шеупомянутых свойств:

public class Slider : DependencyObject {

public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register(«Minimum»,

typeof(int), typeof(Slider),

new PropertyMetadata(0));

public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(«Maximum»,

typeof(int), typeof(Slider),

new PropertyMetadata(100));

public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(«Value»,

typeof(int), typeof(Slider),

new PropertyMetadata(50));

public int Minimum {

get { return (int)GetValue(MinimumProperty); }

set { SetValue(MinimumProperty, value); }

}

public int Maximum {

get { return (int)GetValue(MaximumProperty); }

set { SetValue(MaximumProperty, value); }

}

public int Value {

get { return (int)GetValue(ValueProperty); }

set { SetValue(ValueProperty, value); }

}

}

Поскольку мы хотим, чтобы значение Value всегда лежало между Minimum  и Maximum, введем новый статический метод для выполнения приведения и вклю чим его в состав метаданных  для Value:

public class Slider : DependencyObject {

public static readonly DependencyProperty MinimumProperty =

…;

public static readonly DependencyProperty MaximumProperty =

…;

public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(«Value»,

typeof(int), typeof(Slider),

new PropertyMetadata(50, null, ConstrainValue));

static object ConstrainValue(DependencyObject target, object baseValue) {

int min = (int)target.GetValue(MinimumProperty);

int max = (int)target.GetValue(MaximumProperty);

int value = (int)baseValue;

return Math.Min(Math.Max(value, min), max);

}

public int Minimum { get { … } set { … } } public int Maximum { get { … } set { … } } public int Value { get { … } set { … } }

}

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

ширения для добавления  зависимостей, нам приходится  вручную указывать сис теме свойств, как извещать  об изменениях. Мы можем добавить глобальный об работчик  изменения свойств Maximum  и Minimum  и сделать значение  Value не действительным, когда то или другое изменяется:

public class Slider : DependencyObject {

public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register(«Minimum»,

typeof(int), typeof(Slider),

new PropertyMetadata(0, ValueDependencyChanged));

public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(«Maximum»,

typeof(int), typeof(Slider),

new PropertyMetadata(100, ValueDependencyChanged));

public static readonly DependencyProperty ValueProperty =

…;

static void ValueDependencyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) {

target.InvalidateProperty(ValueProperty);

}

static object ConstrainValue(DependencyObject target, object baseValue) { … }

public int Minimum { get { … } set { … } } public int Maximum { get { … } set { … } } public int Value { get { … } set { … } }

}

Помимо  базового типа PropertyMetadata, существует  еще важный  производ ный  тип FrameworkPropertyMetadata, который  позволяет  настроить  поведение свойства по отношению к высокоуровневым средствам WPF,  например: связыва нию, стилям и наследованию.

Источник: К. Андерсон  Основы  Windows Presentation Foundation. Пер. с англ. А. Слинкина — М.: ДМК Пресс, 2008 — 432 с.: ил.

По теме:

  • Комментарии