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

В этом разделе показано, как определять и реализовывать собственные свойства зависимостей для приложения среды выполнения Windows на C++, C# или Visual Basic.Here we explain how to define and implement your own dependency properties for a Windows Runtime app using C++, C#, or Visual Basic. Перечислены причины, по которым разработчики приложений и авторы компонентов могут пожелать создавать пользовательские свойства зависимостей.We list reasons why app developers and component authors might want to create custom dependency properties. Также описаны действия по реализации пользовательского свойства зависимостей и приведены некоторые рекомендации по повышению производительности, удобства или гибкости свойства зависимостей.We describe the implementation steps for a custom dependency property, as well as some best practices that can improve performance, usability, or versatility of the dependency property.

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

Мы предполагаем, что вы ознакомились с разделом Общая информация о свойствах зависимостей и понимаете свойства зависимостей с точки зрения потребителя существующих свойств зависимостей.We assume that you have read the Dependency properties overview and that you understand dependency properties from the perspective of a consumer of existing dependency properties. Чтобы читать примеры в этом разделе, необходимо также разбираться в языке XAML и знать, как написать простое приложение среды выполнения Windows на C++, C# или Visual Basic.To follow the examples in this topic, you should also understand XAML and know how to write a basic Windows Runtime app using C++, C#, or Visual Basic.

Что такое свойство зависимостей?What is a dependency property?

Чтобы поддерживать стили, привязку данных, анимацию и значения по умолчанию для свойства, оно должно быть реализовано как свойство зависимостей.To support styling, data binding, animations, and default values for a property, then it should be implemented as a dependency property. Значения свойства зависимостей не хранятся как поля класса, они хранятся в платформе XAML; для ссылки на них используется ключ, который получается при регистрации свойства в системе свойств среды выполнения Windows путем вызова метода DependencyProperty.Register.Dependency property values are not stored as fields on the class, they are stored by the xaml framework, and are referenced using a key, which is retrieved when the property is registered with the Windows Runtime property system by calling the DependencyProperty.Register method. Свойства зависимостей могут использоваться только типами, производными от DependencyObject.Dependency properties can be used only by types deriving from DependencyObject. Но DependencyObject находится довольно высоко в иерархии классов, так что большинство классов, предназначенных для поддержки пользовательского интерфейса и презентаций, могут поддерживать свойства зависимостей.But DependencyObject is quite high in the class hierarchy, so the majority of classes that are intended for UI and presentation support can support dependency properties. Подробнее о свойствах зависимостей и некоторых терминов и правил их описания в этой документации см. в разделе Общие сведения о свойствах зависимостей.For more information about dependency properties and some of the terminology and conventions used for describing them in this documentation, see Dependency properties overview.

Примерами свойств зависимостей в среде выполнения Windows помимо прочих являются Control.Background, FrameworkElement.Width и TextBox.Text.Examples of dependency properties in the Windows Runtime are: Control.Background, FrameworkElement.Width, and TextBox.Text, among many others.

В соответствии с соглашением, каждое свойство зависимостей, предоставляемое классом, имеет соответствующее свойство public static readonly типа DependencyProperty, которое предоставляется этим же классом и обеспечивает идентификатор для свойства зависимостей.Convention is that each dependency property exposed by a class has a corresponding public static readonly property of type DependencyProperty that is exposed on that same class the provides the identifier for the dependency property. Имя идентификатора формируется следующим образом: имя свойства зависимостей, со строкой «Property», добавленной к концу имени.The identifier's name follows this convention: the name of the dependency property, with the string "Property" added to the end of the name. Например, идентификатором DependencyProperty, соответствующим свойству Control.Background, является Control.BackgroundProperty.For example, the corresponding DependencyProperty identifier for the Control.Background property is Control.BackgroundProperty. Идентификатор сохраняет информацию о свойстве зависимостей, как оно было зарегистрировано, после чего идентификатор можно использовать для других операций, в которые вовлечено свойство зависимостей, например для вызова SetValue.The identifier stores the information about the dependency property as it was registered, and can then be used for other operations involving the dependency property, such as calling SetValue.

Оболочки свойствProperty wrappers

Свойства зависимостей обычно имеют реализацию в виде оболочки.Dependency properties typically have a wrapper implementation. Без оболочки единственным способом получения или задания свойств будет использование служебных методов свойств зависимостей GetValue и SetValue и передача им идентификатора в качестве параметра.Without the wrapper, the only way to get or set the properties would be to use the dependency property utility methods GetValue and SetValue and to pass the identifier to them as a parameter. Это весьма противоестественный способ использования того, что предположительно является свойством.This is a rather unnatural usage for something that is ostensibly a property. Но при использовании программы-оболочки ваш код и любой иной код, ссылающийся на свойство зависимостей, может использовать прямолинейный синтаксис объект-свойство, который естественен для используемого языка.But with the wrapper, your code and any other code that references the dependency property can use a straightforward object-property syntax that is natural for the language you're using.

В случае самостоятельной реализации свойства зависимостей, которое должно быть общедоступным и простым для вызова, надо определить и программы-оболочки свойства.If you implement a custom dependency property yourself and want it to be public and easy to call, define the property wrappers too. Программы-оболочки свойства также полезны для сообщения основной информации о свойстве зависимостей для процессов отражения или статического анализа.The property wrappers are also useful for reporting basic information about the dependency property to reflection or static analysis processes. А именно, в оболочке мы размещаем атрибуты вроде ContentPropertyAttribute.Specifically, the wrapper is where you place attributes such as ContentPropertyAttribute.

Ситуации, когда стоит реализовывать свойство как свойство зависимостейWhen to implement a property as a dependency property

