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


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

Здесь мы объясним, как определить и реализовать собственные свойства зависимостей для приложения среда выполнения 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.BackgroundControl.BackgroundProperty. Идентификатор хранит сведения о свойстве зависимостей по мере его регистрации, а затем можно использовать для других операций с использованием свойства зависимостей, таких как вызов SetValue.

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

Свойства зависимостей обычно имеют реализацию оболочки. Без оболочки единственный способ получить или задать свойства — использовать методы getValue и SetValue свойства зависимости и передать идентификатор в качестве параметра. Это довольно неестественное использование для чего-то, что якобы является свойством. Но при использовании оболочки код и любой другой код, ссылающийся на свойство зависимостей, может использовать простой синтаксис свойства объекта, естественный для используемого языка.

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

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

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

Вы можете рассмотреть возможность реализации свойства в качестве свойства зависимостей, если вы хотите поддерживать одну или несколько этих функций среда выполнения Windows или приложений среда выполнения Windows:

  • Настройка свойства с помощью стиля
  • Действие в качестве допустимого целевого свойства для привязки данных с помощью {Binding}
  • Поддержка анимированных значений с помощью раскадровки
  • Сообщает, когда значение свойства было изменено следующим образом:
    • Действия, выполняемые самой системой свойств
    • Среда
    • Действия пользователя
    • Чтение и написание стилей

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

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

  • Зарегистрируйте имя свойства в системе свойств (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 атрибуция, атрибут в заголовке тоже. Поместите вызов 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 причина, из-за которой у вас есть частное поле и общедоступное свойство только для чтения, которое отображает зависимостьProperty , так что другие вызывающие лица, использующие свойство зависимостей, также могут использовать 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 в реализации набора.

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

Во всех случаях, кроме исключительных обстоятельств, реализация оболочки должна выполнять только операции 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 , предоставляющий одну или обе эти возможности.

Как правило, вы предоставляете свойствоMetadata в качестве встроенного экземпляра в параметрах для 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))
    );

Default value

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

Если значение по умолчанию не указано, значение по умолчанию для свойства зависимостей имеет значение 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, всегда связано с текущим потоком 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>();
}

Значение по умолчанию DependencyProperty и его PropertyMetadata являются частью статического определения зависимостейProperty. Предоставив значение коллекции по умолчанию (или другое экземплярное) в качестве значения по умолчанию, оно будет совместно использоваться для всех экземпляров класса вместо каждого класса, имеющего собственную коллекцию, как обычно необходимо.

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

Определение коллекции как свойства зависимостей не обеспечивает автоматическое уведомление об изменении элементов в коллекции в силу системы свойств, вызывающей метод обратного вызова PropertyChanged. Если требуется уведомление о коллекциях или элементах коллекции ( например, для сценария привязки данных), реализуйте интерфейс INotifyPropertyChanged или INotifyCollectionChanged . Дополнительные сведения см. в подробной статье о привязке данных.

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

Объявите свойства зависимостей как общедоступные свойства. Объявите идентификаторы свойств зависимостей как общедоступные статические элементы чтения . Даже если вы пытаетесь объявить другие уровни доступа, разрешенные языком (например , защищенным), свойство зависимостей всегда можно получить через идентификатор в сочетании с API системы свойств. Объявление идентификатора свойства зависимостей как внутреннего или закрытого не будет работать, так как система свойств не может работать должным образом.

Свойства оболочки действительно предназначены для удобства, механизмы безопасности, применяемые к оболочкам, можно обойти путем вызова GetValue или SetValue. Поэтому оставьте свойства оболочки общедоступными; в противном случае вы просто усложняете использование имущества для законных абонентов без предоставления каких-либо реальных преимуществ безопасности.

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

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

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

Регистрация свойств зависимостей для приложений C++/CX

Реализация регистрации свойства в C++/CX сложнее, чем C#, как из-за разделения на файл заголовка и реализации, так и из-за того, что инициализация в корневой области файла реализации является плохой практикой. (Расширения компонентов Visual C++ (C++/CX) помещают статический код инициализатора из корневой области непосредственно в DllMain, в то время как компиляторы C# назначают статические инициализаторы классам и таким образом избежать проблем с блокировкой загрузки DllMain .). Рекомендуется объявить вспомогающую функцию, которая выполняет регистрацию всех свойств зависимостей для класса, одну функцию для каждого класса. Затем для каждого пользовательского класса, используемого приложением, потребуется ссылаться на функцию регистрации вспомогательной функции, которая предоставляется каждым пользовательским классом, который вы хотите использовать. Вызовите каждую функцию вспомогательной регистрации один раз в составе конструктора приложений (App::App()), до InitializeComponentэтого. Этот конструктор запускается только при первом обращении к приложению, он не будет запускаться снова, если приостановленное приложение возобновляется, например. Кроме того, как показано в предыдущем примере регистрации C++, проверка nullptr вокруг каждого вызова Register важна: это страхование, что ни одна вызывающая функция не может регистрировать свойство дважды. Второй вызов регистрации, вероятно, завершится сбоем приложения без такой проверки, так как имя свойства будет дубликатом. Этот шаблон реализации можно увидеть в примере пользовательского и пользовательского элементов управления XAML, если посмотреть код для версии C++/CX примера.