컬렉션 형식 종속성 속성(WPF .NET)

이 문서에서는 컬렉션 형식인 종속성 속성을 구현하기 위한 지침 및 권장 패턴을 제공합니다.

중요

.NET 7 및 .NET 6에 관한 데스크톱 가이드 설명서는 제작 중입니다.

필수 구성 요소

이 문서에서는 독자들이 종속성 속성에 대한 기본 지식을 갖고 있으며 종속성 속성 개요를 읽었다고 가정합니다. XAML(Extensible Application Markup Language)에 익숙하고 WPF 애플리케이션을 작성하는 방법을 알고 있으면 이 문서의 예제를 따라 하는 데 도움이 됩니다.

컬렉션 형식 종속성 속성 구현

일반적으로 종속성 속성의 구현 패턴은 필드 또는 다른 구문 대신 DependencyProperty 식별자가 지원하는 CLR 속성 래퍼입니다. 컬렉션 형식 종속성 속성을 구현할 때 동일한 패턴을 따를 수 있습니다. 컬렉션 요소 형식이 DependencyObject 또는 Freezable 파생 클래스인 경우 패턴이 더 복잡합니다.

컬렉션 초기화

종속성 속성을 만들 때 일반적으로 초기 속성 값을 지정하는 대신 종속성 속성 메타데이터를 통해 기본값을 지정합니다. 그러나 속성 값이 참조 형식인 경우 종속성 속성을 등록하는 클래스의 생성자에서 기본값을 설정해야 합니다. 종속성 속성 메타데이터에는 기본 참조 형식 값이 포함되면 안 됩니다. 이 값은 클래스의 모든 인스턴스에 할당되어 싱글톤 클래스를 만들기 때문입니다.

다음 예제에서는 제네릭 List<T>FrameworkElement 요소 컬렉션을 포함하는 Aquarium 클래스를 선언합니다. 기본 컬렉션 값은 RegisterReadOnly(String, Type, Type, PropertyMetadata) 메서드로 전달된 PropertyMetadata에 포함되지 않으며, 대신 클래스 생성자를 사용하여 기본 컬렉션 값을 새 제네릭 List로 설정합니다.

public class Aquarium : DependencyObject
{
    // Register a dependency property with the specified property name,
    // property type, owner type, and property metadata.
    private static readonly DependencyPropertyKey s_aquariumContentsPropertyKey =
        DependencyProperty.RegisterReadOnly(
          name: "AquariumContents",
          propertyType: typeof(List<FrameworkElement>),
          ownerType: typeof(Aquarium),
          typeMetadata: new FrameworkPropertyMetadata()
          //typeMetadata: new FrameworkPropertyMetadata(new List<FrameworkElement>())
        );

    // Set the default collection value in a class constructor.
    public Aquarium() => SetValue(s_aquariumContentsPropertyKey, new List<FrameworkElement>());

    // Declare a public get accessor.
    public List<FrameworkElement> AquariumContents =>
        (List<FrameworkElement>)GetValue(s_aquariumContentsPropertyKey.DependencyProperty);
}

public class Fish : FrameworkElement { }
Public Class Aquarium
    Inherits DependencyObject

    ' Register a dependency property with the specified property name,
    ' property type, owner type, and property metadata.
    Private Shared ReadOnly s_aquariumContentsPropertyKey As DependencyPropertyKey =
        DependencyProperty.RegisterReadOnly(
            name:="AquariumContents",
            propertyType:=GetType(List(Of FrameworkElement)),
            ownerType:=GetType(Aquarium),
            typeMetadata:=New FrameworkPropertyMetadata())
            'typeMetadata:=New FrameworkPropertyMetadata(New List(Of FrameworkElement)))

    ' Set the default collection value in a class constructor.
    Public Sub New()
        SetValue(s_aquariumContentsPropertyKey, New List(Of FrameworkElement)())
    End Sub

    ' Declare a public get accessor.
    Public ReadOnly Property AquariumContents As List(Of FrameworkElement)
        Get
            Return CType(GetValue(s_aquariumContentsPropertyKey.DependencyProperty), List(Of FrameworkElement))
        End Get
    End Property
End Class

Public Class Fish
    Inherits FrameworkElement
End Class

다음 테스트 코드는 두 개의 개별 Aquarium 인스턴스를 인스턴스화하고 각 컬렉션에 다른 Fish 항목을 추가합니다. 이 코드를 실행하면 각 Aquarium 인스턴스에 예상대로 단일 컬렉션 항목이 있습니다.

private void InitializeAquariums(object sender, RoutedEventArgs e)
{
    Aquarium aquarium1 = new();
    Aquarium aquarium2 = new();
    aquarium1.AquariumContents.Add(new Fish());
    aquarium2.AquariumContents.Add(new Fish());
    MessageBox.Show(
        $"aquarium1 contains {aquarium1.AquariumContents.Count} fish\r\n" +
        $"aquarium2 contains {aquarium2.AquariumContents.Count} fish");
}
Private Sub InitializeAquariums(sender As Object, e As RoutedEventArgs)
    Dim aquarium1 As New Aquarium()
    Dim aquarium2 As New Aquarium()
    aquarium1.AquariumContents.Add(New Fish())
    aquarium2.AquariumContents.Add(New Fish())
    MessageBox.Show($"aquarium1 contains {aquarium1.AquariumContents.Count} fish{Environment.NewLine}" +
                    $"aquarium2 contains {aquarium2.AquariumContents.Count} fish")
End Sub