Если вы реализуете общедоступное свойство чтения/записи в классе и класс является производным DependencyObject, свойство можно сделать свойством зависимостей.Whenever you implement a public read/write property on a class, as long as your class derives from DependencyObject, you have the option to make your property work as a dependency property. Иногда достаточно типовой методики резервирования свойства с закрытым полем.Sometimes the typical technique of backing your property with a private field is adequate. Определять свое свойство как свойство зависимостей не всегда необходимо или разумно.Defining your custom property as a dependency property is not always necessary or appropriate. Выбор должен зависеть от сценариев, для поддержки которых предназначено свойство.The choice will depend on the scenarios that you intend your property to support.

Реализацию свойства в качестве свойства зависимостей стоит рассмотреть, если оно должно поддерживать одну или несколько следующих функций среды выполнения Windows или приложений среды выполнения Windows.You might consider implementing your property as a dependency property when you want it to support one or more of these features of the Windows Runtime or of Windows Runtime apps:

  • Задание свойства через StyleSetting the property through a Style
  • Выполнение функции допустимого целевого свойства для привязки данных с {Binding} Acting as valid target property for data binding with {Binding}
  • Поддержка анимированных значений посредством StoryboardSupporting animated values through a Storyboard
  • Сообщение об изменении значения свойства:Reporting when the value of the property has been changed by:
    • действиями самой системы свойств;Actions taken by the property system itself
    • средой;The environment
    • действиями пользователя;User actions
    • чтением и записью стилей.Reading and writing styles

Контрольный список для определения свойства зависимостейChecklist for defining a dependency property

Определение свойства зависимостей можно рассматривать как набор концепций.Defining a dependency property can be thought of as a set of concepts. Эти концепции не обязательно являются этапами процедуры, поскольку в реализации нескольким из них может соответствовать одна строка кода.These concepts are not necessarily procedural steps, because several concepts can be addressed in a single line of code in the implementation. Данный список предоставляет лишь краткий обзор.This list gives just a quick overview. Ниже в этом разделе мы расскажем о каждой из концепций подробнее, иллюстрируя их примерами кода на нескольких языках.We'll explain each concept in more detail later in this topic, and we'll show you example code in several languages.

  • Зарегистрируйте имя свойства в системе свойств (путем вызова функции Register), указав тип владельца и тип значения свойства.Register the property name with the property system (call Register), specifying an owner type and the type of the property value.
    • Существует обязательный параметр функции Register, который принимает метаданные свойства.There's a required parameter for Register that expects property metadata. Укажите для него значение null или, если требуется поведение при изменении свойства или значение по умолчанию на основе метаданных, которое можно восстановить вызовом метода ClearValue, укажите экземпляр PropertyMetadata.Specify null for this, or if you want property-changed behavior, or a metadata-based default value that can be restored by calling ClearValue, specify an instance of PropertyMetadata.
  • Определите идентификатор DependencyProperty как член свойства public static readonly в типе владельца.Define a DependencyProperty identifier as a public static readonly property member on the owner type.
  • Определите свойство-оболочку, следуя модели метода доступа к свойству, применяемой в используемом языке.Define a wrapper property, following the property accessor model that's used in the language you are implementing. Имя свойства-оболочки должно совпадать со строкой name, используемой в Register.The wrapper property name should match the name string that you used in Register. Реализуйте методы доступа get и set для соединения оболочки с заключенным в нее свойством зависимостей, вызывая GetValue и SetValue и передавая в качестве параметра идентификатор вашего свойства.Implement the get and set accessors to connect the wrapper with the dependency property that it wraps, by calling GetValue and SetValue and passing your own property's identifier as a parameter.
  • (Необязательно) Разместите в оболочке такие атрибуты как ContentPropertyAttribute.(Optional) Place attributes such as ContentPropertyAttribute on the wrapper.

Примечание

Если вы определяете пользовательское присоединенное свойство, то оболочка обычно опускается.If you are defining a custom attached property, you generally omit the wrapper. Вместо нее вы создаете метод доступа в другом стиле, который может использоваться обработчиком XAML.Instead, you write a different style of accessor that a XAML processor can use. См. раздел Пользовательские присоединенные свойства.See Custom attached properties. 

Регистрация свойстваRegistering the property

Чтобы свойство стало свойством зависимостей, его необходимо зарегистрировать в хранилище свойств системы свойств среды выполнения Windows.For your property to be a dependency property, you must register the property into a property store maintained by the Windows Runtime property system. Чтобы зарегистрировать свойство, вызовите метод Register.To register the property, you call the Register method.

