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


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

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

В этом разделе содержатся следующие подразделы.

  • Реализация свойства зависимостей типа коллекция
  • Инициализация коллекции со значением не по умолчанию
  • Сообщение об изменениях значений привязки из свойств коллекции
  • Связанные разделы

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

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

Инициализация коллекции со значением не по умолчанию

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

Рассмотрим следующий пример. В следующем разделе примера показано определение класса Aquarium. Класс определяет свойство зависимостей типа коллекция AquariumObjects, которое использует базовый тип List<T> с ограничением типа FrameworkElement. В вызове Register(String, Type, Type, PropertyMetadata) для свойства зависимости метаданные устанавливают значение по умолчанию новым базовым List<T>.

    Public Class Fish
        Inherits FrameworkElement
    End Class
    Public Class Aquarium
        Inherits DependencyObject
        Private Shared ReadOnly AquariumContentsPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("AquariumContents", GetType(List(Of FrameworkElement)), GetType(Aquarium), New FrameworkPropertyMetadata(New List(Of FrameworkElement)()))
        Public Shared ReadOnly AquariumContentsProperty As DependencyProperty = AquariumContentsPropertyKey.DependencyProperty

        Public ReadOnly Property AquariumContents() As List(Of FrameworkElement)
            Get
                Return CType(GetValue(AquariumContentsProperty), List(Of FrameworkElement))
            End Get
        End Property


...


    End Class
public class Fish : FrameworkElement { }
public class Aquarium : DependencyObject
{
    private static readonly DependencyPropertyKey AquariumContentsPropertyKey = 
        DependencyProperty.RegisterReadOnly(
          "AquariumContents",
          typeof(List<FrameworkElement>),
          typeof(Aquarium),
          new FrameworkPropertyMetadata(new List<FrameworkElement>())
        );
    public static readonly DependencyProperty AquariumContentsProperty =
        AquariumContentsPropertyKey.DependencyProperty;

    public List<FrameworkElement> AquariumContents
    {
        get { return (List<FrameworkElement>)GetValue(AquariumContentsProperty); }


...


}

Однако, если оставить код как показано, это одно приведенное значение по умолчанию будет совместно используемым значением всеми экземплярами Aquarium. Если выполнить следующий код проверки, предназначенный для того, чтобы показать, как будут созданы два отдельных экземпляра Aquarium и как к каждому из них будет добавлен один отличный Fish, обнаружится интересный результат:

          Dim myAq1 As New Aquarium()
          Dim myAq2 As New Aquarium()
          Dim f1 As New Fish()
          Dim f2 As New Fish()
          myAq1.AquariumContents.Add(f1)
          myAq2.AquariumContents.Add(f2)
          MessageBox.Show("aq1 contains " & myAq1.AquariumContents.Count.ToString() & " things")
          MessageBox.Show("aq2 contains " & myAq2.AquariumContents.Count.ToString() & " things")
Aquarium myAq1 = new Aquarium();
Aquarium myAq2 = new Aquarium();
Fish f1 = new Fish();
Fish f2 = new Fish();
myAq1.AquariumContents.Add(f1);
myAq2.AquariumContents.Add(f2);
MessageBox.Show("aq1 contains " + myAq1.AquariumContents.Count.ToString() + " things");
MessageBox.Show("aq2 contains " + myAq2.AquariumContents.Count.ToString() + " things");

В каждой коллекции вместо числа 1 будет 2. Это происходит потому, что каждый Aquarium добавляет свой Fish в коллекцию значений по умолчанию, созданную в результате единственного вызова конструктора в метаданных и совместно используемую в дальнейшем всеми экземплярами. Эта ситуация почти никогда не является требуемым результатом.

Чтобы устранить эту проблему, необходимо задать значение свойства зависимости коллекции уникальным экземпляром в составе вызова конструктора класса. Так как свойство зависимости доступно только для чтения, для его задания используется метод SetValue(DependencyPropertyKey, Object) с использованием DependencyPropertyKey, являющегося единственно доступным в классе.

        Public Sub New()
            MyBase.New()
            SetValue(AquariumContentsPropertyKey, New List(Of FrameworkElement)())
        End Sub
public Aquarium() : base()
{
    SetValue(AquariumContentsPropertyKey, new List<FrameworkElement>()); 
}

Теперь, если запустить тот же код проверки еще раз, можно увидеть более ожидаемых результаты: каждый Aquarium поддерживает свою собственную уникальную коллекцию.

Если бы свойство коллекции было доступным и для чтения, и для записи, шаблон слегка отличался бы от этого. В таком случае можно было бы вызвать открытый доступ из конструктора для выполнения инициализации, он бы так же вызвал не ключевую сигнатуру SetValue(DependencyProperty, Object) внутри обертки записи при помощи открытого идентификатора DependencyProperty.

Сообщение об изменениях значений привязки из свойств коллекции

Свойство коллекции, которое само является свойством зависимости, автоматически не сообщает об изменениях своим подсвойствам. При создании привязки в коллекции привязке не будет сообщено об изменениях и следовательно нарушатся некоторые сценарии привязки данных. Однако, если использовать тип коллекции FreezableCollection<T> в качестве типа коллекции, то об изменениях субсвойств в содержащихся в коллекции элементах будет сообщено, и привязка будет работать, как ожидалось.

Чтобы включить привязку подсвойства в объектной коллекции зависимости, создайте свойство коллекции типа FreezableCollection<T> с ограничением типа для этой коллекции любым классом, производным от DependencyObject.

См. также

Ссылки

FreezableCollection<T>

Основные понятия

Код XAML и пользовательские классы для WPF

Общие сведения о связывании данных

Общие сведения о свойствах зависимости

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

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