Поделиться через


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

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

Предпосылки

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

Идентификатор свойства зависимостей

Свойства зависимостей — это свойства, зарегистрированные в системе свойств WPF через Register или RegisterReadOnly вызовы. Метод Register возвращает DependencyProperty экземпляр, содержащий зарегистрированное имя и характеристики свойства зависимости. Вы назначите экземпляр DependencyProperty статическому полю readonly, известному как идентификатор свойства зависимостей, который по соглашению называется <property name>Property. Например, поле идентификатора Background для свойства всегда BackgroundProperty.

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

Свойства зависимостей могут применяться только к классам, производным от DependencyObject типов. Большинство классов WPF поддерживают свойства зависимостей, так как DependencyObject близко к корню иерархии классов WPF. Дополнительные сведения о свойствах зависимостей и терминологии и соглашениях, используемых для их описания, см. в обзоре свойств зависимостей.

Оболочки свойств зависимостей

Свойства зависимостей WPF, которые не являются присоединенными свойствами, предоставляются оболочкой CLR, реализующей методы доступа get и set. Используя оболочку свойств, потребители свойств зависимостей могут получать или задавать значения свойств зависимостей так же, как и любое другое свойство CLR. Методы доступа get и set взаимодействуют с базовой системой свойств с помощью вызовов DependencyObject.GetValue и DependencyObject.SetValue, передавая идентификатор свойства зависимости в качестве параметра. Потребители свойств зависимостей обычно не вызывают GetValue или SetValue напрямую, но если вы реализуете пользовательское свойство зависимостей, вы будете использовать эти методы в оболочке.

Когда следует реализовать свойство зависимостей

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

  • Свойства, которые задаются в стиле. Дополнительные сведения см. в разделе Стили и шаблоны.

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

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

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

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

  • Уведомление системой свойств WPF при изменении значения свойства. Изменения могут быть вызваны действиями системы свойств, среды, пользователя или стилей. Ваше свойство может задать метод обратного вызова в метаданным свойства, который будет вызываться всякий раз, когда система свойств обнаружит изменение значения вашего свойства. Связанным понятием является приведение значения свойства. Дополнительные сведения об обратных вызовах и проверке свойств зависимостей см. в разделе и.

  • Доступ к метаданным свойства зависимостей, которые считываются процессами WPF. Например, вы можете использовать метаданные свойств для:

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

    • Задайте значение по умолчанию свойства зависимости путем переопределения метаданных производных классов.

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

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

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

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

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

  2. Зарегистрируйте свойство зависимостей в системе свойств, указав имя свойства, тип владельца, тип значения свойства и, при необходимости, метаданные свойства.

  3. Определите DependencyProperty идентификатор в качестве public static readonly поля для типа владельца. Имя поля идентификатора — это имя свойства с добавленным суффиксом Property .

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

Регистрация недвижимости

Чтобы свойство было свойством зависимостей, необходимо зарегистрировать его в системе свойств. Чтобы зарегистрировать свойство, вызовите Register метод из тела класса, но за пределами определений элементов. Метод Register возвращает уникальный идентификатор свойства зависимостей, который будет использоваться при вызове API системы свойств. Причина, по которой вызов Register выполняется вне определений элементов, заключается в назначении возвращаемого значения полю public static readonly типа DependencyProperty. Это поле, которое будет создано в классе, является идентификатором свойства зависимостей. В следующем примере первый аргумент Register имя свойства зависимости AquariumGraphic.

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

Замечание

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

Именование свойств зависимостей

Установленное соглашение об именовании свойств зависимостей является обязательным для нормального поведения системы свойств. Имя создаваемого поля идентификатора должно быть зарегистрированным именем свойства с суффиксом Property.

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

Реализация оболочки свойств

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

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );

// Declare a read-write property wrapper.
public Uri AquariumGraphic
{
    get => (Uri)GetValue(AquariumGraphicProperty);
    set => SetValue(AquariumGraphicProperty, value);
}
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

' Declare a read-write property wrapper.
Public Property AquariumGraphic As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set
        SetValue(AquariumGraphicProperty, Value)
    End Set
End Property