Для языков Microsoft .NET (C# и Microsoft Visual Basic) мы вызываем Register внутри тела класса (внутри класса, но вне любых определений членов).For Microsoft .NET languages (C# and Microsoft Visual Basic) you call Register within the body of your class (inside the class, but outside any member definitions). Идентификатор предоставляется как возвращаемое значение метода Register.The identifier is provided by the Register method call, as the return value. Вызов метода Register обычно выполняется в виде статического конструктора или в процессе инициализации свойства public static readonly типа DependencyProperty в рамках вашего класса.The Register call is typically made as a static constructor or as part of the initialization of a public static readonly property of type DependencyProperty as part of your class. Это свойство предоставляет идентификатор для свойства зависимостей.This property exposes the identifier for your dependency property. Вот примеры вызова Register.Here are examples of the Register call.

Примечание

Регистрация свойства зависимостей как части определения свойства идентификатора является обычной реализацией, но можно также зарегистрировать свойство зависимостей в статическом конструкторе класса.Registering the dependency property as part of the identifier property definition is the typical implementation, but you can also register a dependency property in the class static constructor. Такой подход может иметь смысл, если для инициализации свойства зависимостей необходимо более одной строки кода.This approach may make sense if you need more than one line of code to initialize the dependency property.

Для C++кода/CX можно использовать параметры разделения реализации между заголовком и файлом кода.For C++/CX, you have options for how you split the implementation between the header and the code file. Обычным вариантом является объявление самого идентификатора как свойства public static в заголовке, с реализацией get, но без set.The typical split is to declare the identifier itself as public static property in the header, with a get implementation but no set. Реализация get ссылается на закрытое поле, которым является неинициализированный экземпляр DependencyProperty.The get implementation refers to a private field, which is an uninitialized DependencyProperty instance. Также можно объявить оболочки и реализации get и set для оболочки.You can also declare the wrappers and the get and set implementations of the wrapper. В этом случае заголовок включает некоторую минимальную реализацию.In this case the header includes some minimal implementation. Если оболочке нужно определение объекта среды выполнения Windows, выполните его также и в заголовке.If the wrapper needs Windows Runtime attribution, attribute in the header too. Поместите вызов функции Register в файл кода во вспомогательную функцию, которая выполняется только в момент первой инициализации приложения.Put the Register call in the code file, within a helper function that only gets run when the app initializes the first time. Используйте возвращаемое значение Register для заполнения статических, но еще не инициализированных идентификаторов, объявленных в заголовочном файле, которым первоначально было присвоено значение nullptr в корневой области файла реализации.Use the return value of Register to fill the static but uninitialized identifiers that you declared in the header, which you initially set to nullptr at the root scope of the implementation file.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  "Label",
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);
Public Shared ReadOnly LabelProperty As DependencyProperty = 
    DependencyProperty.Register("Label", 
      GetType(String), 
      GetType(ImageWithLabelControl), 
      New PropertyMetadata(Nothing))
