Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Здесь мы объясним, как определить и реализовать собственные свойства зависимостей для приложения среды выполнения Windows с помощью C++, C#или Visual Basic. Мы перечисляем причины, по которым разработчикам приложений и авторам компонентов может потребоваться создать настраиваемые свойства зависимостей. Мы описываем шаги реализации для пользовательского свойства зависимостей, а также некоторые рекомендации, которые могут повысить производительность, удобство использования или универсальность свойства зависимостей.
Предпосылки
Предполагается, что вы ознакомились с обзором свойств зависимостей и понимаете свойства зависимостей с точки зрения потребителя существующих свойств зависимостей. Чтобы следовать примерам в этом разделе, вы также должны понять XAML и узнать, как писать базовое приложение среды выполнения Windows с помощью C++, C#или Visual Basic.
Что такое свойство зависимостей?
Для поддержки стилизации, привязки данных, анимации и значений по умолчанию для свойства она должна быть реализована как свойство зависимостей. Значения свойств зависимостей не хранятся в виде полей класса, они хранятся платформой xaml и ссылаются на ключ, который извлекается при регистрации свойства в системе свойств среды выполнения Windows путем вызова метода DependencyProperty.Register . Свойства зависимостей могут использоваться только типами, производными от DependencyObject. Но DependencyObject довольно высок в иерархии классов, поэтому большинство классов, предназначенных для поддержки пользовательского интерфейса и презентаций, могут поддерживать свойства зависимостей. Дополнительные сведения о свойствах зависимостей и некоторых терминологиях и соглашениях, используемых для описания их в этой документации, см. в обзоре свойств зависимостей.
Примеры свойств зависимостей в среде выполнения Windows: Control.Background, FrameworkElement.Width и TextBox.Text, среди многих других.
Соглашение заключается в том, что каждое свойство зависимостей, предоставляемое классом, имеет соответствующее свойство public static readonly типа DependencyProperty , которое предоставляется в том же классе, которое предоставляет идентификатор свойства зависимостей. Имя идентификатора следует этому соглашению: имя свойства зависимостей с строкой "Свойство", добавленной в конец имени. Например, соответствующий идентификатор DependencyProperty для свойства Control.Background — Control.BackgroundProperty. Идентификатор хранит сведения о свойстве зависимостей по мере его регистрации, а затем можно использовать для других операций с использованием свойства зависимостей, таких как вызов SetValue.
Оболочки свойств
Свойства зависимостей обычно имеют реализацию-обертку. Без оболочки единственный способ получить или задать свойства — это использовать утилитарные методы для свойств зависимости, такие как GetValue и SetValue, и передать идентификатор в качестве параметра. Это довольно неестественное использование для чего-то, что якобы является свойством. Но при использовании оболочки код и любой другой код, ссылающийся на свойство зависимостей, может использовать простой синтаксис свойства объекта, естественный для используемого языка.
Если вы реализуете пользовательское свойство зависимостей самостоятельно и хотите, чтобы оно было общедоступным и простым для вызова, определите оболочки свойств. Оболочки свойств также полезны для предоставления базовой информации о зависимом свойстве для процессов рефлексии или статического анализа. В частности, оболочка — это место, где вы размещаете атрибуты, такие как ContentPropertyAttribute.
Когда следует реализовать свойство в качестве свойства зависимости
При реализации общедоступного свойства чтения и записи в классе, если ваш класс производен от DependencyObject, вы можете сделать это свойство работать в качестве свойства зависимости. Иногда типичная методика резервного копирования свойства с частным полем является достаточной. Определение настраиваемого свойства в качестве свойства зависимостей не всегда является обязательным или подходящим. Выбор зависит от сценариев, которые вы планируете поддерживать с помощью вашего свойства.
Вы можете рассмотреть возможность реализации свойства в качестве свойства зависимостей, если вы хотите поддерживать одну или несколько этих функций среды выполнения Windows или приложений среды выполнения Windows:
- Настройка свойства с помощью стиля
- Выступая в качестве допустимого целевого свойства для привязки данных с {Binding}
- Поддержка анимированных значений с помощью Storyboard
- Сообщает, когда значение свойства было изменено следующим образом:
- Действия, выполняемые самой системой свойств
- Среда
- Действия пользователя
- Способы чтения и записи
Контрольный список для определения свойства зависимостей
Определение свойства зависимостей можно рассматривать как набор понятий. Эти понятия не обязательно являются процедурными этапами, так как в реализации можно решить несколько концепций в одной строке кода. В этом списке представлен краткий обзор. Мы подробно рассмотрим каждую концепцию далее в этом разделе, и мы рассмотрим пример кода на нескольких языках.
- Зарегистрируйте имя свойства в системе свойств (call Register), указав тип владельца и тип значения свойства.
- Существует обязательный параметр для Register , который ожидает метаданные свойства. Укажите значение NULL для этого или если требуется поведение, измененное свойством, или значение по умолчанию на основе метаданных, которое можно восстановить путем вызова ClearValue, укажите экземпляр PropertyMetadata.
- Определите идентификатор DependencyProperty в качестве элемента статического общедоступного свойства только для чтения в типе владельца.
- Определите свойство-оболочку, следуя модели доступа к свойствам, используемой на языке, который вы реализуете. Имя свойства оболочки должно соответствовать строке имени, используемой в Register. Реализуйте аксессоры get и set, чтобы подключить оболочку к зависимому свойству, которое она оборачивает, вызывая GetValue и SetValue и передавая идентификатор собственного свойства в качестве параметра.
- (Необязательно) Поместите атрибуты, такие как ContentPropertyAttribute на оболочку.
Замечание
Если вы определяете пользовательское присоединенное свойство, обычно опускаете оболочку. Вместо этого вы пишете иной стиль аксессора, который может использовать обработчик XAML. Дополнительные свойства см. в разделе "Настраиваемые присоединенные свойства".
Регистрация свойства
Чтобы свойство было свойством зависимостей, необходимо зарегистрировать это свойство в хранилище свойств, поддерживаемом системой свойств среды выполнения Windows. Чтобы зарегистрировать свойство, вызовите метод Register .
Для языков Microsoft .NET (C# и Microsoft Visual Basic) вызывается регистрация в тексте класса (внутри класса, но за пределами определений элементов). Идентификатор предоставляется вызовом метода Register в качестве возвращаемого значения. Вызов Register обычно выполняется как статический конструктор или как часть инициализации общедоступного статического свойства чтения типа DependencyProperty в рамках класса. Это свойство предоставляет идентификатор для свойства зависимостей. Ниже приведены примеры вызова Register .
Замечание
Регистрация свойства зависимостей в рамках определения свойства идентификатора является обычной реализацией, но вы также можете зарегистрировать свойство зависимостей в статическом конструкторе класса. Этот подход может иметь смысл, если требуется несколько строк кода для инициализации свойства зависимостей.
Для C++/CX есть параметры разделения реализации между заголовком и файлом кода. Обычное разделение заключается в объявлении идентификатора как общедоступного статического свойства в заголовке с реализацией get , но без набора. Реализация get ссылается на частное поле, которое является неинициализированным экземпляром DependencyProperty . Вы также можете объявить обертки и геттер и сеттер реализации обертки. В этом случае заголовок включает некоторую минимальную реализацию. Если оболочке необходим атрибут Windows Runtime, он должен быть указан и в заголовке. Поместите вызов register в файл кода в вспомогательной функции, которая запускается только при первом инициализации приложения. Используйте возвращаемое значение Register , чтобы заполнить статические, но неинициализированные идентификаторы, объявленные в заголовке, которые изначально заданы как nullptr в корневой области файла реализации.
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
nameof(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
...
struct ImageWithLabelControl : ImageWithLabelControlT<ImageWithLabelControl>
{
...
public:
static Windows::UI::Xaml::DependencyProperty LabelProperty()
{
return m_labelProperty;
}
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 системы свойств, требующие, чтобы идентификатор был общедоступным. Если вы храните идентификатор закрытым, пользователи не могут использовать эти API-интерфейсы служебной программы. Примеры таких API и сценариев включают GetValue или SetValue по выбору, ClearValue, GetAnimationBaseValue, SetBinding и Setter.Property. Для этого нельзя использовать общедоступное поле, так как правила метаданных среды выполнения Windows не разрешают общедоступные поля.
Соглашения об именовании свойств зависимостей
Существуют соглашения об именовании для свойств зависимостей; следуйте им во всех случаях, кроме исключительных. Свойство зависимостей само по себе имеет базовое имя ("Label" в предыдущем примере), которое присваивается в качестве первого параметра Register. Имя должно быть уникальным в пределах каждого типа регистрируемого объекта, а требование уникальности также применяется ко всем унаследованным членам. Свойства зависимостей, унаследованные через базовые типы, считаются частью уже регистрирующего типа; Имена унаследованных свойств не могут быть зарегистрированы повторно.
Предупреждение
Хотя указанное здесь имя может быть любым строковым идентификатором, допустимым в программировании для выбранного языка, обычно требуется настроить свойство зависимостей в XAML. Чтобы установить свойство в XAML, выбранное вами имя должно быть допустимым именем XAML. Дополнительные сведения см. в обзоре XAML.
При создании свойства идентификатора объедините зарегистрированное имя свойства с суффиксом "Property" (например, "LabelProperty"). Это свойство является идентификатором свойства зависимостей, и он используется в качестве входных данных для вызовов SetValue и GetValue , которые вы выполняете в собственных оболочках свойств. Он также используется системой свойств и другими процессорами XAML, такими как {x:Bind}
Реализация оболочки
Оболочка свойств должна вызывать GetValue в реализации get и SetValue в реализации set.
Предупреждение
Во всех случаях, кроме исключительных обстоятельств, реализация оболочки должна выполнять только операции GetValue и SetValue . В противном случае вы получите другое поведение, если свойство задано через XAML и когда оно задано с помощью кода. Для повышения эффективности XAML-парсер обходит оболочки при установке свойств зависимостей, обращаясь к резервному хранилищу через 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);
}
}
Метаданные свойств для пользовательского свойства зависимостей
Если метаданные свойства назначаются свойству зависимостей, к каждому экземпляру типа владельца свойства или его подкласса применяется одинаковые метаданные. В метаданных свойств можно указать два поведения:
- Значение по умолчанию, которое система свойств назначает всем случаям свойства.
- Статический метод обратного вызова, который автоматически вызывается в системе свойств при обнаружении изменения значения свойства.
Вызов функции регистрации с метаданными свойств
В предыдущих примерах вызова DependencyProperty.Register мы передали значение NULL для параметра propertyMetadata . Чтобы включить свойство зависимостей для предоставления значения по умолчанию или использования обратного вызова с измененным свойством, необходимо определить экземпляр PropertyMetadata , предоставляющий одну или обе эти возможности.
Как правило, вы предоставляете PropertyMetadata в виде встроенного экземпляра в параметры DependencyProperty.Register.
Замечание
Если вы определяете реализацию CreateDefaultValueCallback , необходимо использовать метод PropertyMetadata.Create вместо вызова конструктора PropertyMetadata , чтобы определить экземпляр PropertyMetadata .
Следующий пример изменяет приведенные ранее примеры DependencyProperty.Register , ссылаясь на экземпляр PropertyMetadata со значением PropertyChangedCallback . Реализация обратного вызова OnLabelChanged будет показана далее в этом разделе.
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
nameof(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))
);
Значение по умолчанию
Значение по умолчанию для свойства зависимостей можно указать таким образом, чтобы свойство всегда возвращало определенное значение по умолчанию, если оно не задано. Это значение может отличаться от значения по умолчанию для типа этого свойства.
Если значение по умолчанию не указано, значение по умолчанию для свойства зависимостей имеет значение NULL для ссылочного типа, или тип по умолчанию для типа значения или примитива языка (например, 0 для целого числа или пустой строки для строки). Основная причина установки значения по умолчанию заключается в том, что это значение восстанавливается при вызове ClearValue в свойстве. Установка значения по умолчанию на основе каждого свойства может быть удобнее, чем установка значений по умолчанию в конструкторах, особенно для типов значений. Однако для ссылочных типов убедитесь, что установка значения по умолчанию не создает непреднамеренный одноэлементный шаблон. Дополнительные сведения см. в разделе "Рекомендации " далее в этом разделе
// 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. Если вы это сделаете, это запутает потребителей свойств и будет иметь непредвиденные последствия в системе свойств.
CreateDefaultValueCallback
В некоторых сценариях вы определяете свойства зависимостей для объектов, которые используются в нескольких потоках пользовательского интерфейса. Это может быть так, если вы определяете объект данных, используемый несколькими приложениями, или элемент управления, используемый в нескольких приложениях. Вы можете включить обмен объектом между различными потоками пользовательского интерфейса, предоставив реализацию CreateDefaultValueCallback , а не экземпляр значения по умолчанию, который привязан к потоку, который зарегистрировал свойство. В основном createDefaultValueCallback определяет фабрику значений по умолчанию. Значение, возвращаемое CreateDefaultValueCallback, всегда связано с текущим UI потоком CreateDefaultValueCallback, использующим объект.
Чтобы определить метаданные, указывающие createDefaultValueCallback, необходимо вызвать PropertyMetadata.Create для возврата экземпляра метаданных; Конструкторы PropertyMetadata не имеют сигнатуры, включающую параметр CreateDefaultValueCallback .
Типичным шаблоном реализации для CreateDefaultValueCallback является создание нового класса DependencyObject , установка определенного значения свойства каждого свойства объекта DependencyObject на предполагаемое значение по умолчанию, а затем возврат нового класса в качестве ссылки на объект с помощью возвращаемого значения метода CreateDefaultValueCallback .
Метод обратного вызова с измененным свойством
Можно определить метод обратного вызова с измененным свойством, чтобы определить взаимодействие свойства с другими свойствами зависимостей, или обновить внутреннее свойство или состояние объекта при каждом изменении свойства. Если срабатывает ваш обратный вызов, это означает, что системой свойств было зафиксировано изменение актуального значения свойства. Так как метод обратного вызова является статическим, параметр d обратного вызова важен, так как он сообщает, какой экземпляр класса сообщил об изменении. Типичная реализация использует свойство NewValue данных событий и обрабатывает это значение каким-то образом, как правило, путем выполнения какого-то другого изменения объекта, переданного как d. Дополнительные ответы на изменение свойства — отклонить значение, указанное NewValue, восстановить OldValue или задать значение программным ограничением, примененным к NewValue.
В следующем примере показана реализация PropertyChangedCallback . Он реализует метод, на который вы ссылаетесь в предыдущих примерах регистра , как часть аргументов конструкции для propertyMetadata. Сценарий, рассмотренный этим обратным вызовом, заключается в том, что класс также имеет вычисляемое свойство только для чтения с именем HasLabelValue (реализация не показана). При повторном вычислении свойства Label этот метод обратного вызова вызывается, а обратный вызов позволяет зависимым вычисляемым значением оставаться в синхронизации с изменениями свойства зависимостей.
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;
}
}
Изменено поведение свойств для структур и перечислений
Если тип DependencyProperty является перечислением или структурой, обратный вызов может вызываться даже в том случае, если внутренние значения структуры или значения перечисления не изменились. Это отличается от системного примитива, например строки, в которой вызывается только при изменении значения. Это побочный эффект операций упаковки и распаковки с этими значениями, которые выполняются внутри. Если у вас есть метод PropertyChangedCallback для свойства, где ваше значение является перечислением или структурой, необходимо сравнить OldValue и NewValue, выполнив самостоятельное преобразование значений и используя перегруженные операторы сравнения, доступные для преобразованных значений. Или, если такой оператор недоступен (что может быть для пользовательской структуры), может потребоваться сравнить отдельные значения. Обычно вы бы выбрали ничего не делать, если значения не изменились.
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
}
}
Лучшие практики
При определении пользовательского свойства зависимостей следует учитывать следующие рекомендации.
DependencyObject и потоки
Все экземпляры DependencyObject должны быть созданы в потоке пользовательского интерфейса, связанном с текущим окном , которое отображается приложением среды выполнения Windows. Хотя каждый объект DependencyObject должен быть создан в основном потоке пользовательского интерфейса, объекты можно получить с помощью ссылки диспетчера из других потоков, вызвав диспетчер.
Аспекты потоков в DependencyObject важны, так как обычно это означает, что только код, выполняющийся в потоке пользовательского интерфейса, может изменять или даже читать значение свойства зависимости. Проблемы с потоком обычно можно избежать в типичном коде пользовательского интерфейса, который позволяет правильно использовать асинхронные шаблоны и фоновые рабочие потоки. Обычно возникают проблемы, связанные с потоком DependencyObject, только если вы определяете собственные типы DependencyObject и пытаетесь использовать их для источников данных или других сценариев, когда ЗависимостьObject не обязательно подходит.
Избегайте непреднамеренных синглтонов
Непреднамеренный одноэлементный объект может возникнуть, если вы объявляете свойство зависимости, которое принимает ссылочный тип, и вы вызываете конструктор для этого ссылочного типа в рамках кода, который устанавливает PropertyMetadata. Происходит следующее: все использования свойства зависимости разделяют только один экземпляр PropertyMetadata и, следовательно, пытаются использовать единственный созданный вами ссылочный тип. Любые вложенные свойства этого типа значения, заданные через свойство зависимости, затем распространяются на другие объекты таким образом, который может не соответствовать вашим намерениям.
Конструкторы классов можно использовать для задания начальных значений для свойства зависимостей ссылочного типа, если требуется ненулевое значение, но помните, что это будет считаться локальным значением для целей обзора свойств зависимостей. Возможно, лучше использовать шаблон для этой цели, если класс поддерживает шаблоны. Другой способ избежать единого шаблона, но по-прежнему предоставляет полезное значение по умолчанию, заключается в предоставлении статического свойства в ссылочном типе, который предоставляет подходящее значение по умолчанию для значений этого класса.
Коллекционные свойства зависимости
Свойства зависимостей типа коллекции имеют некоторые дополнительные проблемы реализации.
Свойства зависимости типа коллекции относительно редки в API среды выполнения Windows. В большинстве случаев можно использовать коллекции, в которых элементы являются подклассом DependencyObject , но само свойство коллекции реализуется как обычное свойство CLR или C++ . Это связано с тем, что коллекции не обязательно подходят для некоторых типичных сценариев, в которых участвуют свойства зависимостей. Рассмотрим пример.
- Обычно вы не анимируете коллекцию.
- Обычно элементы в коллекции не заполняются стилями или шаблоном.
- Хотя привязка к коллекциям является основным сценарием, коллекция не должна быть свойством зависимостей, чтобы быть источником привязки. Для целевых объектов привязки более типично использовать подклассы ItemsControl или DataTemplate для поддержки элементов коллекции или использования шаблонов модели представления. Дополнительные сведения о привязке к коллекциям и из нее см. в подробной статье о привязке данных.
- Уведомления об изменениях коллекции лучше обрабатываются через интерфейсы, такие как INotifyPropertyChanged или INotifyCollectionChanged, или путем наследования типа коллекции от ObservableCollection<T>.
Тем не менее существуют сценарии для свойств зависимостей типа коллекции. В следующих трех разделах приведены некоторые рекомендации по реализации свойства зависимости типа коллекции.
Инициализация коллекции
При создании свойства зависимостей можно установить значение по умолчанию с помощью метаданных свойства зависимости. Но будьте осторожны, чтобы не использовать одну статическую коллекцию в качестве значения по умолчанию. Вместо этого необходимо намеренно задавать значение коллекции уникальной (экземплярной) коллекции в рамках логики конструктора класса-владельца свойства коллекции.
// WARNING - DO NOT DO THIS
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
nameof(Items),
typeof(IList<object>),
typeof(ImageWithLabelControl),
new PropertyMetadata(new List<object>())
);
// DO THIS Instead
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
nameof(Items),
typeof(IList<object>),
typeof(ImageWithLabelControl),
new PropertyMetadata(null)
);
public ImageWithLabelControl()
{
// Need to initialize in constructor instead
Items = new List<object>();
}
Значение по умолчанию зависимого свойства и его PropertyMetadata являются частью статического определения DependencyProperty. Предоставив значение коллекции по умолчанию (или другое экземплярное) в качестве значения по умолчанию, оно будет совместно использоваться для всех экземпляров класса вместо каждого класса, имеющего собственную коллекцию, как обычно необходимо.
Уведомления об изменениях
Определение коллекции как свойства зависимостей не обеспечивает автоматическое уведомление об изменении элементов в коллекции в силу системы свойств, вызывающей метод обратного вызова PropertyChanged. Если требуется уведомление о коллекциях или элементах коллекции ( например, для сценария привязки данных), реализуйте интерфейс INotifyPropertyChanged или INotifyCollectionChanged . Дополнительные сведения см. в разделе Подробное изучение привязки данных.
Соображения безопасности свойств зависимостей
Объявите свойства зависимостей как общедоступные свойства. Объявите идентификаторы свойств зависимостей как публичные статические только для чтения элементы. Даже если вы пытаетесь объявить другие уровни доступа, разрешенные языком (например, защищенным), свойство зависимостей всегда можно получить через идентификатор в сочетании с API системы свойств. Объявление идентификатора свойства зависимостей как внутреннего или закрытого не будет работать, так как система свойств не может работать должным образом.
Свойства оболочки действительно предназначены для удобства, механизмы безопасности, применяемые к оболочкам, можно обойти путем вызова GetValue или SetValue . Поэтому оставьте свойства обертки открытыми для доступа; в противном случае вы просто усложняете использование свойств для авторизованных пользователей, не предоставляя при этом никаких реальных преимуществ безопасности.
Среда выполнения Windows не предоставляет способ регистрации пользовательского свойства зависимости только для чтения.
Свойства зависимостей и конструкторы классов
Существует общий принцип, что конструкторы классов не должны вызывать виртуальные методы. Это связано с тем, что конструкторы могут вызываться для выполнения базовой инициализации конструктора производного класса, и вызов виртуального метода через конструктор может выполняться, когда экземпляр объекта еще не полностью инициализирован. Если вы наследуете класс, который уже является производным от DependencyObject, помните, что сама система свойств вызывает и предоставляет виртуальные методы как часть своей функциональности. Чтобы избежать потенциальных проблем с инициализацией во время выполнения, не устанавливайте значения свойств зависимостей в конструкторах классов.
Регистрация свойств зависимостей для приложений C++/CX
Реализация регистрации свойства в C++/CX сложнее, чем C#, как из-за разделения на файл заголовка и реализации, так и из-за того, что инициализация в корневой области файла реализации является плохой практикой. (Расширения компонентов Visual C++ (C++/CX) помещают статический код инициализатора из корневой области непосредственно в DllMain, в то время как компиляторы C# назначают статические инициализаторы классам и таким образом избежать проблем с блокировкой загрузки DllMain .). Рекомендуется объявить вспомогающую функцию, которая выполняет регистрацию всех свойств зависимостей для класса, одну функцию для каждого класса. Затем для каждого пользовательского класса, используемого приложением, потребуется ссылаться на функцию регистрации вспомогательной функции, которая предоставляется каждым пользовательским классом, который вы хотите использовать. Вызовите каждую функцию вспомогательной регистрации один раз в рамках Application constructor (App::App()), перед InitializeComponent. Этот конструктор запускается только при первом обращении к приложению, он не будет запускаться снова, если приостановленное приложение возобновляется, например. Кроме того, как показано в предыдущем примере регистрации C++, проверка nullptr вокруг каждого вызова Register важна: это страхование, что ни одна вызывающая функция не может регистрировать свойство дважды. Повторный вызов регистрации, вероятно, может привести к сбою приложения без такой проверки, так как имя свойства будет дубликатом. Этот шаблон реализации можно увидеть в примере пользовательского и пользовательского элементов управления XAML , если посмотреть код для версии C++/CX примера.
Связанные темы
Windows developer