DependencyProperty Value Inheritance

프로그래밍 2009.08.26 12:00 Posted by 아일레프

의존 프로퍼티에 대해서 하나 더 포스팅 합니다. 사실 이 포스팅을 하면서 두려운 마음이 많이 드는데, 제가 WPF를 만든 사람도 아닐 뿐 더러 아래 내용은 어디까지나 저 개인의 추측과 실험으로 나온 결과이기 때문입니다. 무책임 한 말일 수 있지만, 절대 아래 내용을 완전히 신뢰하지 마시기 바랍니다. 옳고 그름을 스스로 판단해주시고, 혹시 틀린 부분이 있나, 더 개선될 수 있는 부분이 있다면 댓글 남겨주시면 감사드리겠습니다. 그리고 아래의 내용은 결론을 얻기 까지 제 사고의 흐름입니다. 너무 길어 보기 싫으신 분은 가장 아래의 결론 부분만 읽어 주세요.

일단 의존 프로퍼티에 대한 정의를 한번 하고 넘어가겠습니다. 제가 가장 좋아하는 의존 프로퍼티에 대한 정의는 페졸드 아저씨의 정의입니다.

"It is a property that depend on number of other properties and outside influences"

다른 환경에 의해 영향을 받는(또는 의존하는) Property라는 것이죠. 이런 의존 프로퍼티에 대한 개념을 잡아 주기 위해 대부분의 책들이 Property Inheritance를 예로 듭니다. 오늘의 이슈는 바로 이녀석입니다.

 위와 같이 VisualTree가 구성되어있을 때 Window의 FontSize를 30으로 만들면 Button의 FontSize도 자동으로 30으로 설정되죠. 즉 Button의 FontSize라는 Property는 Button의 부모인 Window의 FontSize Property에 영향을 받고 있습니다. 그런데 이게 어떻게 가능할까요?

   

Property Inheritance를 위한 부모와 자식간의 관계는 어떻게 설정되는 것일까?

위와 같은 그림에서 Window와 Button은 직접 부모, 자식 관계로 이어져 있을 까요? 답은 그럴 수도 있고, 아닐 수도 있습니다. 위 Window와 Button은 VisualTree내에서 직접적인 부모, 자식 관계가 아닙니다. 가장 최소화된 VisualTree라 하더라도 Window->ContentPresenter->Button으로서 직접적인 부모, 자식으로 이어지지 않습니다. 하지만 Logical Tree내에서는 직접적인 부모, 자식간의 관계로 이루어져 있습니다. 조금 만 더 깊게 들어가보면 VisualTree는 Window의 Template과 ContentTemplate에 의해 영향을 받게 된다는 것을 알 수 있습니다. 그렇다면 LogicalTree는 어떻게 구성되는 걸까요? 질문 1. LogicalTree는 단지 VisualTree를 단순화한 Tree일까요?

여기서 알아야할 중요한 사실이 있는데, 특정 Control A의 Logical Tree내의 자손이 Control A의 VisualTree내의 자손이 아닐 수도 있다는 사실입니다. 이것을 가장 크게 보여주는 예가 바로 Grid입니다. Grid의 LogicalTree내에는 Grid의 Child로 지정된 UIElement들 뿐 아니라 Visual객체가 아닌 RowDefinition, ColumnDefinition도 존재하고 있습니다. 질문 1의 해답. LogicalTree는 VisualTree를 단순화한 Tree가 아니다. LogicalTree와 VisualTree를 별개로 보는 것이 오히려 타당하다.

   

질문 2. VisualTree는 Template에 의해 결정된다고 말한 바 있습니다. 그렇다면 LogicalTree는 어떻게 구성되는 걸까요?

질문 3. Property Inheritance의 혜택은 Visual Tree를 통해서 얻어 질 수 있을까요, 아니면 Logical Tree로 얻을 수 있을 까요? 그게 아니면 둘 다일까요?

위 질문들의 해답은 Of logical and visual trees in WPF를 보면 얻을 수 있습니다. 결론만 말하겠습니다.

질문 2의 해답 : LogicalTree는 Template이 아닌 명시적으로 구현해 주어야 합니다. FrameworkElement를 상속받은 객체는 AddLogicalChildren이라는 메소드를 가지는데 이 메소드를 통해서 특정 객체를 자신의 LogicalChildren으로 넣을 수 있고, LogicalChild의 모든 이점을 얻을 수 있습니다. 그런데 이렇게 AddLogicalChildren을 이용해 넣은 후에도 LogicalTreeHelper를 통해 검색해도 해당 객체가 보이지 않을 수 있는데, 이것은 명시적으로 LogicalChildren을 override해주지 않았기 때문입니다.

  1. AddLogicalChildren을 통해 LogicalChild에 추가하고 싶은 객체를 추가한다.(필수)
  2. LogicalChildren을 override해 LogicalTreeHelper를 통해 해당 객체가 검색될 수 있도록 한다.(optional)

질문 3의 해답 : VisualTree, LogicalTree 모든 Tree를 통해서 Property Inheritance의 혜택을 얻을 수 있습니다. 단 해당 member가 LogicalTree에도 속해있고, VisualTree에도 속해있을 경우 LogicalTree의 Parent의 Property가 Child에게 Inherit됩니다.

이는 Of logical and visual trees in WPF에도 나와 있는 간단한 실험을 통해 확인 할 수 있습니다.

