Настраиваемые свойства зависимостей (WPF .NET)

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

Важно!

Документация по рабочему столу для .NET 7 и .NET 6 находится в стадии разработки.

Необходимые компоненты

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

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

Свойства зависимостей — это свойства, зарегистрированные в системе свойств WPF через Register или RegisterReadOnly вызовы. Метод Register возвращает DependencyProperty экземпляр, содержащий зарегистрированное имя и характеристики свойства зависимости. Вы назначите DependencyProperty экземпляр статическому полю чтения, известному как идентификатор свойства зависимостей, который по соглашению называется <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 и предоставления помощи в среде конструктора на уровне свойств.

  • Текущая реализация загрузчика XAML WPF полностью передает оболочки и использует соглашение об именовании для обработки значений атрибутов. Дополнительные сведения см. в разделе о загрузке и свойствах зависимостей 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 Задайте флаг, чтобы указать, что свойство зависимостей должно быть обнаружено или использовано службами журналов навигации. Например, свойство задает Journal флаг, SelectedIndex который рекомендует, чтобы приложения сохраняли журнал элементов.

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

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

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

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

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

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

Для свойств зависимостей только для чтения возвращается RegisterReadOnlyDependencyPropertyKeyзначение, которое обычно не делается DependencyPropertyKeypublic членом класса. Так как система свойств WPF не распространяется DependencyPropertyKey за пределами кода, свойство зависимости только для чтения имеет более set высокую безопасность, чем свойство зависимостей для чтения и записи.

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

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

См. также