Вот объяснение того, как работают свойства зависимостей, которые я всегда хотел, чтобы кто-то написал для меня. Он неполный и, возможно, неправильный, но поможет вам выработать достаточное понимание их, что вы сможете понять прочитанную документацию.
Свойства зависимости - это свойства-подобные значения, которые получают и устанавливают с помощью методов класса DependencyObject
. Они могут (и обычно так) выглядят очень похоже на свойства CLR, но это не так. И это становится первым запутанным в них. Свойство зависимостей действительно состоит из пары компонентов.
Вот пример:
Document
является свойством объекта RichTextBox
. Это настоящая собственность CLR. То есть у него есть имя, тип, метод получения и метод установки, как и любое другое свойство CLR. Но в отличие от «обычных» свойств, свойство RichTextBox
не просто получает и устанавливает частное значение внутри экземпляра. Внутренне это реализовано так:
public FlowDocument Document
{
get { return (FlowDocument)GetValue(DocumentProperty); }
set { SetValue(DocumentProperty, value); }
}
Когда вы устанавливаете Document
, переданное вами значение передается в SetValue
вместе с DocumentProperty
. А что такое , что ? И как GetValue
получает его значение? И ... почему?
Сначала что. В RichTextBox
определено статическое свойство с именем DocumentProperty
. Когда это свойство объявлено, оно делается так:
public static DependencyProperty DocumentProperty = DependencyProperty.Register(
"Document",
typeof(FlowDocument),
typeof(RichTextBox));
Метод Register
в этом случае сообщает системе свойств зависимостей, что RichTextBox
- тип, а не экземпляр - теперь имеет свойство зависимости с именем Document
типа FlowDocument
. Этот метод хранит эту информацию ... где-то. Где именно детали реализации, которые скрыты от нас.
Когда метод установки для свойства Document
вызывает SetValue
, метод SetValue
просматривает аргумент DocumentProperty
, проверяет, действительно ли это свойство принадлежит RichTextBox
и что value
является правильным типа, а затем сохраняет его новое значение ... где-то. Документация для DependencyObject
не совсем понятна в этой детали реализации, потому что вам не нужно это знать. В моей ментальной модели того, как этот материал работает, я предполагаю, что есть свойство типа Dictionary<DependencyProperty, object>
, которое является приватным для DependencyObject
, поэтому производные классы (например, RichTextBox
) не могут его видеть, но GetValue
и SetValue
можете обновить его. Но кто знает, может, это написано на пергаменте монахами.
Во всяком случае, это значение теперь является так называемым «локальным значением», то есть значением, локальным для данного конкретного RichTextBox
, как обычное свойство.
Точка все это:
- Код CLR не должен знать, что свойство является свойством зависимости. Это выглядит точно так же, как любое другое свойство. Вы можете позвонить
GetValue
и SetValue
, чтобы получить и установить его, но если вы не делаете что-то с системой свойств зависимостей, вам, вероятно, не нужно.
- В отличие от обычного свойства, при его получении и установке может быть задействовано что-то, кроме объекта, которому оно принадлежит. (Возможно, вы могли бы сделать это с отражением, но размышление медленное. Поиск слов в словарях быстрый.)
- Это нечто - система свойств зависимостей - по сути, находится между объектом и его свойствами зависимостей. И это может сделать все виды вещей .
Какие вещи? Что ж, давайте рассмотрим некоторые варианты использования.
Привязка. При привязке к свойству оно должно быть свойством зависимости. Это связано с тем, что объект Binding
фактически не устанавливает свойства для цели, он вызывает SetValue
для целевого объекта.
Стили. Когда вы устанавливаете свойство зависимости объекта на новое значение, SetValue
сообщает системе стилей, что вы сделали это. Вот как работают триггеры: они не обнаруживают, что значение свойства изменилось за счет магии, сообщает им система свойств зависимости.
Динамические ресурсы. Если вы напишите XAML, как Background={DynamicResource MyBackground}
, вы можете изменить значение ресурса MyBackground
, а фон объекта, ссылающегося на него, будет обновлен. Это тоже не волшебство; динамический ресурс вызывает SetValue
.
Анимации. Анимации работают путем манипулирования значениями свойств. Это должны быть свойства зависимостей, потому что анимация вызывает SetValue
, чтобы получить их.
Уведомление об изменении. Когда вы регистрируете свойство зависимости, вы также можете указать функцию, которую SetValue
будет вызывать при установке значения свойства.
Наследование значений. Когда вы регистрируете свойство зависимостей, вы можете указать, что оно участвует в наследовании значений свойств. Когда вы вызываете GetValue
, чтобы получить значение свойства зависимости объекта, GetValue
проверяет, есть ли локальное значение. Если нет, то он пересекает цепочку родительских объектов, просматривая их локальные значения для этого свойства.
Вот так вы можете установить FontFamily
на Window
и волшебным образом (я часто использую это слово) каждый элемент управления в окне использует новый шрифт. Кроме того, так получается, что вы можете иметь сотни элементов управления в окне, при этом каждый из них не имеет переменной FontFamily
для отслеживания их шрифта (поскольку они не имеют локальных значений), но вы все равно можете установить FontFamily
на любом элементе управления (из-за поискового словаря скрытых значений, который есть у каждого DependencyObject
).