// ImageWithLabelControl.idl
namespace ImageWithLabelControlApp
{
    runtimeclass ImageWithLabelControl : Windows.UI.Xaml.Controls.Control
    {
        ImageWithLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

// ImageWithLabelControl.h
...
private:
    static Windows::UI::Xaml::DependencyProperty m_labelProperty;
...

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
...
//.h file
//using namespace Windows::UI::Xaml::Controls;
//using namespace Windows::UI::Xaml::Interop;
//using namespace Windows::UI::Xaml;
//using namespace Platform;

public ref class ImageWithLabelControl sealed : public Control
{
private:
    static DependencyProperty^ _LabelProperty;
...
public:
    static void RegisterDependencyProperties();
    static property DependencyProperty^ LabelProperty
    {
        DependencyProperty^ get() {return _LabelProperty;}
    }
...
};

//.cpp file
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml.Interop;

DependencyProperty^ ImageWithLabelControl::_LabelProperty = nullptr;

// This function is called from the App constructor in App.xaml.cpp
// to register the properties
void ImageWithLabelControl::RegisterDependencyProperties()
{ 
    if (_LabelProperty == nullptr)
    { 
        _LabelProperty = DependencyProperty::Register(
          "Label", Platform::String::typeid, ImageWithLabelControl::typeid, nullptr);
    } 
}

Примечание

Для кода C++/CX причина, по которой у вас есть закрытое поле и открытое свойство только для чтения, которое служит для указания DependencyProperty , так что другие вызывающие объекты, использующие свойство зависимостей, также могут использовать API-интерфейсы служебной программы, требующие, чтобы идентификатор был открытым.For the C++/CX code, the reason why you have a private field and a public read-only property that surfaces the DependencyProperty is so that other callers who use your dependency property can also use property-system utility APIs that require the identifier to be public. Если оставить идентификатор закрытым, то другие пользователи не смогут использовать служебные API.If you keep the identifier private, people can't use these utility APIs. Примеры таких API и сценариев включают GetValue или SetValue (по выбору), ClearValue, GetAnimationBaseValue, SetBinding и Setter.Property.Examples of such API and scenarios include GetValue or SetValue by choice, ClearValue, GetAnimationBaseValue, SetBinding, and Setter.Property. Для этого невозможно использовать открытое поле, поскольку правила метаданных среды выполнения Windows не допускают открытые поля.You can't use a public field for this, because Windows Runtime metadata rules don't allow for public fields.

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

Для свойств зависимостей существуют соглашения об именовании; следуйте им, если не возникает каких-либо исключительных обстоятельств.There are naming conventions for dependency properties; follow them in all but exceptional circumstances. У самого свойства зависимостей имеется простое имя («Label» в предыдущем примере), которое задается как первый параметр функции Register.The dependency property itself has a basic name ("Label" in the preceding example) that is given as the first parameter of Register. Это имя должно быть уникально внутри каждого регистрирующего типа, и это требование уникальности также относится к любым унаследованным членам.The name must be unique within each registering type, and the uniqueness requirement also applies to any inherited members. Свойства зависимостей, унаследованные через базовые типы, уже считаются частью регистрирующего типа; имена унаследованных свойств нельзя зарегистрировать снова.Dependency properties inherited through base types are considered to be part of the registering type already; names of inherited properties cannot be registered again.

Предупреждение

Хотя здесь имя может быть любым строковым идентификатором, допустимым в программировании для выбранного языка, обычно требуется возможность задать свойство зависимости в XAML тоже.Although the name you provide here can be any string identifier that is valid in programming for your language of choice, you usually want to be able to set your dependency property in XAML too. Для задания в XAML выбранное имя свойства должно быть допустимым именем XAML.To be set in XAML, the property name you choose must be a valid XAML name. Подробнее см. в разделе Обзор XAML.For more info, see XAML overview.

При создании свойства-идентификатора соедините имя свойства в том виде, в котором оно было зарегистрировано, с суффиксом «Property» (например, «LabelProperty»).When you create the identifier property, combine the name of the property as you registered it with the suffix "Property" ("LabelProperty", for example). Данное свойство является идентификатором для свойства зависимостей и используется в качестве входных данных для вызовов SetValue и GetValue, выполняемых в ваших оболочках свойств.This property is your identifier for the dependency property, and it is used as an input for the SetValue and GetValue calls you make in your own property wrappers. Оно также используется системой свойств и другими обработчиками XAML, такими как {x:Bind} It is also used by the property system and other XAML processors such as {x:Bind}

Реализация оболочкиImplementing the wrapper

Программе-оболочке свойства следует вызывать GetValue в реализации get и SetValue в реализации set.Your property wrapper should call GetValue in the get implementation, and SetValue in the set implementation.

Предупреждение

Во всех, но исключительных обстоятельствах реализация оболочек должна выполнять только операции GetValue и SetValue .In all but exceptional circumstances, your wrapper implementations should perform only the GetValue and SetValue operations. В ином случае поведение при задании свойства через разметку XAML и при его задании через код будет различным.Otherwise, you'll get different behavior when your property is set via XAML versus when it is set via code. Для эффективности средство синтаксического анализа XAML обходит программы-оболочки при установке свойств зависимостей; оно обменивается данными с резервным хранилищем через SetValue.For efficiency, the XAML parser bypasses wrappers when setting dependency properties; and talks to the backing store via SetValue.

public String Label
{
    get { return (String)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}
Public Property Label() As String
    Get
        Return DirectCast(GetValue(LabelProperty), String) 
    End Get 
    Set(ByVal value As String)
        SetValue(LabelProperty, value)
    End Set
End Property
// ImageWithLabelControl.h
...
winrt::hstring Label()
{
    return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
}

void Label(winrt::hstring const& value)
{
    SetValue(m_labelProperty, winrt::box_value(value));
}
...
//using namespace Platform;
public:
...
  property String^ Label
  {
    String^ get() {
      return (String^)GetValue(LabelProperty);
    }
    void set(String^ value) {
      SetValue(LabelProperty, value);
    }
  }

Метаданные свойства для пользовательского свойства зависимостейProperty metadata for a custom dependency property

Когда свойству зависимостей назначаются метаданные свойства, эти же метаданные применяются к этому свойству для каждого экземпляра типа, которому принадлежит свойство, или его подклассов.When property metadata is assigned to a dependency property, the same metadata is applied to that property for every instance of the property-owner type or its subclasses. В метаданных свойства можно указать два поведения:In property metadata, you can specify two behaviors:

  • значение по умолчанию, назначаемое системой свойств всем случаям данного свойства;A default value that the property system assigns to all cases of the property.
  • статический метод обратного вызова, автоматически вызываемый в системе свойств при обнаружении изменения значения свойства.A static callback method that is automatically invoked within the property system whenever a property value change is detected.

Вызов метода Register с метаданными свойстваCalling Register with property metadata

В предыдущих примерах вызова DependencyProperty.Register мы передавали параметру propertyMetadata значение null.In the previous examples of calling DependencyProperty.Register, we passed a null value for the propertyMetadata parameter. Чтобы свойство зависимостей предоставляло значение по умолчанию или использовало обратный вызов при изменении свойства, необходимо определить экземпляр PropertyMetadata, который предоставляет одну или обе эти возможности.To enable a dependency property to provide a default value or use a property-changed callback, you must define a PropertyMetadata instance that provides one or both of these capabilities.

Как правило, вы предоставляете PropertyMetadata как внутренне созданный экземпляр в параметрах для DependencyProperty.Register.Typically you provide a PropertyMetadata as an inline-created instance, within the parameters for DependencyProperty.Register.

Примечание

При определении реализации креатедефаултвалуекаллбакк необходимо использовать служебный метод PropertyMetadata. Create вместо вызова конструктора PropertyMetadata для определения экземпляра PropertyMetadata .If you are defining a CreateDefaultValueCallback implementation, you must use the utility method PropertyMetadata.Create rather than calling a PropertyMetadata constructor to define the PropertyMetadata instance.

Следующий пример отличается от предыдущих примеров DependencyProperty.Register ссылкой на экземпляр PropertyMetadata с помощью значения PropertyChangedCallback.This next example modifies the previously shown DependencyProperty.Register examples by referencing a PropertyMetadata instance with a PropertyChangedCallback value. Реализация обратного вызова OnLabelChanged показана далее в этом разделе.The implementation of the "OnLabelChanged" callback will be shown later in this section.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  "Label",
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null,new PropertyChangedCallback(OnLabelChanged))
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
    DependencyProperty.Register("Label",
      GetType(String),
      GetType(ImageWithLabelControl),
      New PropertyMetadata(
        Nothing, new PropertyChangedCallback(AddressOf OnLabelChanged)))
// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr, Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...
DependencyProperty^ ImageWithLabelControl::_LabelProperty =
    DependencyProperty::Register("Label",
    Platform::String::typeid,
    ImageWithLabelControl::typeid,
    ref new PropertyMetadata(nullptr,
      ref new PropertyChangedCallback(&ImageWithLabelControl::OnLabelChanged))
    );

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

Вы можете указать значение по умолчанию для свойства зависимостей, чтобы это свойство, если оно не задано, всегда возвращало определенное значение по умолчанию.You can specify a default value for a dependency property such that the property always returns a particular default value when it is unset. Это значение может отличаться от значения по умолчанию, присущего типу этого свойства.This value can be different than the inherent default value for the type of that property.

Если значение по умолчанию не указано, то значением по умолчанию для свойства зависимостей будет null для ссылочного типа, значение по умолчанию типа для типа значения или примитив языка (например, 0 для целого числа или пустая строка для строки).If a default value is not specified, the default value for a dependency property is null for a reference type, or the default of the type for a value type or language primitive (for example, 0 for an integer or an empty string for a string). Основная причина для установки значения по умолчанию состоит в том, что это значение восстанавливается при вызове ClearValue на свойстве.The main reason for establishing a default value is that this value is restored when you call ClearValue on the property. Установка значений по умолчанию для отдельных свойств может быть удобнее установки значений по умолчанию в конструкторах, особенно для типов значений.Establishing a default value on a per-property basis might be more convenient than establishing default values in constructors, particularly for value types. Однако для ссылочных типов следует убедиться, что установка значения по умолчанию не создает непредвиденного шаблона одноэлементного объекта.However, for reference types, make sure that establishing a default value does not create an unintentional singleton pattern. Подробнее см. далее в разделе Рекомендации.For more info, see Best practices later in this topic

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...

Примечание

Не регистрироваться со значением по умолчанию UnsetValue.Do not register with a default value of UnsetValue. Это может запутать объект-получатель свойств и повлечет непредвиденные последствия внутри системы свойств.If you do, it will confuse property consumers and will have unintended consequences within the property system.

CreateDefaultValueCallbackCreateDefaultValueCallback

В некоторых случаях необходимо определить свойства зависимостей для объектов, которые используются в нескольких потоках пользовательского интерфейса.In some scenarios, you are defining dependency properties for objects that are used on more than one UI thread. Это требуется, например, когда вы определяете объект данных или элемент управления, которые используются в нескольких приложениях.This might be the case if you are defining a data object that is used by multiple apps, or a control that you use in more than one app. Вы можете разрешить обмен объектом между разными потоками пользовательского интерфейса, предоставив реализацию CreateDefaultValueCallback, а не экземпляр значения по умолчанию, привязанный к потоку, зарегистрировавшему это свойство.You can enable the exchange of the object between different UI threads by providing a CreateDefaultValueCallback implementation rather than a default value instance, which is tied to the thread that registered the property. По сути, CreateDefaultValueCallback определяет фабрику для значений по умолчанию.Basically a CreateDefaultValueCallback defines a factory for default values. Значение, возвращаемое CreateDefaultValueCallback, всегда ассоциируется с текущим потоком пользовательского интерфейса CreateDefaultValueCallback, использующим этот объект.The value returned by CreateDefaultValueCallback is always associated with the current UI CreateDefaultValueCallback thread that is using the object.

Чтобы определить метаданные, задающие CreateDefaultValueCallback, необходимо вызвать метод PropertyMetadata.Create для возврата экземпляра метаданных. Конструкторы PropertyMetadata не имеют подписи, включающей параметр CreateDefaultValueCallback.To define metadata that specifies a CreateDefaultValueCallback, you must call PropertyMetadata.Create to return a metadata instance; the PropertyMetadata constructors do not have a signature that includes a CreateDefaultValueCallback parameter.

Обычный шаблон реализации для CreateDefaultValueCallback: создание нового класса DependencyObject, установка специального значения для каждого свойства DependencyObject равным желаемому значению по умолчанию, а затем возврат нового класса как ссылки Object с помощью возвращаемого значения метода CreateDefaultValueCallback.The typical implementation pattern for a CreateDefaultValueCallback is to create a new DependencyObject class, set the specific property value of each property of the DependencyObject to the intended default, and then return the new class as an Object reference via the return value of the CreateDefaultValueCallback method.

Метод обратного вызова при изменении свойстваProperty-changed callback method

Для определения взаимодействия свойства с иными свойствами зависимостей либо обновления внутреннего свойства или состояния объекта при изменении данного свойства можно задать метод обратного вызова при изменении свойства.You can define a property-changed callback method to define your property's interactions with other dependency properties, or to update an internal property or state of your object whenever the property changes. Выполнение такого обратного вызова означает, что система свойств обнаружила фактическое изменение значения свойства.If your callback is invoked, the property system has determined that there is an effective property value change. Метод обратного вызова является статическим, поэтому параметр d метода обратного вызова важен, так как он говорит нам, какой экземпляр класса сообщил об изменении.Because the callback method is static, the d parameter of the callback is important because it tells you which instance of the class has reported a change. Типичная реализация использует свойство NewValue данных события и обрабатывает это значение тем или иным образом, обычно выполняя какое-то еще изменение объекта, переданного как d.A typical implementation uses the NewValue property of the event data and processes that value in some manner, usually by performing some other change on the object passed as d. Дополнительные ответы на изменение свойства включают отклонение значения, сообщаемого свойством NewValue, восстановление значения OldValue или присвоение значению программного ограничения, примененного к NewValue.Additional responses to a property change are to reject the value reported by NewValue, to restore OldValue, or to set the value to a programmatic constraint applied to the NewValue.

Следующий пример показывает реализацию PropertyChangedCallback.This next example shows a PropertyChangedCallback implementation. Он реализует метод, ссылки на который можно было заметить в предыдущих примерах Register, в качестве части аргументов создания для PropertyMetadata.It implements the method you saw referenced in the previous Register examples, as part of the construction arguments for the PropertyMetadata. Ситуация, которой касается данный обратный вызов, заключается в том, что у класса также имеется вычисляемое свойство только для чтения под названием HasLabelValue (реализация не показана).The scenario addressed by this callback is that the class also has a calculated read-only property named "HasLabelValue" (implementation not shown). При каждой переоценке значения свойства Label вызывается метод обратного вызова, и обратный вызов обеспечивает синхронизацию между независимым вычисленным значением и изменениями в свойстве зависимостей.Whenever the "Label" property gets reevaluated, this callback method is invoked, and the callback enables the dependent calculated value to remain in synchronization with changes to the dependency property.

private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    ImageWithLabelControl iwlc = d as ImageWithLabelControl; //null checks omitted
    String s = e.NewValue as String; //null checks omitted
    if (s == String.Empty)
    {
        iwlc.HasLabelValue = false;
    } else {
        iwlc.HasLabelValue = true;
    }
}
    Private Shared Sub OnLabelChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim iwlc As ImageWithLabelControl = CType(d, ImageWithLabelControl) ' null checks omitted
        Dim s As String = CType(e.NewValue,String) ' null checks omitted
        If s Is String.Empty Then
            iwlc.HasLabelValue = False
        Else
            iwlc.HasLabelValue = True
        End If
    End Sub
void ImageWithLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto iwlc{ d.as<ImageWithLabelControlApp::ImageWithLabelControl>() };
    auto s{ winrt::unbox_value<winrt::hstring>(e.NewValue()) };
    iwlc.HasLabelValue(s.size() != 0);
}
static void OnLabelChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    ImageWithLabelControl^ iwlc = (ImageWithLabelControl^)d;
    Platform::String^ s = (Platform::String^)(e->NewValue);
    if (s->IsEmpty()) {
        iwlc->HasLabelValue=false;
    }
}

