Общие сведения о свойствах зависимостей

В этом разделе объясняется система свойств зависимостей, доступная при создании приложений среды выполнения Windows на C++, C# и Visual Basic с использованием XAML-определений для пользовательского интерфейса.

Что такое свойство зависимостей?

Свойство зависимостей представляет собой специализированный тип свойства. В частности, это свойство, значение которого отслеживается и изменяется посредством специальной системы свойств, которая является частью среды выполнения Windows.

Для поддержки свойства зависимостей объект, который определяет свойство, должен быть DependencyObject (то есть представлять собой класс, имеющий в своем наследовании базовый класс DependencyObject). Многие типы, используемые для определения пользовательского интерфейса для приложения UWP с XAML, будут подклассом DependencyObject и будут поддерживать свойства зависимостей. Однако любой тип, который поступает из пространства имен среды выполнения Windows, но не содержит в имени «XAML», не будет поддерживать свойства зависимостей. Свойства таких типов являются обычными свойствами и не будут зависеть от системы свойств.

Задачей свойств зависимостей является предоставление систематического способа расчета значения свойства на основании других входных данных (других свойств, событий и состояний, которые возникают в вашем приложении во время выполнения). Входными данными могут быть:

  • Внешние входные данные, например настройка пользователя
  • Механизмы оперативного определения свойств, например привязка данных, анимация и раскадровка
  • Шаблоны многократного использования, например ресурсы и стили
  • Значения, имеющие родительско-дочернюю связь с другими элементами дерева объектов

Свойство зависимостей представляет или поддерживает особую функцию модели программирования для определения приложений среды выполнения Windows, которые используют XAML для описания пользовательского интерфейса и расширения компонентов C#, Microsoft Visual Basic или Visual C++ (C++/CX) для создания программного кода. Эти функции включают перечисленные ниже.

  • привязка данных,
  • стили.
  • Раскадрованные анимации
  • Поведение «PropertyChanged»; свойство зависимостей может быть реализовано для предоставления обратных вызовов, распространяющих изменения на другие свойства зависимостей
  • Использование значения по умолчанию, которое поступает из метаданных свойства
  • Общее системное средство для работы со свойствами, например ClearValue, и поиск по метаданным

Свойства зависимостей и свойства среды выполнения Windows

Свойства зависимостей расширяют функциональные возможности базовых свойств среды выполнения Windows, предоставляя глобальное внутреннее хранилище для резервных копий свойств зависимостей в приложении во время выполнения. Свойства зависимостей — это альтернативный способ стандартного резервирования свойства при помощи частного поля (частного в классе определения свойств). Это внутреннее хранилище свойств можно представить как набор идентификаторов свойств и их значений, существующих для каждого отдельного объекта (при условии, что он является DependencyObject). Каждое свойство определяется по экземпляру класса DependencyProperty, а не по имени. Однако система свойств чаще всего скрывает эту подробность реализации. Обычно вы можете получить доступ к свойствам зависимостей просто по имени: программному имени свойства в используемом языке программирования или имени атрибута, если дело касается XAML-кода.

Базовым типом, который образует основу для системы свойств зависимостей, является DependencyObject. Тип DependencyObject определяет методы, которые могут обращаться к свойству зависимости, и экземпляры производного класса DependencyObject с внутренней поддержкой хранилища свойств, о котором мы говорили ранее.

Объяснение терминов, используемых в этом документе при обсуждении свойств зависимостей:

Термин Описание
Свойство зависимостей Свойство, которое существует в идентификаторе DependencyProperty (см. ниже). Этот идентификатор обычно доступен как статический член определяющего производного класса DependencyObject.
Идентификатор свойства зависимостей Постоянное значение для определения свойства. Как правило, оно общедоступно и доступно только для чтения.
Оболочка свойств Вызываемые реализации методов get и set для свойств в среде выполнения Windows либо зависящая от языка реализация исходного определения. Реализация оболочки свойства get вызывает метод GetValue и передает ему соответствующий идентификатор свойства зависимостей.