그러나 클래스 생성자를 주석으로 처리하고 기본 컬렉션 값을 PropertyMetadataRegisterReadOnly(String, Type, Type, PropertyMetadata) 메서드에 전달하면 각 Aquarium 인스턴스가 두 개의 컬렉션 항목을 가져옵니다! 이는 두 Fish 인스턴스가 동일한 목록에 추가되고, 해당 목록은 Aquarium 클래스의 모든 인스턴스에서 공유되기 때문입니다. 따라서 각 개체 인스턴스가 고유한 목록을 가지도록 하려는 경우 클래스 생성자에서 기본값을 설정해야 합니다.

읽기/쓰기 컬렉션 초기화

다음 예제에서는 키가 아닌 서명 메서드 Register(String, Type, Type)SetValue(DependencyProperty, Object)를 사용하여 Aquarium 클래스에서 읽기/쓰기 컬렉션 형식 종속성 속성을 선언합니다.

public class Aquarium : DependencyObject
{
    // Register a dependency property with the specified property name,
    // property type, and owner type. Store the dependency property
    // identifier as a public static readonly member of the class.
    public static readonly DependencyProperty AquariumContentsProperty =
        DependencyProperty.Register(
          name: "AquariumContents",
          propertyType: typeof(List<FrameworkElement>),
          ownerType: typeof(Aquarium)
        );

    // Set the default collection value in a class constructor.
    public Aquarium() => SetValue(AquariumContentsProperty, new List<FrameworkElement>());

    // Declare public get and set accessors.
    public List<FrameworkElement> AquariumContents
    {
        get => (List<FrameworkElement>)GetValue(AquariumContentsProperty);
        set => SetValue(AquariumContentsProperty, value);
    }
}
Public Class Aquarium
    Inherits DependencyObject

    ' Register a dependency property with the specified property name,
    ' property type, and owner type. Store the dependency property
    ' identifier as a static member of the class.
    Public Shared ReadOnly AquariumContentsProperty As DependencyProperty =
        DependencyProperty.Register(
            name:="AquariumContents",
            propertyType:=GetType(List(Of FrameworkElement)),
            ownerType:=GetType(Aquarium))

    ' Set the default collection value in a class constructor.
    Public Sub New()
        SetValue(AquariumContentsProperty, New List(Of FrameworkElement)())
    End Sub

    ' Declare public get and set accessors.
    Public Property AquariumContents As List(Of FrameworkElement)
        Get
            Return CType(GetValue(AquariumContentsProperty), List(Of FrameworkElement))
        End Get
        Set
            SetValue(AquariumContentsProperty, Value)
        End Set
    End Property
End Class

FreezableCollection 종속성 속성

컬렉션 형식 종속성 속성은 해당 하위 속성의 변경 내용을 자동으로 보고하지 않습니다. 따라서 컬렉션에 바인딩하는 경우 바인딩이 변경 내용을 보고하지 않아 일부 데이터 바인딩 시나리오가 무효화될 수 있습니다. 그러나 종속성 속성 형식에 FreezableCollection<T>을 사용하는 경우 컬렉션 요소의 속성에 대한 변경 내용이 올바르게 보고되고 바인딩이 예상대로 작동합니다.

종속성 개체 컬렉션에서 하위 속성 바인딩을 사용하도록 설정하려면 DependencyObject 파생 클래스의 형식 제약 조건과 함께 컬렉션 형식 FreezableCollection을 사용합니다.

다음 예제에서는 형식 제약 조건 FrameworkElement와 함께 FreezableCollection을 포함하는 Aquarium 클래스를 선언합니다. 기본 컬렉션 값은 RegisterReadOnly(String, Type, Type, PropertyMetadata) 메서드로 전달된 PropertyMetadata에 포함되지 않으며, 대신 클래스 생성자를 사용하여 기본 컬렉션 값을 새 FreezableCollection로 설정합니다.

public class Aquarium : DependencyObject
{
    // Register a dependency property with the specified property name,
    // property type, and owner type.
    private static readonly DependencyPropertyKey s_aquariumContentsPropertyKey =
        DependencyProperty.RegisterReadOnly(
          name: "AquariumContents",
          propertyType: typeof(FreezableCollection<FrameworkElement>),
          ownerType: typeof(Aquarium),
          typeMetadata: new FrameworkPropertyMetadata()
        );

    // Store the dependency property identifier as a static member of the class.
    public static readonly DependencyProperty AquariumContentsProperty =
        s_aquariumContentsPropertyKey.DependencyProperty;

    // Set the default collection value in a class constructor.
    public Aquarium() => SetValue(s_aquariumContentsPropertyKey, new FreezableCollection<FrameworkElement>());

    // Declare a public get accessor.
    public FreezableCollection<FrameworkElement> AquariumContents =>
        (FreezableCollection<FrameworkElement>)GetValue(AquariumContentsProperty);
}
Public Class Aquarium
    Inherits DependencyObject

    ' Register a dependency property with the specified property name,
    ' property type, and owner type.
    Private Shared ReadOnly s_aquariumContentsPropertyKey As DependencyPropertyKey =
        DependencyProperty.RegisterReadOnly(
            name:="AquariumContents",
            propertyType:=GetType(FreezableCollection(Of FrameworkElement)),
            ownerType:=GetType(Aquarium),
            typeMetadata:=New FrameworkPropertyMetadata())

    ' Set the default collection value in a class constructor.
    Public Sub New()
        SetValue(s_aquariumContentsPropertyKey, New FreezableCollection(Of FrameworkElement)())
    End Sub

    ' Declare a public get accessor.
    Public ReadOnly Property AquariumContents As FreezableCollection(Of FrameworkElement)
        Get
            Return CType(GetValue(s_aquariumContentsPropertyKey.DependencyProperty), FreezableCollection(Of FrameworkElement))
        End Get
    End Property
End Class

참고 항목