Реакция на событие изменения свойства для структур и перечисленийProperty changed behavior for structures and enumerations

Если тип DependencyProperty является перечислением или структурой, обратный вызов может выполняться, даже если внутренние значения структуры или перечисления не менялись.If the type of a DependencyProperty is an enumeration or a structure, the callback may be invoked even if the internal values of the structure or the enumeration value did not change. Этим он отличается от примитивов системы, например строки, где обратный вызов выполняется только в случае, если значение было изменено.This is different from a system primitive such as a string where it only is invoked if the value changed. Это побочный эффект, оказываемый на эти значения внутренними операциями упаковки-преобразования и распаковки-преобразования.This is a side effect of box and unbox operations on these values that is done internally. Если вы используете метод PropertyChangedCallback для свойства со значением, представленным перечислением или структурой, необходимо сравнить OldValue и NewValue, приведя значения самостоятельно и используя перегруженные операторы сравнения, доступные для только что приведенных значений.If you have a PropertyChangedCallback method for a property where your value is an enumeration or structure, you need to compare the OldValue and NewValue by casting the values yourself and using the overloaded comparison operators that are available to the now-cast values. Или, если такой оператор недоступен (что возможно в случае пользовательской структуры), может потребоваться сравнить индивидуальные значения.Or, if no such operator is available (which might be the case for a custom structure), you may need to compare the individual values. Если в результате значения не меняются, то, как правило, предпринимать ничего не нужно.You would typically choose to do nothing if the result is that the values have not changed.

