Пользовательские свойства зависимостей

В этом разделе описываются основания для создания настраиваемых свойств зависимостей для приложений Windows Presentation Foundation (WPF), а также этапы и некоторые варианты реализации, которые могут повысить производительность, удобство использования и универсальность свойств.

Предварительные требования

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

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

Чтобы обеспечить поддержку стилей, привязки данных, наследования, анимации и значений по умолчанию, можно включить свойство среды CLR, которое в противном случае будет реализовано как свойство зависимостей. Свойства зависимостей — это свойства, которые регистрируются в WPF системе свойств путем вызова Register метода (или RegisterReadOnly ) и поддерживаются DependencyProperty полем идентификатора. Свойства зависимостей могут использоваться только DependencyObject типами, но DependencyObject довольно высоки в WPF иерархии классов, поэтому большинство классов, доступных в, WPF может поддерживать свойства зависимостей. Дополнительные сведения о свойствах зависимостей и некоторых терминологиях и соглашениях, используемых для их описания в этом пакете SDK, см. в разделе Общие сведения о свойствах зависимостей.

Примеры свойств зависимостей

Примерами свойств зависимостей, реализованных в WPF классах, являются Background свойство, Width свойство и Text свойство, среди многих других. Каждое свойство зависимости, предоставляемое классом, имеет соответствующее открытое статическое поле типа DependencyProperty , предоставляемое для этого же класса. Это идентификатор для свойства зависимостей. Идентификатор именуется по следующему соглашению: имя свойства зависимостей, за которым следует строка Property. Например, соответствующее DependencyProperty поле идентификатора для Background свойства имеет значение BackgroundProperty . Идентификатор хранит сведения о свойстве зависимостей в том виде, в котором оно было зарегистрировано, а идентификатор затем используется позже для других операций, использующих свойство зависимостей, например вызов SetValue .

Как упоминалось в разделе Общие сведения о свойствах зависимостей, все свойства зависимостей в WPF (за исключением большинства присоединенных свойств) также являются свойствами CLR из-за реализации "оболочки". Таким образом, из кода можно получить или задать свойства зависимостей, вызвав методы доступа CLR, которые определяют оболочки так же, как и другие свойства среды CLR. Как потребитель установленных свойств зависимостей, обычно не используются DependencyObject методы GetValue и SetValue , которые являются точкой подключения к базовой системе свойств. Вместо этого существующая реализация свойств CLR будет уже вызвана GetValue и SetValue в get set реализации оболочки и свойства, используя поле идентификатора соответствующим образом. При собственной реализации настраиваемого свойства зависимостей вы будете определять оболочку аналогичным образом.

Почему требуется реализовывать свойство зависимостей?

При реализации свойства в классе при условии, что класс является производным от DependencyObject , можно вернуть свойство с DependencyProperty идентификатором и таким образом сделать его свойством зависимостей. Назначение свойства свойством зависимостей не всегда требуется и не всегда уместно и зависит от конкретной ситуации. В некоторых случаях достаточно просто снабдить свойство закрытым полем. Тем не менее реализовать свойство как свойство зависимостей потребуется, если свойство должно поддерживать одну или несколько из следующих возможностей WPF.

  • Требуется, чтобы свойство было задаваемым в стиле. Более подробную информацию см. в разделе Стилизация и использование шаблонов.

  • Требуется, чтобы свойство поддерживало привязку данных. Дополнительные сведения о свойствах зависимостей привязки данных см. в разделе Привязка свойств двух элементов управления.

  • Требуется, чтобы свойство было задаваемым с помощью динамической ссылки ресурса. Дополнительные сведения см. в разделе Ресурсы XAML.

  • Требуется настроить автоматическое наследование значения свойства из родительского элемента в дереве элементов. В этом случае Зарегистрируйтесь в RegisterAttached методе, даже если также создается оболочка свойств для доступа к среде CLR. Дополнительные сведения см. в разделе Наследование значения свойства.

  • Требуется, чтобы свойство поддерживало анимацию. Более подробную информацию см. в разделе Общие сведения об эффектах анимации.

  • Требуется, чтобы система свойств сообщала об изменении предыдущего значения свойства действиями, выполняемыми системой свойств, окружением или пользователем, или путем чтения и использования стилей. Используя метаданные свойства, свойство может указывать метод обратного вызова, который будет вызываться каждый раз, когда система свойств определит, что значение свойства было однозначно изменено. Связанным понятием является приведение значения свойства. Дополнительные сведения см. в разделе Проверка и обратные вызовы свойства зависимостей.

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

  • необходимо, чтобы свойства пользовательского элемента управления получали Visual Studio поддержки конструктора WPF, например для редактирования окна свойств . Дополнительные сведения см. в разделе Общие сведения о разработке элементов управления.

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

