본문 바로가기

.NET/WPF

[WPF] DependencyProperty에 대해서 - RelativeSource 사용하여 Binding하기

앞선 글에서 DependencyProperty를 사용하여 UserControl의 코드비하인드에 정의한 사용자 정의 속성에 바인딩을 해보았다. 

 

[WPF] DependencyProperty에 대해서

WPF에서 속성과 속성 간의 Binding을 통해 데이터를 View로 가져오거나 ViewModel로 보낼 수 있음을 앞선 글에서 알아보았다. 이러한 Binding이 이뤄지기 위해서는 반드시 필요한 조건이 있는데, Binding 하

cs-solution.tistory.com

바인딩을 하기 위해 PropertyMetadata에 PropertyChangedCallBack 메서드를 사용하여 사용자 정의 속성값이 변경되었을 때 각 컨트롤에 변경된 값들을 직접 지정해줬음을 기억할 것이다.

이번 글에서는 PropertyChangedCallBack 메서드를 이용하지 않고, RelativeSource를 사용하여 XAML코드에서 코드비하인드에 있는 속성값에 직접 바인딩을 해보려고 한다.

 

과감하게 지워주자.

 

지운 후

이제 코드 비하인드에서는 따로 작업이 필요하지 않다.

 

MemberInfoView의 XAML코드로 가보자.

 


이번 주제에 대해 설명하기에 앞서 이전에 작성해둔 코드에서 잘못된 점이 하나 있어 잠시 설명하려고 한다.

기존의 코드는 코드비하인드에서 컨트롤의 Text 속성값을 직접 변경했기 때문에 XAML 코드상에서 각 컨트롤의 Text 속성에 Binding은 필요하지 않았음에도 Binding이 되어 있었다.


RelativeSource를 사용하여 Binding을 할 것이다.

RelativeSource는 Binding할 개체를 찾기 위한 방법을 제공하는 마크업 확장 요소로, 사용하기 어려워 항상 조심스럽게 만드는 녀석이지만 Binding할 개체가 어디에 위치해 있느냐에 따라 해당 개체를 찾아가기 위해서는 피해갈 수 없는 개념이다.

보통 컨트롤 자기 자신의 다른 속성이나, 부모 컨트롤의 속성 등을 찾기 위해서도 사용되며, 이번 주제처럼 가장 상위 컨트롤인 사용자 정의 컨트롤 자체에 정의된 사용자 정의 속성을 찾기 위해서도 사용할 수 있다.

 

RelativeSource를 사용하여 사용자 정의 속성을 바인딩하기 위해서는 아래와 같이 작성한다.

1. {Binding MemberData.ID,

바인딩 대상을 지정했다. MemberData 속성의 멤버 중 ID 속성값을 지정하였다. 이런식으로 중첩된 참조를 타고 들어가 지정할 수 있다.

코드를 작성하는 단계에서 바로 뒤에 나올 RelativeSource~ 부분이 아직 작성되지 않았다면 당연히 MemberData.ID를 입력하는 과정에서 자동완성이 되지 않으니 당황하지 말자.

 

2. RelativeSource=

바인딩 대상을 찾기 위한 RelativeSource를 지정할 것이다.

 

3. {RelativeSource AncestorType=local:MemberInfoView},

새로운 RelativeSource 개체를 생성하여 지정한다는 의미의 확장 요소라고 볼 수 있다. AncestorType 속성은 Type형식으로, Binding 속성을 탐색할 타입을 이곳에 지정한다. 여기서는 현재 UserControl인 MemberInfoView 타입을 지정했다.

 

이걸로 끝이다.

나머지 TextBlock들도 위와 같은 방법으로 RelativeSource를 지정해주자.

빌드 후 실행하여 결과를 확인해보면,

위와 같이 RelativeSource에 지정된 타입에서 MemberData 속성을 잘 찾아서 Binding이 정상적으로 잘 동작하는 것을 확인할 수 있다.

다만 생년월일과 가입일에 대한 출력 포맷이 기본 ToString()으로 변환된 값이기 때문에 원래 코드처럼 yyyy-MM-dd 형식으로 나오지 않는데, 이것은 아래와 같이 StringFormat을 지정하여 해결할 수 있다.

수정 후 실행해보면 정상적으로 잘 나오는 것을 확인할 수 있을 것이다.

 

다만 이러한 경우 검색된 ID가 존재하지 않을 때 각 필드의 값을 하이픈(-)으로 표시하는 기능은 동작하지 않는다. 기존 코드에서는 PropertyChangedCallBack 메서드에서 이러한 내용을 처리했기 때문에 검색된 ID가 존재하지 않는 경우를 의미하는 MemberData가 null인지를 검사하여 대체 행동을 취할 수 있었지만 순수 XAML에서는 이러한 조건부 행동을 구현하기 어렵다.

따라서 가장 쉽게 해결하는 방법은 MemeberData를 가져오는 파트에서 검색된 MemberData가 없을 경우 null을 반환하는 것이 아닌 각 필드의 값을 "-"로 초기화해서 주는 것이다.

DataManager.cs 파일, line: 35~

기존에 res.Count가 0인 경우에는 null을 반환했지만 이제는 새로운 MemberInfoModel을 만들고 각 필드를 초기화하여 반환하도록 변경했다.

 

안타깝게도 이러한 경우에도 Birth와 JoinData를 하이픈으로 표시하지는 못한다. DateTime을 StringFormat yyyy-MM-dd를 사용해서는 어떠한 값을 넣어도 하이픈이 출력되는 경우는 없기 때문이다.

이러한 경우에도 해결하는 방법이 당연히 있는데, 모델쪽에 출력만을 위한 속성을 추가하는 것이다. 아래 코드를 보자.

MemberInfoModel.cs

우선 Birth와 JoinDate에 null을 저장할 수 있도록 Nullable<T>로 타입을 변경해준다. 타입 뒤에 ?를 붙임으로 간단하게 적용할 수 있다. 이렇게 되면 Birth와 JoinDate은 구조체 변수이지만 null을 대입할 수 있다.

다음으로 XAML 코드에서 읽기 전용으로 바인딩하기 위한 Birth_Str와 JoinDate_Str 속성을 추가했다. 이 두 속성은 각각 Birth와 JoinDate가 null인지 검사하고 null이라면 "-"를, null이 아니라면 ToString("yyyy-MM-dd")의 결과를 반환한다.

 

XAML코드에서는 아래와 같이 변경된다. 

항상 문자열을 돌려주며, 날짜값이 있는 경우 yyyy-MM-dd 포맷으로 변환된 값을 반환하기 때문에 StringFormat은 필요없어져서 제거하였다.

DataManager.cs 파일, line: 35~

FindMemberInfo() 에서 검색 결과가 없는 경우 이제는 Birth와 JoinDate에도 null을 넣어서 반환한다.

이제 다 끝났으니 실행하여 테스트해보자.

 

잘 동작하는 것을 확인할 수 있다.

 

전체 코드를 첨부하였으니 궁금한 부분은 확인해보자.

WpfMvvm_RelativeSource.zip
0.11MB