private static void OnVisibilityValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    if ((Visibility)e.NewValue != (Visibility)e.OldValue)
    {
        //value really changed, invoke your changed logic here
    } // else this was invoked because of boxing, do nothing
}
Private Shared Sub OnVisibilityValueChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    If CType(e.NewValue,Visibility) != CType(e.OldValue,Visibility) Then
        '  value really changed, invoke your changed logic here
    End If
    '  else this was invoked because of boxing, do nothing
End Sub
static void OnVisibilityValueChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto oldVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.OldValue()) };
    auto newVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.NewValue()) };

    if (newVisibility != oldVisibility)
    {
        // The value really changed; invoke your property-changed logic here.
    }
    // Otherwise, OnVisibilityValueChanged was invoked because of boxing; do nothing.
}
static void OnVisibilityValueChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    if ((Visibility)e->NewValue != (Visibility)e->OldValue)
    {
        //value really changed, invoke your changed logic here
    } 
    // else this was invoked because of boxing, do nothing
    }
}

РекомендацииBest practices

При определении своего свойства зависимостей учитывайте следующие рекомендации.Keep the following considerations in mind as best practices when as you define your custom dependency property.

DependencyObject и использование потоковDependencyObject and threading

Все экземпляры DependencyObject должны создаваться в потоке пользовательского интерфейса, связанном с текущей версией Window, которая отображается приложением среды выполнения Windows.All DependencyObject instances must be created on the UI thread which is associated with the current Window that is shown by a Windows Runtime app. Хотя каждый экземпляр DependencyObject должен создаваться в основном потоке пользовательского интерфейса, доступ к объектам может обеспечиваться при помощи диспетчерских ссылок из других потоков вызовом Dispatcher.Although each DependencyObject must be created on the main UI thread, the objects can be accessed using a dispatcher reference from other threads, by calling Dispatcher.

Потоковые аспекты DependencyObject имеют отношение к данному вопросу, поскольку фактически только код, выполняемый в потоке пользовательского интерфейса, может изменить или хотя бы прочитать значение свойства зависимостей.The threading aspects of DependencyObject are relevant because it generally means that only code that runs on the UI thread can change or even read the value of a dependency property. Проблем с потоками обычно можно избежать в стандартном коде пользовательского интерфейса, который обеспечивает корректное использование шаблонов async и фоновых рабочих потоков.Threading issues can usually be avoided in typical UI code that makes correct use of async patterns and background worker threads. Как правило, проблемы, связанные с потоками DependencyObject, возникают при определении ваших собственных типов DependencyObject и попытке использовать их для формирования источников данных либо в других сценариях, где использование DependencyObject может быть неуместно.You typically only run into DependencyObject-related threading issues if you are defining your own DependencyObject types and you attempt to use them for data sources or other scenarios where a DependencyObject isn't necessarily appropriate.