Оболочка для свойства применяется не только для удобства вызывающей стороны, она также предоставляет свойство зависимостей любому процессу, средству или проекции, которые используют определения среды выполнения Windows для свойств.

В следующем примере определяется пользовательское свойство зависимости, определенное для C#, и показывается связь идентификатора свойства зависимости с оболочкой свойства.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  "Label",
  typeof(string),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);


public string Label
{
    get { return (string)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}

Примечание

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

Приоритет значения свойств зависимостей

При получении значения свойства зависимостей вы получаете значение, которое было определено для этого свойства через любое из входных значений, относящихся к системе свойств среды выполнения Windows. Существует приоритет значений свойств зависимостей, который позволяет системе свойств среды выполнения Windows рассчитывать значения предсказуемым способом. Важно, чтобы вы тоже имели представление об основном порядке приоритетов. В противном случае может возникнуть ситуация, в которой вы пытаетесь задать свойство на одном уровне приоритета, но что-то другое (система, вызовы третьей стороны или какая-то часть вашего собственного кода) задает его на другом уровне, и вам будет непросто понять, какое значение свойства используется и откуда оно взялось.

Например, стили и шаблоны являются общей стартовой точкой для задания значений свойств и тем самым — для задания внешнего вида элементов управления. Но возможно, что для какого-то отдельного экземпляра элемента управления вам захочется заменить значение в шаблоне другим значением, например выбрать другой цвет фона или другую строку текста в качестве содержимого. В системе свойств среды выполнения Windows локально заданные значения имеют более высокий приоритет, чем значения из стилей и шаблонов. Благодаря этому можно переопределять специфические для приложений значения шаблона, чтобы вы могли по-своему использовать элементы управления в пользовательском интерфейсе приложения.

Список приоритетов свойства зависимостей

Ниже приведен точный порядок, который используется системой свойств при присвоении значения свойству зависимостей во время выполнения. Сначала указаны элементы с наивысшим приоритетом. Более подробные пояснения приведены сразу за списком.

  1. Анимированные значения: активные анимации, анимации визуального состояния или анимации с поведением HoldEnd. Чтобы получить практический результат, примененная к свойству анимация должна иметь приоритет перед основным (неанимированным) значением, даже если это значение было задано локально.
  2. Локальное значение: локальное значение может быть задано через удобную оболочку свойства, что равнозначно заданию атрибута или элемента свойства в XAML либо путем вызова метода SetValue с использованием свойства конкретного экземпляра. Если локальное значение задается с помощью привязки или статического ресурса, то каждое из этих действий имеет приоритет, равный приоритету локального значения, поэтому ссылки на ресурсы или привязки удаляются, если задается новое локальное значение.
  3. Шаблонные свойства: элемент имеет такие свойства, если был создан как часть шаблона (ControlTemplate или DataTemplate).
  4. Методы установки значений для стиля: значения из класса Setter в стилях из ресурсов страницы или приложения.
  5. Значение по умолчанию: свойство зависимостей может иметь значение по умолчанию, определяемое как часть его метаданных.

Шаблонные свойства

Шаблонные свойства как элемент списка приоритетов не применяются к тем свойствам элемента, которые были объявлены непосредственно в XAML-разметке страницы. Понятие шаблонных свойств существует только для объектов, которые создаются, когда среда выполнения Windows применяет шаблон XAML к элементу пользовательского интерфейса и таким образом определяет его визуальные свойства.

Все свойства, заданные в шаблоне элемента управления, имеют какие-либо значения. Эти значения практически являются расширенным набором значений по умолчанию для данного элемента управления и часто ассоциируются со значениями, которые впоследствии можно отменить, установив значения свойств напрямую. Таким образом, значения, заданные в шаблоне, должны отличаться от настоящих локальных значений, чтобы их можно было перезаписывать новыми локальными значениями.

Примечание

В некоторых случаях шаблон может переопределять даже локальные значения, если он не предоставил ссылки расширения разметки {TemplateBinding} для свойств, которые должны задаваться на экземплярах. Это обычно делается только в тех случаях, когда свойство в действительности не предназначено для задания на экземплярах. Например, если оно существенно только для визуальных эффектов и поведения шаблона и не имеет значения для функционирования или логики среды выполнения элемента управления, использующего этот шаблон.

Привязки и приоритет

Операции привязки имеют приоритет, соответствующий области, в которой они используются. Например, расширение разметки {Binding}, примененное к локальному значению, действует как локальное значение, а расширение разметки {TemplateBinding} для метода задания значения свойства в стиле применяется так же, как метод задания значений для стиля. Поскольку привязки должны ожидать времени выполнения, чтобы получать значения из источников данных, процесс определения приоритета значений свойств для любого свойства также распространяется на время выполнения приложения.

Привязки не только имеют такой же приоритет, как локальные значения. Они на самом деле являются локальными значениями, в которых привязка является заполнителем для отложенного значения. Если вместо значения свойства определена привязка, то любое локальное значение, заданное во время выполнения, полностью заменяет привязку. Аналогичным образом, если вы вызываете SetBinding, чтобы определить привязку, которая вступает в действие только во время выполнения, то вы заменяете любое локальное значение, которое могло быть применено в XAML или в ранее выполненном коде.

Раскадрованные анимации и базовое значение

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

Для анимированного свойства базовое значение может влиять на поведение анимации, если анимация не указывает явным образом From и To либо если анимация возвращает базовое значение свойства после своего завершения. В подобных случаях после завершения анимации снова используется остальной набор приоритетов.

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

К одному свойству можно применить сразу несколько анимаций. Для каждой из этих анимаций может быть определена замена базовых значений, которые поступают из различных мест, в соответствии с приоритетом значений. Однако эти анимации будут выполняться одновременно во время выполнения. Часто это означает, что они будут комбинировать значения, поскольку каждая из анимаций имеет равное влияние на значение. Это зависит от того, как именно определяются анимации, и от типа анимируемого значения.

Подробнее: Раскадрованные анимации.

Значения по умолчанию

Об установке значения по умолчанию для свойства зависимостей при помощи значения PropertyMetadata более подробно рассказывается в разделе Пользовательские свойства зависимостей.

Свойства зависимостей имеют значения по умолчанию, даже если эти значения по умолчанию не были явным образом определены в метаданных свойства. Если они не были изменены в метаданных, значением по умолчанию для свойства зависимостей среды выполнения Windows обычно является одно из следующих.

  • Свойство, которое использует объект времени выполнения или базовый тип Object (ссылочный тип), имеет значение по умолчанию null. Например, свойство DataContext имеет значение null, если оно не задано специально или не унаследовано.
  • Свойство, использующее базовое значение, например числовое или логическое (тип значения), использует ожидаемое значение по умолчанию для этого значения. Например, 0 для целых чисел и чисел с плавающей точкой и false для логических значений.
  • Свойство, которое использует структуру среды выполнения Windows, имеет значение по умолчанию, которое получается в результате вызова неявного конструктора по умолчанию для этой структуры. Конструктор использует значения по умолчанию для каждого из полей базового значения этой структуры. Например, по умолчанию значение Point инициализируется с нулевыми значениями X и Y.
  • Свойство, использующее перечисление, имеет в качестве значения по умолчанию первый определенный элемент в этом перечислении. Значение по умолчанию для конкретного перечисления см. в справочнике.
  • Свойство, использующее строковое значение (System.String для .NET, Platform::String для C++/CX) имеет в качестве значения по умолчанию пустую строку ("").
  • Свойства коллекций обычно не реализуются как свойства зависимостей по причинам, которые будут упомянуты далее в этом разделе. Но если вы реализуете пользовательское свойство коллекции и хотите, чтобы оно было свойством зависимостей, избегайте формирования незапланированного одноэлементного объекта, о котором говорится в конце раздела Пользовательские свойства зависимостей.

Функциональные возможности свойства, предоставленные свойством зависимостей

привязка данных,

Свойство зависимостей может получать значение путем применения привязки данных. Привязка данных использует синтаксис расширения разметки {Binding} в XAML, расширение разметки {x:Bind} или класс Binding в программном коде. Для свойства с привязкой данных определение значения свойства откладывается до времени выполнения. В это время значение поступает из источника данных. Роль, которую здесь играет система свойств зависимостей, позволяет использовать заполнители для таких операций, как загрузка XAML, когда значение еще не известно, а затем поставлять значение во время выполнения при взаимодействии с модулем привязки данных среды выполнения Windows.

В следующем примере задается значение Text для элемента TextBlock с помощью привязки в XAML. Для привязки используется наследуемый контекст данных и источник данных объекта. (Они не показаны в данном сокращенном примере; полностью пример вместе с контекстом и источником показан в разделе Подробно о привязке данных.)

<Canvas>
  <TextBlock Text="{Binding Team.TeamName}"/>
</Canvas>

Привязку можно установить не только при помощи XAML, но и при помощи программного кода. См. SetBinding.

Примечание

Привязки, подобные этим, рассматриваются как локальные значения в целях приоритета значений свойств зависимостей. Если вы зададите другое локальное значение для свойства, которое изначально содержало значение Binding, вы полностью переопределите привязку, а не только значение привязки во время выполнения. Привязки {x:Bind} реализуются с помощью сформированного кода, устанавливающего локальное значение для свойства. Если установить локальное значение для свойства, использующего {x:Bind}, это значение будет заменено при следующей оценке привязки, например при наблюдении изменения свойства в исходном объекте.

Источники привязки, цели привязки, роль FrameworkElement

Используемое в качестве источника привязки свойство не обязательно должно быть свойством зависимостей. Как правило, источником привязки может быть любое свойство, хотя это будет зависеть от языка программирования: в каждом из них имеются свои пограничные случаи. Но для того, чтобы быть целевым объектом расширения разметки {Binding} или Binding, свойство обязательно должно быть свойством зависимостей. У расширения разметки {x:Bind} нет этого требования, так как оно использует сформированный код для применения значений привязки.

Если вы создаете привязку в программном коде, учтите, что API SetBinding определен только для класса FrameworkElement. Тем не менее, вы можете создать определение привязки при помощи BindingOperations и таким образом сослаться на любое свойство DependencyObject.

Помните, что и для разметки XAML, и для кода свойство DataContext является свойством FrameworkElement. Используя форму родительского-дочернего наследования свойств (обычно устанавливаемого в разметке XAML), система привязок может разрешать DataContext, существующий в родительском элементе. Такое наследование может давать результат, даже если дочерний объект (являющийся целевым свойством) не представляет собой FrameworkElement и таким образом не содержит собственное значение DataContext. Однако наследуемый родительский элемент должен представлять собой FrameworkElement, чтобы для родительского элемента можно было определить и содержать DataContext. Альтернативный вариант: следует определить привязку таким образом, чтобы она могла действовать со значением null для DataContext.

Установление привязки — не единственный момент, необходимый для большинства сценариев привязки данных. Чтобы односторонняя или двусторонняя привязка была эффективной, свойство-источник должно поддерживать уведомления об изменениях, которые распространяются на систему привязки и тем самым — на целевой объект. В случае с пользовательскими источниками привязки это означает, что свойство должно быть свойством зависимостей, либо объект должен поддерживать INotifyPropertyChanged. Коллекции должны поддерживать интерфейс INotifyCollectionChanged. Некоторые классы в своей реализации поддерживают эти интерфейсы, поэтому их можно использовать в качестве базовых классов для сценариев привязки данных. Примером такого класса является ObservableCollection<T>. Дополнительные сведения о привязке данных и об отношении привязок к системе свойств см. в разделе Подробно о привязке данных.

Примечание

Перечисленные ниже типы поддерживают Microsoft .NET источники данных. Источники данных C++/CX используют различные интерфейсы для уведомлений об изменениях или наблюдаемого поведения. См. Подробно о привязке данных.

Стили и шаблоны

Стили и шаблоны — два сценария, для которых свойства определяются как свойства зависимостей. Стили подходят для задания свойств, определяющих пользовательский интерфейс приложения. Стили определены в XAML в качестве ресурсов — либо как запись в коллекции Resources, либо в отдельных XAML-файлах, например в словарях ресурсов темы. Стили взаимодействуют с системой свойств, так как содержат методы установки значений для свойств. Самое важное свойство — свойство Control.Template элемента Control. Оно в значительной степени определяет внешний вид и визуальное состояние Control. Подробнее о стилях и ряд примеров разметки XAML, в которых определяется Style и используются методы установки значений, см. в статье Настройка стиля элементов управления.

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

Раскадрованные анимации

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

Чтобы к свойству можно было применить анимацию, целевое свойство анимации должно быть свойством зависимостей. Кроме того, для анимации необходимо, чтобы тип значения целевого свойства поддерживался одним из существующих типов анимации, производных от класса Timeline. Значения Color, Double и Point можно анимировать с помощью методов интерполяции или опорных кадров. Большинство других значений можно анимировать с помощью дискретных опорных кадров Object.

При применении и выполнении анимации приоритет анимированного значения при выполнении выше, чем у любого (например, локально заданного) значения, которое данное свойство имеет в противном случае. Кроме того, анимации имеют необязательное поведение HoldEnd, благодаря чему могут применяться к значениям свойств, даже если внешне все выглядит так, как будто анимация остановлена.

Принцип конечного автомата реализуется путем использования раскадрованных анимаций в составе модели состояний VisualStateManager для элементов управления. Подробнее о раскадрованных анимациях см. в статье Раскадрованные анимации. Подробнее о VisualStateManager и определении визуальных состояний элементов управления см. в статье Раскадрованные анимации для визуальных состояний или в статье Шаблоны элементов управления.

Поведение при изменении свойства

Поведение при изменении свойства является причиной включения слова "зависимости" в термин "свойство зависимостей". Сохранение корректных значений свойства в условиях, когда на значение этого свойства может влиять другое свойство, — сложная проблема разработки на многих платформах. В системе свойств среды выполнения Windows в каждом свойстве зависимостей можно определить обратный вызов, который будет выполняться при каждом изменении его значения. Этот обратный вызов может использоваться для уведомления или изменения значений связанных свойств, обычно синхронным методом. Для многих существующих свойств зависимостей определено поведение при изменении свойств. Вы также можете добавить аналогичное поведение обратного вызова для пользовательского свойства зависимостей и реализовать собственные обратные вызовы при изменении свойств. См. пример в статье Пользовательские свойства зависимостей.

В Windows 10 появился метод RegisterPropertyChangedCallback. С его помощью код приложения можно зарегистрировать для получения уведомлений об изменениях при изменении указанного свойства зависимостей экземпляра DependencyObject.

Значение по умолчанию и метод ClearValue

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

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

DependencyObject и потоки

Все экземпляры DependencyObject должны создаваться в потоке пользовательского интерфейса, связанном с текущей версией Window, которая отображается приложением среды выполнения Windows. Хотя каждый DependencyObject должен создаваться в основном потоке пользовательского интерфейса, доступ к объектам может обеспечиваться при помощи диспетчерских ссылок из других потоков путем обращения к свойству Dispatcher. Затем можно вызвать методы, например RunAsync в объекте CoreDispatcher, и выполнить ваш код в потоке пользовательского интерфейса с учетом правил ограничений для потоков.

Потоковые аспекты DependencyObject имеют отношение к данному вопросу, поскольку фактически только код, выполняемый в потоке пользовательского интерфейса, может изменить или хотя бы прочитать значение свойства зависимостей. Проблем с потоками обычно можно избежать в стандартном коде пользовательского интерфейса, который обеспечивает корректное использование шаблонов async и фоновых рабочих потоков. Как правило, проблемы, связанные с потоками DependencyObject, возникают при определении ваших собственных типов DependencyObject и попытке использовать их для формирования источников данных либо в других сценариях, где использование DependencyObject может быть неуместно.

Теоретический материал