Контрольный список определения свойства зависимостей

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

  • (Необязательно.) Создайте метаданные для свойства зависимостей.

  • Зарегистрируйте имя свойства в системе свойств, указав тип владельца и тип значения свойства. Также укажите метаданные свойства, если они используются.

  • Определите DependencyProperty идентификатор как public static readonly поле в типе владельца.

  • Определите свойство "оболочка" среды CLR, имя которого соответствует имени свойства зависимостей. Реализуйте get методы доступа и для свойства оболочки CLR set для подключения к свойству зависимостей, которое его создает.

Регистрация свойства в системе свойств

Чтобы назначить свойство свойством зависимостей, необходимо зарегистрировать это свойство в таблице, обслуживаемой системой свойств, и предоставить ему уникальный идентификатор, используемый в качестве квалификатора для последующих операций системы свойств. Эти операции могут быть внутренними операциями или собственным кодом, вызывающим 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)
  )
);
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))

Соглашения об именовании свойств зависимостей

Существуют установленные соглашения об именовании, касающиеся свойств зависимостей, которым необходимо следовать, если ситуация не является исключительной.

Само свойство зависимости будет иметь базовое имя «Акуариумграфик», как в этом примере, которое задается в качестве первого параметра 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); }
}

Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))
Public Property AquariumGraphic() As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set(ByVal value As Uri)
        SetValue(AquariumGraphicProperty, value)
    End Set
End Property

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

  • Не будут работать определенные аспекты стилей и шаблонов.

  • Большинство инструментов и конструкторов полагается на соглашения об именовании для правильной сериализации XAML, а также для предоставления помощи разработчику на уровне свойств.

  • Текущая реализация WPF XAML загрузчика полностью обходит обертки и использует соглашение об именовании при обработке значений атрибутов. Дополнительные сведения см. в разделе Загрузка кода XAML и свойства зависимостей.

Метаданные свойства для нового свойства зависимостей

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

При создании свойства зависимостей, существующего в производном классе FrameworkElement , можно использовать более специализированный класс метаданных, FrameworkPropertyMetadata а не базовый PropertyMetadata класс. Конструктор для FrameworkPropertyMetadata класса имеет несколько сигнатур, где можно задать различные характеристики метаданных в сочетании. Если необходимо указать только значение по умолчанию, используйте сигнатуру, которая принимает один параметр типа Object . Передайте этот параметр объекта как значение по умолчанию конкретного типа для свойства (указанное значение по умолчанию должно быть типом, предоставленным propertyType в качестве параметра в Register вызове).

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

