본문 바로가기

.NET/WPF

[WPF] DependencyProperty에 대해서

WPF에서 속성과 속성 간의 Binding을 통해 데이터를 View로 가져오거나 ViewModel로 보낼 수 있음을 앞선 글에서 알아보았다.

이러한 Binding이 이뤄지기 위해서는 반드시 필요한 조건이 있는데, Binding 하려는 속성이 DependencyProperty(의존 속성) 여야 한다는 것이다. 보통 컨트롤의 속성과 ViewModel의 속성 간에 Binding을 하는데, 기본적으로 WPF에서 제공하는 컨트롤의 대부분의 속성은 의존 속성으로 구현이 되어 있기 때문에 DependencyProperty에 대한 개념을 알지 못해도 사용할 수 있었다.

아마 DependencyProperty의 존재에 대해 처음 직면하게 되는 경우는 사용자 정의 컨트롤(이하 UserControl)로 데이터 묶음을 Binding하려고 했을 때가 아닐까 싶다.

 


아래에서 설명하는 방법은 DependencyProperty를 사용하지 않았을 때 발생하는 오류를 보이기 위한 예제입니다.

DependencyProperty의 사용 방법을 바로 확인하려면 이 다음 문단으로 이동하세요.

 

DependencyProperty의 사용법에 대해 알아보기 위해 지난번에 만들었던 유저 정보 검색 기능을 수정해보려고 한다.

아래 컨트롤에서 빨간색 영역을 UserControl로 분리해보자.

각 필드로 들어갈 값들이 MemberInfoModel 클래스에 멤버 속성으로 구성되어 있는데, 이 MemberInfoModel 개체가 UserControl의 MemberData 속성에 통째로 Binding 되게 구성하는 것이 목적이다.

 

MemberInfoView.xaml

새로 구성한 UserControl의 XAML 코드는 기존의 MainWindowView에서 그대로 가져왔으나, 코드 비하인드에서 각 값이 들어갈 컨트롤에 접근할 수 있도록 위와 같이 이름을 부여했다.

 

UserControl 자체의 속성에 MainWindowView에서 Binding을 해야 하는데, 그러기 위해서는 이 UserControl의 코드 비하인드에 사용자 정의 속성이 추가되어야 한다. 앞서 각 컨트롤들에 이름을 부여한 것은 UserControl의 코드 비하인드에서 자기 자신이 가지는 각 컨트롤들의 이름을 통해 직접 속성값을 지정할 수 있어야 하기 때문이다.

MemberInfoView.xaml.cs

위와 같이 새로 생성한 MemberInfoView UserControl의 코드 비하인드에 새로운 속성을 추가한다.

편의를 위해 설정 전용 속성으로 생성했다.

 

MainWindowViewModel에 MemberInfoView의 속성과 Binding될 새로운 속성을 추가하고, MainWindowView에는 아래와 같이 생성한 UserControl을 추가했다.

MainWindowViewModel.cs
MainWindowView.xaml

 

그리고 실행을 했더니...

 

위와 같이 'Binding은 DependencyObject의 DependencyProperty에서만 설정할 수 있습니다.'라는 메시지와 함께 예외가 발생한다.

이는 일반적으로 컨트롤의 속성들(예를 들어 TextBlock.Text나 Button.Content 같은)과는 다르게 UserControl의 사용자 정의 속성인 MemberData가 DependencyProperty가 아니기 때문에 발생하는 것이다.

 

따라서 사용자 정의 속성에 Binding하기 위해서는 속성을 DependencyProperty로 만들어줘야 하며, 지금부터 그 방법을 알아보겠다.

 


속성을 DependencyProperty로 설정하기 위해서는 해당 속성이 위치한 코드 비하인드에서만 작업하면 된다.

 

1. 속성을 DependencyProperty에 등록한다.

모든 의존 속성은 DependencyProperty 목록에 등록된 후에 사용될 수 있다. 아래와 같이 DependencyProperty에 사용자 정의 속성을 등록한다.

public static readonly DependencyProperty MemberDataProperty = DependencyProperty.Register("MemberData",
                                                                                           typeof(MemberInfoModel),
                                                                                           typeof(MemberInfoView),
                                                                                           new PropertyMetadata(null, OnMemberDataChanged));

아래 설명을 참고하자.

  • DependencyProperty는 public, static, readonly로 지정한다.
  • DependencyProperty의 이름은 실제 사용할 속성 명 뒤에 Property를 붙여서 지정한다. (마이크로소프트 설명서에서는 이렇게 하지 않을 경 예기치 못한 오류가 발생할 수 있음을 경고하고 있다.)
  • DependencyProperty.Register( <속성 이름>, <속성의 타입>, <속성을 사용할 컨트롤의 타입>, <속성 메타 데이터 개체>); 와 같이 인수를 지정한다.
  • PropertyMetadata 개체는 new PropertyMetadata(<해당 속성의 기본값>, <속성값이 변화됐을 때 발생될 콜백 메서드>) 로 지정한다.
  • 속성 값이 변화됐을 때 발생될 콜백 메서드는 선택 사항이다. 사용하지 않아도 되는 경우에는 없애도 된다.
  • 콜백 메서드의 매개변수 리스트는 DependencyObject, DependencyPropertyChangedEventArgs이다.