Избежание незапланированных одноэлементных объектовAvoiding unintentional singletons

Незапланированный одноэлементный объект может возникнуть, если вы объявляете свойство зависимостей, принимающее ссылочный тип, и вызываете конструктор для этого типа в коде, устанавливающем PropertyMetadata.An unintentional singleton can happen if you are declaring a dependency property that takes a reference type, and you call a constructor for that reference type as part of the code that establishes your PropertyMetadata. В результате все случаи использования свойства зависимостей разделяют единственный экземпляр PropertyMetadata и пытаются совместно использовать единственный ссылочный тип, сконструированный вами.What happens is that all usages of the dependency property share just one instance of PropertyMetadata and thus try to share the single reference type you constructed. Тогда любые подсвойства этого типа значения, устанавливаемые через свойство зависимостей, распространятся на другие объекты путями, которые, возможно, не были предусмотрены.Any subproperties of that value type that you set through your dependency property then propagate to other objects in ways you may not have intended.

Для установки исходных значений для свойства зависимостей ссылочного типа можно использовать конструкторы классов, если нужно значение, отличное от null, но следует учитывать, что эти значения будут считаться локальными значениями в целях общих сведений о свойствах зависимостей.You can use class constructors to set initial values for a reference-type dependency property if you want a non-null value, but be aware that this would be considered a local value for purposes of Dependency properties overview. В таком случае правильнее будет использовать шаблон, если класс поддерживает шаблоны.It might be more appropriate to use a template for this purpose, if your class supports templates. Другой способ избежать одноэлементной схемы, но все же предоставить полезное значение по умолчанию — предоставить статическое свойство на ссылочном типе, дающее подходящие установки по умолчанию для значений класса.Another way to avoid a singleton pattern, but still provide a useful default, is to expose a static property on the reference type that provides a suitable default for the values of that class.

Свойства зависимостей типа коллекцииCollection-type dependency properties

У свойств зависимостей типа коллекции есть свои дополнительные проблемы реализации, которые следует рассмотреть.Collection-type dependency properties have some additional implementation issues to consider.

Свойства зависимостей типа коллекции довольно редки в API среды выполнения Windows.Collection-type dependency properties are relatively rare in the Windows Runtime API. В большинстве случаев коллекции можно использовать там, где элементы относятся к подклассу DependencyObject, но само свойство коллекции реализовано как свойство среды CLR или C++.In most cases, you can use collections where the items are a DependencyObject subclass, but the collection property itself is implemented as a conventional CLR or C++ property. Это вызвано тем, что коллекции не обязательно подходят некоторым типовым сценариям, включающим свойства зависимостей.This is because collections do not necessarily suit some typical scenarios where dependency properties are involved. Например:For example:

  • Мы обычно не анимируем коллекции.You do not typically animate a collection.
  • Мы обычно не выполняем предварительного заполнения элемента в коллекции стилями или шаблоном.You do not typically prepopulate the items in a collection with styles or a template.
  • Хотя привязка к коллекциям является распространенным сценарием, коллекция не обязана быть свойством зависимостей, чтобы быть источником привязки.Although binding to collections is a major scenario, a collection does not need to be a dependency property to be a binding source. Для целевых объектов привязки более типично использование подклассов ItemsControl или DataTemplate для поддержки элементов коллекции или использования схем моделей просмотра.For binding targets, it is more typical to use subclasses of ItemsControl or DataTemplate to support collection items, or to use view-model patterns. Подробнее о привязке коллекций и к коллекциям см. в разделе Подробно о привязке данных.For more info about binding to and from collections, see Data binding in depth.
  • Для уведомлений об изменении коллекций лучше использовать такие интерфейсы, как INotifyPropertyChanged или INotifyCollectionChanged, либо наследовать тип коллекции от ObservableCollection<T> .Notifications for collection changes are better addressed through interfaces such as INotifyPropertyChanged or INotifyCollectionChanged, or by deriving the collection type from ObservableCollection<T>.

Тем не менее сценарии для свойств зависимостей типа коллекции существуют.Nevertheless, scenarios for collection-type dependency properties do exist. Следующие три раздела предоставляют некоторые рекомендации по реализации свойства зависимостей типа коллекции.The next three sections provide some guidance on how to implement a collection-type dependency property.

Инициализация коллекцииInitializing the collection

При создании свойства зависимостей можно установить его значение по умолчанию с помощью метаданных.When you create a dependency property, you can establish a default value by means of dependency property metadata. Но остерегайтесь использования одноэлементной статической коллекции в качестве значения по умолчанию.But be careful to not use a singleton static collection as the default value. Вместо этого следует преднамеренно установить значение коллекции на уникальную коллекцию (экземпляров) в качестве части логики конструктора классов для класса, которому принадлежит свойство коллекции.Instead, you must deliberately set the collection value to a unique (instance) collection as part of class-constructor logic for the owner class of the collection property.

Уведомления об измененияхChange notifications

Определение коллекции как свойства зависимостей не предоставляет автоматически уведомления об изменениях для элементов в коллекции за счет обращения к методу обратного вызова PropertyChanged системы свойств.Defining the collection as a dependency property does not automatically provide change notification for the items in the collection by virtue of the property system invoking the "PropertyChanged" callback method. Если необходимы уведомления для коллекций или элементов коллекций (например, для сценариев привязки данных), реализуйте интерфейс INotifyPropertyChanged или INotifyCollectionChanged interface.If you want notifications for collections or collection items—for example, for a data-binding scenario— implement the INotifyPropertyChanged or INotifyCollectionChanged interface. Дополнительные сведения см. в статье Подробно о привязке данных.For more info, see Data binding in depth.