Задание соответствующих флагов метаданных

  • Если свойство (или изменяет его значение) влияет на пользовательский интерфейс , а в частности влияет на то, как система макета должна масштабировать или визуализировать элемент на странице, установите один или несколько следующих флагов: AffectsMeasure , AffectsArrange , AffectsRender .

    • AffectsMeasure Указывает, что изменение этого свойства требует изменения для UI отрисовки, где содержащий объект может потребовать больше или меньше места в пределах родительского объекта. Например, этот флаг следует задать для свойства "Ширина".

    • AffectsArrange Указывает, что изменение этого свойства требует изменения в UI отрисовке, которое обычно не требует изменения в выделенном пространстве, но указывает на то, что положение в пространстве изменилось. Например, этот флаг следует задать для свойства "Выравнивание".

    • AffectsRender Указывает, что произошло другое изменение, которое не повлияет на макет и меру, но требует другого рендеринга. Примером будет свойство, изменяющее цвет существующего элемента, например "Фон".

    • Эти флаги часто используются в качестве протокола в метаданных для собственных реализаций переопределения системы свойств или обратных вызовов макета. Например, у вас может быть OnPropertyChanged обратный вызов, который будет вызывать, InvalidateArrange Если какое-либо свойство экземпляра сообщает об изменении значения и имеет AffectsArrange как true в метаданных.

  • Некоторые свойства могут влиять на характеристики отрисовки содержащего их родительского элемента указанными выше способами и помимо изменений в обязательном размере, упомянутых выше. Примером является свойство, MinOrphanLines используемое в модели документов нефиксированного формата, при котором изменения этого свойства могут изменить общую отрисовку документа нефиксированного формата, содержащего абзац. Используйте AffectsParentArrange или AffectsParentMeasure для поиска похожих вариантов в собственных свойствах.

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

  • По умолчанию привязка данных Mode для свойств зависимостей по умолчанию имеет значение OneWay . Всегда можно изменить привязку на TwoWay экземпляр привязки. Дополнительные сведения см. в разделе Указание направления привязки. Но, как автор свойства зависимостей, вы можете сделать свойство использовать TwoWay режим привязки по умолчанию. Примером существующего свойства зависимостей является MenuItem.IsSubmenuOpen ; сценарий для этого свойства заключается в том, что IsSubmenuOpen логика настройки и компоновка MenuItem взаимодействуют с стилем темы по умолчанию. IsSubmenuOpenЛогика свойств использует привязку данных изначально для поддержания состояния свойства в соответствии с другими свойствами состояния и вызовами методов. Еще одним примером свойства, которое TwoWay по умолчанию выполняет привязку, является TextBox.Text .

  • Можно также включить наследование свойств в пользовательском свойстве зависимости, установив Inherits флаг. Наследование свойств удобно для ситуации, когда родительские и дочерние элементы имеют общее свойство и для дочерних элементов имеет смысл задать то же значение свойства, что и в родительском элементе. Примером наследуемого свойства является DataContext , которое используется для операций привязки, чтобы включить важный сценарий «основной/подробности» для представления данных. Сделав DataContext наследуемый, все дочерние элементы также наследуют этот контекст данных. Благодаря наследованию значения свойства вы можете указать контекст данных в корне приложения или страницы и вам не потребуется уточнять его для привязок во всех возможных дочерних элементах. DataContext также является хорошим примером того, чтобы проиллюстрировать, что наследование переопределяет значение по умолчанию, но оно всегда может быть задано локально для любого дочернего элемента. Дополнительные сведения см. в разделе Использование шаблона Master-Detail с иерархическими данными. Наследование значения свойства может влиять на производительность, и таким образом, его следует использовать с осторожностью. Дополнительные сведения см. в разделе Наследование значения свойства.

  • Установите Journal флаг, указывающий, должно ли свойство зависимостей обнаруживаться или использоваться службами ведения журнала навигации. Примером является SelectedIndex свойство; любой элемент, выбранный в элементе управления Selection, должен сохраняться при перемещении журнала ведения журнала.

Свойства зависимости "только для чтения"

Можно определить свойство зависимости, которое доступно только для чтения. Однако ситуации для такого использования немного отличаются, как и процедура регистрации свойства в системе свойств и предоставление идентификатора. Дополнительные сведения см. в разделе Свойства зависимостей "только для чтения".

Свойства зависимостей типа коллекция

Свойства зависимостей типа коллекции имеют некоторые дополнительные проблемы при реализации, которые необходимо учитывать. Подробности см. в разделе Свойства зависимостей типа коллекции.

Замечания по безопасности свойств зависимостей

Свойства зависимостей должны объявляться как открытые свойства. Поля идентификаторов свойств зависимостей должны объявляться как открытые статические поля. Даже при попытке объявить другие уровни доступа (например, защищенные) доступ к свойству зависимостей всегда можно получить с помощью идентификатора в сочетании с API системы свойств. Даже поле защищенного идентификатора потенциально доступно из-за создания отчетов или API-интерфейсов определения значений, которые являются частью системы свойств, например LocalValueEnumerator . Дополнительные сведения см. в разделе Безопасность свойств зависимостей.

Свойства зависимостей и конструкторы класса

Есть общий принцип в программировании управляемого кода (он часто принудительно применяется средствами анализа кода, такими как FxCop), подразумевающий, что конструкторы класса не должны вызывать виртуальные методы. Этот принцип обусловлен тем, что конструкторы могут быть вызваны в качестве базовой инициализации конструктора производного класса, а ввод виртуального метода через конструктор может произойти в состоянии неполной инициализации конструируемого экземпляра объекта. При наследовании от любого класса, который уже является производным от DependencyObject , следует иметь в виду, что сама система свойств вызывает и предоставляет виртуальные методы внутренним образом. Эти виртуальные методы являются частью служб системы свойств WPF. Переопределение методов позволяет производным классам участвовать в определении значения. Чтобы избежать потенциальных проблем при инициализации среды выполнения, не задавайте значения свойств зависимостей в конструкторах классов (если только вы не следуете конкретному шаблону конструктора). Подробнее см. в разделе Шаблоны безопасного конструктора для DependencyObjects.

См. также раздел