За исключением редких случаев, реализация оболочки должна содержать только код GetValue и SetValue. Причины этого см. в статье "Последствия" для пользовательских свойств зависимостей.

Если свойство не соответствует установленным соглашениям об именовании, могут возникнуть следующие проблемы:

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

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

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

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

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

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

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

Настройка флагов метаданных

При настройке флагов метаданных следует учитывать следующее:

  • Если значение свойства (или изменения в нем) влияет на то, как система макета отрисовывает элемент пользовательского интерфейса, а затем задайте один или несколько следующих флагов:

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

    • AffectsArrange, указывающее, что изменение значения свойства требует изменения в отрисовке пользовательского интерфейса, в частности положение объекта в родительском элементе. Как правило, объект также не изменяет размер. Например, задайте этот флаг метаданных для Alignment свойства.

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

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

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

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

  • Хотя режим привязки данных по умолчанию для свойств зависимостей имеет значение OneWay, можно изменить режим привязки конкретной привязки TwoWayна . Дополнительные сведения см. в разделе "Направление привязки". В качестве автора свойства зависимостей вы можете даже выбрать двустороннюю привязку в качестве режима по умолчанию. Пример существующего свойства зависимости, использующего двустороннюю привязку данных, — это MenuItem.IsSubmenuOpen, состояние которого основано на других свойствах и вызовах методов. Сценарий IsSubmenuOpen заключается в том, что его логика установки и составная часть MenuItemвзаимодействуют со стилем темы по умолчанию. TextBox.Text — это другое свойство зависимости WPF, которое использует двусторонняя привязка по умолчанию.

  • Вы можете включить наследование свойств для свойства зависимостей, задав Inherits флаг. Наследование свойств полезно в сценариях, когда у родительских и дочерних элементов есть общее свойство, и логично, чтобы дочерний элемент наследовал значение родителя для общего свойства. Примером наследуемого свойства является DataContext, который поддерживает операции привязки, использующие сценарий master-detail.

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

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

Можно определить свойство зависимостей, доступное только для чтения. Типичный сценарий заключается в использовании свойства зависимости, которое хранит внутреннее состояние. Например, IsMouseOver является доступным только для чтения, потому что его состояние должно определяться исключительно входными данными мыши. Дополнительные сведения см. в разделе свойств зависимостей только для чтения.

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

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

Безопасность свойств зависимости

Как правило, вы объявляете свойства зависимостей как общедоступные свойства, а поля, содержащие идентификатор, как поля public static readonly. Если указать более строгий уровень доступа, например protected, свойство зависимостей по-прежнему можно получить через его идентификатор в сочетании с API системы свойств. Даже защищенное поле идентификатора потенциально доступно через отчеты метаданных WPF или API определения значений, например LocalValueEnumerator. Для получения дополнительной информации см. раздел "Безопасность свойств зависимостей".

Для свойств зависимостей только для чтения значение, возвращаемое из RegisterReadOnly, является DependencyPropertyKey, и обычно вы не делаете DependencyPropertyKey членом public вашего класса. Поскольку система свойств WPF не распространяет DependencyPropertyKey за пределы вашего кода, свойство зависимости только для чтения лучше защищено, чем свойство зависимости для чтения и записи.

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

Существует общий принцип программирования управляемого кода, часто применяемый средствами анализа кода, что конструкторы классов не должны вызывать виртуальные методы. Это связано с тем, что базовые конструкторы могут вызываться во время инициализации конструктора производного класса, а виртуальный метод, вызываемый базовым конструктором, может выполняться до завершения инициализации производного класса. Когда вы наследуете от класса, который уже наследуется от DependencyObject, система свойств вызывает и предоставляет виртуальные методы. Эти виртуальные методы являются частью служб системы свойств WPF. Переопределение методов позволяет производным классам участвовать в определении значения. Чтобы избежать потенциальных проблем с инициализацией среды выполнения, не следует задавать значения свойств зависимостей в конструкторах классов, если вы не следуйте определенному шаблону конструктора. Дополнительные сведения см. в разделе Безопасные шаблоны конструкторов для DependencyObjects.

См. также