Вопросы безопасности свойств зависимостейDependency property security considerations

Определяйте свойства зависимостей как открытые свойства.Declare dependency properties as public properties. Определяйте идентификаторы свойств зависимостей как члены public static readonly.Declare dependency property identifiers as public static readonly members. Даже если попытаться объявить иные уровни доступа, допускаемые языком (например, protected), доступ к свойству зависимостей всегда будет возможен через идентификатор в сочетании с API системы свойств.Even if you attempt to declare other access levels permitted by a language (such as protected), a dependency property can always be accessed through the identifier in combination with the property-system APIs. Объявление идентификатора свойств зависимостей внутренним или закрытым не будет работать, поскольку без него не сможет правильно работать система свойств.Declaring the dependency property identifier as internal or private will not work, because then the property system cannot operate properly.

Свойства-оболочки по сути предназначены лишь для удобства пользователей. Механизмы безопасности, применяемые к оболочкам, можно обойти, вызвав GetValue или SetValue.Wrapper properties are really just for convenience, Security mechanisms applied to the wrappers can be bypassed by calling GetValue or SetValue instead. То есть свойства оболочек лучше держать общедоступными; иначе обычным клиентам будет сложнее обратиться к ним, и при этом не возникнет никаких существенных улучшений безопасности.So keep wrapper properties public; otherwise you just make your property harder for legitimate callers to use without providing any real security benefit.

В среде выполнения Windows пользовательское свойство зависимостей нельзя зарегистрировать как свойство только для чтения.The Windows Runtime does not provide a way to register a custom dependency property as read-only.

Свойства зависимостей и конструкторы классовDependency properties and class constructors

Общий принцип состоит в том, что конструкторы классов не должны вызывать виртуальные методы.There is a general principle that class constructors should not call virtual methods. Это связано с тем, что конструкторы можно вызывать для выполнения базовой инициализации конструктора производного класса, а вход в виртуальный метод через конструктор может произойти, когда конструируемый экземпляр объекта еще не до конца проинициализирован.This is because constructors can be called to accomplish base initialization of a derived class constructor, and entering the virtual method through the constructor might occur when the object instance being constructed is not yet completely initialized. При создании потомков любого класса, который сам является производным от DependencyObject, следует помнить, что сама система свойств вызывает и предоставляет виртуальные методы, как часть своих внутренних услуг.When you derive from any class that already derives from DependencyObject, remember that the property system itself calls and exposes virtual methods internally as part of its services. Во избежание потенциальных проблем с инициализацией во время выполнения не устанавливайте значения свойств зависимостей внутри конструкторов или классов.To avoid potential problems with run-time initialization, don't set dependency property values within constructors of classes.

Регистрация свойств зависимостей в приложениях на C++/CXRegistering the dependency properties for C++/CX apps

Чтобы реализовать регистрацию свойства в C++/CX, придется приложить больше усилий, чем в C#, так как имеется разделение на заголовочный файл и файл реализации, а также потому, что инициализацию в корневой области файла реализации выполнять не рекомендуется.The implementation for registering a property in C++/CX is trickier than C#, both because of the separation into header and implementation file and also because initialization at the root scope of the implementation file is a bad practice. (Расширения C++ визуальных компонентовC++(/CX) помещает статический код инициализатора из корневой области непосредственно в DllMain, тогда как C# компиляторы применяют статические инициализаторы к классам и, таким образом, устраняют проблемы с блокировкой нагрузки DllMain .).(Visual C++ component extensions (C++/CX) puts static initializer code from the root scope directly into DllMain, whereas C# compilers assign the static initializers to classes and thus avoid DllMain load lock issues.). Лучше всего объявить вспомогательную функцию (для каждого класса), которая будет выполнять регистрацию всех свойств зависимостей для класса.The best practice here is to declare a helper function that does all your dependency property registration for a class, one function per class. Далее для каждого пользовательского класса, используемого в приложении, необходимо дать ссылку на вспомогательную функцию регистрации, которая предоставляется каждым требуемым пользовательским классом.Then for each custom class your app consumes, you'll have to reference the helper registration function that's exposed by each custom class you want to use. Вызывайте каждую вспомогательную функцию регистрации один раз в конструкторе приложений (App::App()) перед InitializeComponent.Call each helper registration function once as part of the Application constructor (App::App()), prior to InitializeComponent. Этот конструктор выполняется только в случае, когда впервые возникает ссылка на приложение. При возобновлении работы приостановленного приложения этот конструктор не выполняется.That constructor only runs when the app is really referenced for the first time, it won't run again if a suspended app resumes, for example. Кроме того, как было показано в предыдущем примере регистрации для C++, важно выполнять проверку на nullptr каждого вызова Register — это гарантия того, что на вызывающей стороне, обращающейся к функции, регистрация свойства не будет выполнена дважды.Also, as seen in the previous C++ registration example, the nullptr check around each Register call is important: it's insurance that no caller of the function can register the property twice. Без этой проверки повторная регистрация может вызвать аварийное завершение приложения, поскольку имя свойства будет дублироваться.A second registration call would probably crash your app without such a check because the property name would be a duplicate. Этот шаблон реализации можно посмотреть в примере пользовательских и настраиваемых элементов управления XAML (если нужен вариант этого примера для C++/CX).You can see this implementation pattern in the XAML user and custom controls sample if you look at the code for the C++/CX version of the sample.