2. 실제 사용될 속성을 추가한다.

public MemberInfoModel MemberData
{
    set => SetValue(MemberDataProperty, value);
    get => (MemberInfoModel)GetValue(MemberDataProperty);
}

속성을 정의하는 방법은 위 방법으로 고정이다. 속성의 Setter 또는 Getter에서는 SetValue(), GetValue()를 호출하는 것 이외의 다른 작업을 진행하는 것은 하지 말것을 권장한다.(미숙한 사용으로 인한 문제, 예를 들어 재귀 호출로 인한 스택 오버플로우 등의 오류가 발생할 수 있다.)

 

3. 콜백 메서드를 추가한다. (선택 사항)

private static void OnMemberDataChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
    if ( d is MemberInfoView control )
    {
        if ( e.NewValue is MemberInfoModel value )
        {
            control.textBlock_ID.Text = value.ID;
            control.textBlock_Name.Text = value.Name;
            control.textBlock_Contact.Text = value.Contact;
            control.textBlock_Address.Text = value.Address;
            control.textBlock_Birth.Text = value.Birth.ToString( "yyyy-MM-dd" );
            control.textBlock_JoinDate.Text = value.JoinDate.ToString( "yyyy-MM-dd" );
        }
        else
        {
            control.textBlock_ID.Text = "-";
            control.textBlock_Name.Text = "-";
            control.textBlock_Contact.Text = "-";
            control.textBlock_Address.Text = "-";
            control.textBlock_Birth.Text = "-";
            control.textBlock_JoinDate.Text = "-";
        }
    }
}

콜백 메서드의 매개변수인 DependencyObject d에는 변경된 속성을 멤버로 가지는 컨트롤 개체가 지정된다. (일반적인 이벤트 메서드에서의  sender와 같다.)

DependencyPropertyChangedEventArgs e에는 속성의 이전 값(OldValue), 새로운 값(NewValue), 해당 DependencyProperty의 등록 정보(Property) 등이 포함되어 있다.

 

이제 DependencyProperty의 등록이 끝났다. 바로 실행하면 아래와 같이 정상적으로 동작하는 것을 확인할 수 있다.

 

완성된 MemberInfoView의 코드 비하인드는 다음과 같다.

/// <summary>
/// UserInfoView.xaml에 대한 상호 작용 논리
/// </summary>
public partial class MemberInfoView : UserControl
{
    public static readonly DependencyProperty MemberDataProperty = DependencyProperty.Register( "MemberData",
                                                                                                typeof( MemberInfoModel ),
                                                                                                typeof( MemberInfoView ),
                                                                                                new PropertyMetadata( null, OnMemberDataChanged ) );

    private static void OnMemberDataChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
    {
        if ( d is MemberInfoView control )
        {
            if ( e.NewValue is MemberInfoModel value )
            {
                control.textBlock_ID.Text = value.ID;
                control.textBlock_Name.Text = value.Name;
                control.textBlock_Contact.Text = value.Contact;
                control.textBlock_Address.Text = value.Address;
                control.textBlock_Birth.Text = value.Birth.ToString( "yyyy-MM-dd" );
                control.textBlock_JoinDate.Text = value.JoinDate.ToString( "yyyy-MM-dd" );
            }
            else
            {
                control.textBlock_ID.Text = "-";
                control.textBlock_Name.Text = "-";
                control.textBlock_Contact.Text = "-";
                control.textBlock_Address.Text = "-";
                control.textBlock_Birth.Text = "-";
                control.textBlock_JoinDate.Text = "-";
            }
        }
    }

    public MemberInfoModel MemberData
    {
        set => SetValue( MemberDataProperty, value );
        get => ( MemberInfoModel )GetValue( MemberDataProperty );
    }

    private MemberInfoModel _memberData;

    public MemberInfoView()
    {
        InitializeComponent();
    }

Tip. DependencyProperty를 추가하기 위한 위 과정을 VisualStudio에서 매크로로 지원한다.

코드 에디터에 propdp를 입력하면 아래와 같이 자동 완성 창이 표시된다.

Tab 키를 두 번 누르면 DependencyProperty를 추가하기 위한 기본 틀을 자동으로 완성해준다.

여기서 속성의 타입, 이름, 소유자 클래스 타입, 기본값 등만 간단하게 수정하여 사용할 수 있다. (화면상의 강조 표시된 부분)

 

 

 

 

DependencyProperty를 생성하는 부분에 대한 설명만 이곳에 작성하였고 실제 코드에서 추가적으로 작업된 내용이 일부 있어 전체 코드를 함께 첨부한다.

WpfMvvm_DependencyProperty.zip
0.11MB

 

 

'.NET > WPF' 카테고리의 다른 글

[WPF] XAML 코드 상에서 DataContext 지정하기  (0) 2023.01.03
[WPF] Binding 기초  (0) 2022.11.25
[WPF] MVVM 디자인 패턴 (3) - ViewModelBase  (0) 2022.11.18
[WPF] MVVM 디자인 패턴 (2)  (0) 2022.11.13
[WPF] MVVM 디자인 패턴 (1)  (0) 2022.11.10