Пользовательские свойства зависимостей
Обновлен: Ноябрь 2007
В этом разделе описываются причины, по которым разработчики приложения Windows Presentation Foundation (WPF) и авторы компонента могут создавать пользовательские свойства зависимостей, а также этапы и параметры реализации, которые могут улучшить быстродействие, удобство использования и универсальность свойства.
В этом разделе содержатся следующие подразделы.
- Необходимые компоненты
- Что такое свойство зависимостей?
- Примеры свойств зависимостей
- Когда следует реализовывать свойство зависимостей?
- Контрольный список для определения свойства зависимостей
- Свойства зависимости «только для чтения»
- Свойства зависимостей типа коллекции
- Вопросы безопасности свойств зависимостей
- Свойства зависимостей и конструкторы класса
- Связанные разделы
Необходимые компоненты
В этом разделе предполагается, что вы понимаете свойства зависимостей с точки зрения потребителя существующих свойств зависимостей классов WPF и ознакомлены с разделом Общие сведения о свойствах зависимости. Чтобы выполнить примеры в этом подразделе, следует также понимать принципы работы Язык XAML (Extensible Application Markup Language) и знать, как создаются приложения WPF.
Что такое свойство зависимостей?
То, что в противном случае будет свойством среда CLR (common language runtime) можно настроить для поддержки стилизации, привязки данных, наследования, анимаций и значений по умолчанию, посредством реализации в качестве свойства зависимостей. Свойства зависимостей являются свойствами, которые зарегистрированы системой свойств WPF посредством вызова метода Register (или RegisterReadOnly) и зарезервированы полем идентификатора DependencyProperty. Свойства зависимостей могут использоваться только типами DependencyObject, но DependencyObject расположен довольно высоко в иерархии классов WPF, поэтому большинство классов, доступных в WPF, могут поддерживать свойства зависимостей. Дополнительные сведения о свойствах зависимостей и некоторых терминах и соглашениях, используемых для их описания в этом Пакет SDK, см. в разделе Общие сведения о свойствах зависимости.
Примеры свойств зависимостей
Примерами свойств зависимостей, реализуемых в классах WPF, являются, например, свойство Background, свойство Width, и свойство Text. Каждое свойство зависимостей, предоставляемое классом, содержит соответствующее открытое статическое поле типа DependencyProperty, предоставляемое для этого же класса. Это является идентификатором для свойства зависимости. Идентификатор именуется в соответствии со следующим соглашением: к имени свойства зависимостей добавляется строка Property. Например, соответствующим полем идентификатора DependencyProperty для свойства Background является BackgroundProperty. После регистрации идентификатор хранит информацию о свойстве зависимостей, а затем используется для других операций, включающих свойство зависимостей, например при вызове SetValue.
Как отмечалось в разделе Общие сведения о свойствах зависимости, все свойства зависимостей в WPF (за исключением большинства вложенных свойств) также являются свойствами CLR из-за реализации программы-оболочки. Следовательно из кода можно получить или установить свойства зависимости путем вызова методов доступа CLR, определяющих программы-оболочки таким же образом, что и использование других свойств CLR. Как потребители установленных свойств зависимостей, разработчики обычно не используют методы DependencyObjectGetValue и SetValue, являющиеся точкой подключения для основной системы свойств. Существующая реализация свойств CLR уже вызывает GetValue и SetValue в реализациях программы-оболочки get и set свойства, надлежащим образом используя поле идентификатора. Если разработчик самостоятельно реализует пользовательское свойство зависимости, программа-оболочка будет определяться аналогичным образом.
Когда следует реализовывать свойство зависимостей?
При реализации свойства в классе (если класс является производным от DependencyObject) разработчик может зарезервировать свое свойство с идентификатором DependencyProperty, таким образом сделав это свойство свойством зависимости. Свойство не всегда удобно делать свойством зависимости, это будет зависеть от потребностей сценария. Иногда подходят обычные способы резервирования свойства с закрытым полем. Тем не менее, свойство всегда следует реализовывать как свойство зависимостей в том случае, когда свойство должно поддерживать одну или несколько следующих возможностей WPF:
Свойство должно устанавливаться в стиле. Дополнительные сведения см. в разделе Стилизация и использование шаблонов.
Свойство должно поддерживать привязку данных. Дополнительные сведения о привязке данных свойств зависимостей содержатся в разделе Как привязать свойства двух элементов управления.
Свойство должно устанавливаться ссылкой на динамический ресурс. Дополнительные сведения см. в разделе Общие сведения о ресурсах.
Необходимо автоматическое наследование значения свойства из родительского элемента в дереве элементов. В этом случае следует зарегистрировать с помощью метода RegisterAttached, даже при создании программы-оболочки свойства для доступа к CLR. Дополнительные сведения см. в разделе Наследование значения свойства.
Свойство должно быть анимируемым. Дополнительные сведения см. в разделе Общие сведения об эффектах анимации.
Система свойств должна уведомлять разработчика, когда предыдущее значение свойства изменяется в результате действий, производимых системой свойств, средой или пользователем, а также в результате чтения и использования стилей. Используя метаданные свойства, свойство сможет задать метод обратного вызова, который будет вызываться каждый раз, когда система свойств определит, что значение свойства было окончательно изменено. Связанным понятием является приведение значения свойства. Дополнительные сведения см. в разделе Проверка и обратные вызовы свойства зависимостей.
Необходимо использование установленных соглашений о метаданных, которые также используются процессами WPF, например, уведомлением о том, должно ли изменение значения свойства удовлетворять системе макета, чтобы представлять визуализации элемента. Также в том случае, если необходима возможность использования переопределений метаданных таким образом, чтобы производные классы могли изменять основанные на метаданных характеристики, такие как значение по умолчанию.
Свойства пользовательского элемента управления должны получать поддержку Visual Studio 2008WPF (конструктор), например редактирование окна Свойства. Дополнительные сведения см. в разделе Общие сведения о разработке управления.
При рассмотрении этих сценариев также следует учитывать, можно ли выполнить сценарий, переопределяя метаданные существующего свойства зависимостей, вместо реализации совершенно нового свойства. Практичность переопределения метаданных зависит от сценария и от того, насколько точно он совпадает с реализацией существующих свойств зависимостей и классов WPF. Дополнительные сведения о переопределении метаданных в существующих свойствах см. в разделе Метаданные свойства зависимости.
Контрольный список для определения свойства зависимостей
Определение свойства зависимости состоит из четырех различных понятий. Эти понятия не обязательно являются строгими процедурными этапами, так как некоторые из объединяются в одну строку кода в реализации:
(Необязательно) Создание метаданных свойства для свойства зависимости.
Регистрация имени свойства с помощью системы свойства, путем задания типа владельца и типа значения свойства. Также необходимо задать метаданные свойства (при их использовании).
Определение идентификатора DependencyProperty как поле public static readonly в типе владельца.
Определение свойства CLR программы-оболочки, имя которой соответствует имени свойства зависимостей. Реализация методов доступа get и set свойства программы-оболочки CLR для соединения со свойством зависимости, которое резервирует его.
Регистрация свойства с помощью системы свойств
Чтобы свойство было свойством зависимостей, необходимо зарегистрировать это свойство в таблице, поддерживаемой системой свойств, и предоставить ему уникальный идентификатор, используемый в качестве квалификатора для последующих операций системы свойств. Эти операции могут быть внутренними операциями или системой свойств API-интерфейсы, обращающейся к собственному коду. Чтобы зарегистрировать свойство, нужно вызвать метод Register в теле класса (внутри класса, но вне определений любого из членов). Вызов метода Register также предоставляет поле идентификатора в качестве возвращаемого значения. Причина того, что вызов Register выполняется вне определения других участников, заключается в том, что это возвращаемое значение используется для назначения и создания поля public static readonly типа DependencyProperty как части класса. Это поле становится идентификатором для данного свойства зависимостей.
public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
"AquariumGraphic",
typeof(Uri),
typeof(AquariumObject),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnUriChanged)
)
);
Соглашения об имени свойств зависимостей
Существуют установленные соглашения об именах, касающиеся свойств зависимостей. Эти соглашения необходимо соблюдать во всех случаях за редким исключением.
Само свойство зависимости будет иметь базовое имя — в следующем примере «AquariumGraphic». Это имя предоставляется в качестве первого параметра Register. Это имя должно быть уникальным внутри каждого регистрирующего типа. Считается, что свойства зависимости, наследуемые через базовые типы, уже являются частью регистрирующего типа; имена наследуемых свойств не могут быть зарегистрированы повторно. Однако, существует метод для добавления класса как владельца свойства зависимостей, даже если свойство зависимостей не наследуется. Подробные сведения содержатся в разделе Метаданные свойства зависимости.
При создании поля идентификатора назовите это поле по имени зарегистрированного свойства, прибавив суффикс Property. Это поле является идентификатором для свойства зависимостей, и позднее будет использовано как ввод для вызовов SetValue и GetValue, которые будут осуществляться в программе-оболочке, посредством любого другого доступа к коду в свойстве через собственный код, посредством любого внешнего разрешенного доступа к коду, посредством системой свойств или через обработчики XAML.
Примечание. |
---|
Определение свойства зависимости в теле класса является обычной реализацией, но также возможно определить свойство зависимости в статическом конструкторе класса. Этот подход может пригодиться в том случае, если для инициализации свойства зависимостей понадобится несколько строк кода. |
Реализация «программы-оболочки»
Реализация программы-оболочки должна вызывать GetValue в реализации get, и SetValue в реализации set (исходный вызов регистрации и поле также показаны здесь для ясности).
Во всех случаях, за редким исключением, реализации программы-оболочки должны выполнять только действия GetValue и SetValue, соответственно. Причина этого обсуждается в разделе Загрузка кода XAML и свойства зависимостей.
Все существующие открытые свойства зависимости, предоставляемые на классах WPF, используют эту простую модель реализации программы-оболочки. Основная трудность того, как работают свойства зависимости, заключается либо в поведении системы свойств, либо в реализации других основных понятий, таких как приведение типа данных или обратный вызов свойства через метаданные свойства.
public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
"AquariumGraphic",
typeof(Uri),
typeof(AquariumObject),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnUriChanged)
)
);
public Uri AquariumGraphic
{
get { return (Uri)GetValue(AquariumGraphicProperty); }
set { SetValue(AquariumGraphicProperty, value); }
}
Согласно правилам, имя упаковщика свойства должно совпадать с именем, выбранным и заданным как первый параметр вызова Register, регистрирующего свойство. Если свойство не следует правилу, это не обязательно отключает все возможные применения, но приведет к следующим существенным проблемам:
Не будут работать определенные аспекты стилей и шаблонов.
Большинство инструментов и конструкторов должны полагаться на соглашения об именах для правильной сериализации XAML или для предоставления помощи среды конструктора на уровне каждого свойства.
Текущая реализация загрузчика WPF XAML полностью обходит программы-оболочки и основывается на соглашении об именах при обработке значений атрибута. Дополнительные сведения см. в разделе Загрузка кода XAML и свойства зависимостей.
Метаданные свойства для нового свойства зависимости
При регистрации свойства зависимости регистрация с помощью системы свойств создает объект метаданных, который хранит характеристики свойства. Большинство этих характеристик имеют значения по умолчанию, которые установлены, если свойство зарегистрированно с простыми подписями Register. Другие подписи Register позволяют указывать метаданные, требуемые при регистрации свойства. Наиболее общие метаданные, заданные для свойств зависимости, можно получить, задав им значение по умолчанию, применяемое на новых экземплярах, которые использует свойство.
Если создается свойство зависимостей, размещающееся на производном классе FrameworkElement, то можно использовать более специализированный класс метаданных FrameworkPropertyMetadata вместо базового класса PropertyMetadata. Конструктор для класса FrameworkPropertyMetadata имеет несколько подписей, где можно указать различные сочетания характеристик метаданных. Если требуется указать только значение по умолчанию, следует использовать подпись, принимающую один параметр типа Object. Передайте этот параметр объекта в качестве типоспецифического значения по умолчанию для свойства (предоставленное значение по умолчанию должно быть типом, предоставленным как параметр propertyType в вызове Register).
Для FrameworkPropertyMetadata можно также указать флаги параметра метаданных для свойства. После регистрации эти флаги преобразуются в дискретные свойства в метаданных свойства и используются для связи определенных условных операторов с другими процессами, такими как обработчик макета.
Задание соответствующих флагов метаданных
Если свойство (или изменения в его значении) влияет на пользовательский интерфейс и, в частности, влияет на то, как система макета будет изменять размер или визуализировать элемент на странице, задайте один или несколько следующих флагов: AffectsMeasure, AffectsArrange и AffectsRender.
AffectsMeasure показывает, что изменение этого свойства требует изменения для визуализации Пользовательский интерфейс, где содержащийся объект может потребовать большего или меньшего пространства внутри родителя. Например, этот флаг должен быть установлен у свойства Width.
AffectsArrange показывает, что изменение этого свойства требует изменения визуализации Пользовательский интерфейс, обычно не требующей изменения в назначенном пространстве, но не указывает на изменение положения в пространстве. Например, этот флаг должен быть установлен у свойства Alignment.
AffectsRender показывает, что произошли некоторые другие изменения, которые не повлияют на макет и масштаб, но требуют повторной визуализации. Примером будет свойство, изменяющее цвет существующего элемента, такое как Background.
Эти флаги часто используются в метаданных в качестве протокола для собственных реализаций переопределения системы свойств или обратных вызовов макета. Например, можно иметь обратный вызов OnPropertyChanged, который будет вызывать InvalidateArrange, если любое свойство экземпляра уведомляет об изменении значения и имеет AffectsArrange в качестве true в своих метаданных.
Некоторые свойства могут влиять на характеристики визуализации включенного родительского элемента (т.е. уменьшать или увеличивать требуемый размер) различными способами, упомянутыми ранее. Примером этого является свойство MinOrphanLines, используемое в модели потокового документа, где изменения этого свойства могут изменять общую визуализацию документа нефиксированного формата, содержащего абзац. Используйте AffectsParentArrange или AffectsParentMeasure для определения похожих случаев в своих свойствах.
По умолчанию свойства зависимостей поддерживают привязку данных. Можно специально отключить привязку данных для случаев, когда реалистичного сценария для привязки данных не существует или когда быстродействие в привязке данных для больших объектов становится проблемой.
Привязка данных Mode для свойств зависимости по умолчанию устанавливается равной OneWay. Всегда можно изменить привязку так, что она будет равна TwoWay для каждого экземпляра привязки. Дополнительные сведения см. в разделе Практическое руководство. Указание направления привязки. Как автор свойства зависимости, вы можете выбирать вариант, когда свойство использует по умолчанию режим привязки TwoWay. Примером существующего свойства зависимостей является MenuItem.IsSubmenuOpen; сценарий для этого свойства заключается в том, что логика установки IsSubmenuOpen и компоновка из MenuItem взаимодействуют со стилями темы по умолчанию. Логика свойства IsSubmenuOpen изначально использует привязку данных для поддержания состояния свойства в соответствии с другими свойствами состояния и вызовами методов. Другим примером свойства, которое по умолчанию связывает TwoWay, является TextBox.Text.
Можно также включить наследование свойства в пользовательском свойстве зависимости, установив флаг Inherits. Наследование свойства полезно для сценария, в котором родительские и дочерние элементы одновременно имеют одно свойство, что может помешать дочерним элементам установить то же значение данного свойства, что и значение родительского элемента. Примером наследуемого свойства является DataContext, которое используется для привязки операций, чтобы включить важный сценарий «основной/подробности» для представления данных. Если сделано DataContext наследуемым, то любые дочерние элементы также наследуют контекст данных. По причине наследования значения свойства можно указать контекст данных на странице или в корне приложения, что освобождает от необходимости повторного указания наследования для привязки во всех возможных дочерних элементах. DataContext также представляет собой хороший пример для демонстрации того, что наследование переопределяет значение по умолчанию, но может всегда быть локально установлено для любого конкретного дочернего элемента. Дополнительные сведения см. в разделе Практическое руководство. Использование шаблона "основной-подчиненный" с иерархическими данными. Наследование значения свойства снижает быстродействие, а, следовательно, должно использоваться минимально. Дополнительные сведения см. в разделе Наследование значения свойства.
Установленный флаг Journal показывает, что свойство зависимостей определено или используется службами навигации по журналированию. Примером является свойство SelectedIndex. любой элемент, выбранный в элементе управления выбора, должен сохраняться при навигации по истории журналирования.
Свойства зависимости «только для чтения»
Можно определить свойство зависимости, которое является свойством «только для чтения». Однако, ситуации, в которых свойство может задаваться как свойство «только для чтения», немного отличаются, как и процедура их регистрации системой свойств и предоставление идентификатора. Дополнительные сведения см. в разделе Свойства зависимости "только для чтения".
Свойства зависимостей типа коллекции
Следует учитывать некоторые дополнительные проблемы при реализации свойства зависимостей типа коллекции. Дополнительные сведения см. в разделе Свойства зависимостей типа коллекция.
Вопросы безопасности свойств зависимостей
Свойства зависимостей должны объявляться как открытые свойства. Поля идентификатора свойства зависимостей должны объявляться как открытые статические поля. Даже при попытке объявить другие уровни доступа (например, защищенный), свойство зависимости всегда может быть доступно через идентификатор в сочетании с системой свойств API-интерфейсы. Даже защищенное поле идентификатора потенциально доступно из-за метаданных, уведомляющих об определении значений API-интерфейсы, являющихся частью системы свойств, например, LocalValueEnumerator. Дополнительные сведения см. в разделе Безопасность свойства зависимости.
Свойства зависимостей и конструкторы класса
Общий принцип в программировании управляемого кода (который часто применяется с помощью инструментов анализа кода, таких как FxCop) заключается в том, что конструкторы класса не должны вызывать виртуальные методы. Это вызвано тем, что конструкторы могут быть вызваны в качестве основной инициализации конструктора производного класса, а ввод виртуального метода в конструктор может произойти на незавершенной стадии инициализации конструируемого экземпляра объекта. При наследовании из любого класса, который уже является производным от DependencyObject, следует помнить, что сама система свойств вызывает методы и предоставляют виртуальные методы для внутренних целей. Эти виртуальные методы являются частью служб системы свойств WPF. Переопределение методов позволяет производным классам участвовать в определении значения. Чтобы избежать возможных проблем с инициализацией времени выполнения, не следует устанавливать значения свойства зависимостей в конструкторах классов за исключениям того случая, когда используется очень специфичный шаблон конструктора. Дополнительные сведения см. в разделе Шаблоны безопасного конструктора для DependencyObjects.
См. также
Основные понятия
Общие сведения о свойствах зависимости
Метаданные свойства зависимости
Общие сведения о разработке управления
Свойства зависимостей типа коллекция
Безопасность свойства зависимости