위와 같은 상황에서 ContentControl의 Content인 TextBlock은 ContentControl의 Logical Child이며, Label의 Visual Child이기도 합니다. 따라서 TextBlock의 FontSize는 40이 됩니다. 만약 ContentControl의 FontSize를 40으로 설정하지 않았다면 TextBlock의 FontSize는 20이 되겠죠.

   

그럼 실제 Property Inheritance의 구현을 유추해봅시다. (아래의 내용은 실제 WPF가 의존 Property를 설정하는 방식이 절대 아닙니다.)

Public double FontSize
{
  Get{ return (doble)GetValue(FontSizeProperty);}
  Set{ SetValue(FontSizeProperty, value);}
}

이는 곧 위 코드의 SetValue의 구현을 유추해본다는 의미입니다.(오직 PropertyInheritance만 지원하는) 다음과 같은 논리라면 가능할 것 같습니다.

Private void SetValue(DependencyProperty dependencyProperty, value)
{
    1.
value를 "dependencyProperty value Dictionary에 넣는다." (무슨 말인지 모르시는 분은 이전 포스팅 왜 Dependency Property는 public static readonly로 선언하는가?를 읽어 보시기 바랍니다.) 

    2.
dependencyProperty가 Inheritance를 허용하지 않는다면 메소드를 빠져나온다. :: 이는 해당 DependencyProperty의 metadata를 통해 알 수 있습니다.

    3.
VisualTree상의 자기 자식들이 해당 dependencyProperty를 소유하고 있는가? 
       
만약 해당 dependencyProperty를 소유하고 있으며, 해당 Property의 value가 명시적으로 설정되지 않았다면(Property Inheritance보다 우선순위가 높은 환경에 의해 설정되지 않았다면) 자식의 Property값을 변경한다. 

    4.
LogicalTree상의 자기 자식들에 대해 3번과 동일한 논리를 수행한다.
}

위 작업으로 앞서 알아본 요구사항을 만족할 수 있습니다. 여기서 중요한 것은 바로 3번과 4번의 다음 질문입니다. "자기 자식들이 해당 dependencyProperty를 소유하고 있는가?" 지금 당신이 생각하는 그것이 맞습니다. 예, 바로 Owner의 개념입니다. DependencyProperty 그 자체는 value와 관계 없이 metadata로 이해되거나 value와 metadata를 얻을 수 있는 특정 key로 이해되어야 한다고 이전 포스팅에 말한 바 있습니다. 그렇기에 Owner의 개념이 필요합니다. 우리가 DependencyProperty를 register메소드를 통해 "등록"할때 지정하는 owner가 바로 그것입니다.

위 그림을 다시 보겠습니다. Window의 FontSize가 30으로 변경되었습니다. 이 때 FontSize에 대응되는 DependencyProperty는 곧 Control.FontSizeProperty이며 owner가 Control입니다. Button은 Control을 상속받았기에 Button도 곧 FontSizeProperty의 owner입니다. 따라서 Property Inheritance가 성립되는 것입니다.

    

자 그러면 다음과 같은 것을 해봅시다.

Control이 아닌 FrameworkElement라를 상속 받는 녀석을 만들고, 이 녀석이 Window의 FontSize Property를 상속받게 하는 것입니다.

Window의 FontSize가 상속된다면 testObj의 FontSize도 30이 되겠죠. 그런데 PropertyInheritanceTest는 FontSizeProperty의 Owner가 아닙니다. 어떻게 하면 될까요? Bravo! 맞습니다. 당신은 멋쟁이! PropertyInheritanceTest를 아래와 같이 변경하면 됩니다.

 

위와 같이 한 후 breakPoint를 사용해 PropertyInheritanceTest의 FontSize를 확인해보세요, 분명 30이라는 값이 들어있을 겁니다.

그런데 끝이 아닙니다. 만약 PropertyInheritanceTest가 FrameworkElement가 아닌 DependencyObject를 상속받고 있다면 Property Inheritance상속이 일어나지 않습니다. 왜 이렇게 되는지는 잘 모르겠습니다. ㅜ.ㅜ

   

Anyway, 결론입니다.

1. 의존 속성에 의한 상속을 지원받고 싶다면 Logical Tree, 또는 Visual Tree상의 관계가 맺어 져야 한다.
2. 부모와 자식은 반드시 FrameworkElement, 혹은 FrameworkContentElement를 상속 받은 객체여야 한다.
3. 자식이 상속받고 싶은 DependencyProperty의 Owner가 아니라면 AddOwner를 통해 자신을 해당 DependencyProperty의 소유자로 등록한다.

그리고 하나 더, 위 사항 외에 다른 케이스가 있는데 이는 What's an inheritance context? 라는 글에서 확인할 수 있습니다. 그리고 Artificial Inheritance Contexts in WPF는 이를 이용한 간단한 예입니다.

이 다른 케이스란 것은 Freezable 객체를 상속받은 녀석의 특이성에서 오는 것입니다. 너무 머리가 아퍼 명확하게 알지는 못했지만 다음과 같은 놀라운 케이스가 성립한다는 사실입니다. 보세요, SolidColorBrush는 분명 Button의 LogicalTree상의 자손도 아니고, VisualTree상의 자손도 아닌데 Button의 DataContext 객체를 사용하고 있습니다. 재미 있죠?

  <Button DataContext="Red">
    
<Button.Background>
        
<SolidColorBrush Color="{Binding}"/>
    
</Button.Background>
 
</Button>

   

신고