WPF의 트리

대부분의 기술에서 요소와 구성 요소는 트리 구조로 구성됩니다. 이 트리 구조에서 개발자는 트리의 개체 노드를 직접 조작하여 애플리케이션의 동작이나 렌더링에 영향을 줍니다. Windows Presentation Foundation(WPF)도 여러 가지 트리 구조 메타포를 사용하여 프로그램 요소 간의 관계를 정의합니다. 대부분 경우 WPF 개발자는 개념적으로 개체 트리 메타포를 고려하면서 코드로 애플리케이션을 만들거나 XAML로 애플리케이션의 일부를 정의할 수 있지만 XML DOM에서 사용할 수 있는 몇 가지 일반적인 개체 트리 조작 API 대신 특정 API를 호출하거나 특정 태그를 사용하여 이 작업을 수행합니다. WPF는 트리 메타포 보기를 제공하는 2개의 도우미 클래스, 즉 LogicalTreeHelperVisualTreeHelper를 노출합니다. 또한 WPF 설명서에서는 시각적 트리와 논리 트리라는 용어를 사용하는데 이러한 동일한 트리는 특정 주요 WPF 기능의 동작을 이해하는 데 유용하기 때문입니다. 이 항목에서는 시각적 트리와 논리 트리가 나타내는 항목을 정의하고, 이러한 트리가 전체 개체 트리 개념과 관련되는 방식에 대해 설명하며, LogicalTreeHelperVisualTreeHelper를 소개합니다.

WPF의 트리

WPF에서 가장 완벽한 트리 구조는 개체 트리입니다. XAML에서 애플리케이션 페이지를 정의한 다음 XAML을 로드하면 태그에 있는 요소의 중첩 관계를 기반으로 트리 구조가 만들어집니다. 코드에서 애플리케이션이나 애플리케이션 일부를 정의하는 경우 트리 구조는 지정된 개체에 대한 콘텐츠 모델을 구현하는 속성에 대해 속성 값을 할당하는 방법에 따라 만들어집니다. WPF에는 전체 개체 트리가 개념화되고 공용 API에 보고될 수 있는 두 가지 방법이 있습니다. 즉, 논리 트리와 시각적 트리로 보고할 수 있습니다. 논리 트리와 시각적 트리를 구분하는 것이 항상 중요한 것은 아니지만 특정 WPF 하위 시스템에 문제가 발생하고 태그 또는 코드에서 선택하는 데 영향을 줄 수 있습니다.

항상 논리 트리나 시각적 트리를 직접 조작하지 않는 경우에도 트리가 상호 작용하는 방식에 대한 개념을 이해하면 WPF를 기술로 이해하는 데 도움이 됩니다. 또한 WPF를 일종의 트리 메타포로 생각하는 것은 WPF에서 속성 상속 및 이벤트 라우팅의 작동 방식을 이해하는 데 매우 중요합니다.

참고

개체 트리는 실제 API라기보다는 일종의 개념이기 때문에 이 개념을 개체 그래프로 생각할 수도 있습니다. 실제로 트리 메타포가 분류되는 런타임에 개체 간에 관계가 있습니다. 그러나 특히 XAML 정의 UI를 사용하는 경우 트리 메타포는 대부분의 WPF 설명서에서 이 일반적인 개념을 참조할 때 개체 트리라는 용어를 사용할 정도로 충분히 관련이 있습니다.

논리 트리

WPF에서 해당 요소를 지원하는 개체의 속성을 설정하여 UI 요소에 콘텐츠를 추가합니다. 예를 들어 Items 속성을 조작하여 ListBox 컨트롤에 항목을 추가합니다. 이렇게 하면 Items 속성 값인 ItemCollection에 항목이 배치됩니다. 마찬가지로 DockPanel에 개체를 추가하려면 해당 Children 속성 값을 조작합니다. 여기에서는 UIElementCollection에 개체를 추가합니다. 코드 예제는 방법: 동적으로 요소 추가를 참조하세요.

XAML(Extensible Application Markup Language)에서는 목록 항목을 ListBox 또는 컨트롤에 배치하거나 기타 UI 요소를 DockPanel에 배치할 때 다음 예제와 같이 ItemsChildren 속성을 명시적이거나 암시적으로 사용합니다.

<DockPanel
  Name="ParentElement"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  >
  <!--implicit: <DockPanel.Children>-->
  <ListBox DockPanel.Dock="Top">
    <!--implicit: <ListBox.Items>-->
    <ListBoxItem>
      <TextBlock>Dog</TextBlock>
    </ListBoxItem>
    <ListBoxItem>
      <TextBlock>Cat</TextBlock>
    </ListBoxItem>
    <ListBoxItem>
      <TextBlock>Fish</TextBlock>
    </ListBoxItem>
  <!--implicit: </ListBox.Items>-->
  </ListBox>
  <Button Height="20" Width="100" DockPanel.Dock="Top">Buy a Pet</Button>
  <!--implicit: </DockPanel.Children>-->
</DockPanel>

문서 개체 모델에서 이 XAML을 XML로 처리하려는 경우 및 적합해야 하는 태그를 암시적으로 주석 처리하여 포함한 경우 결과 XML DOM 트리에는 <ListBox.Items>에 대한 요소 및 기타 암시적 항목이 포함됩니다. 그러나 XAML은 태그를 읽고 개체에 쓸 때 이런 방식으로 처리하지 않으며 결과 개체 그래프에 ListBox.Items가 포함되지 않습니다. 하지만 ItemCollection을 포함하는 Items라는 ListBox 속성이 있으며, 이 ItemCollection은 초기화되지만 ListBox XAML이 처리될 때 비어 있습니다. 그런 다음 ListBox의 콘텐츠로 존재하는 자식 개체 요소가 ItemCollection.Add에 대한 파서 호출에 의해 ItemCollection에 추가됩니다. 지금까지 XAML을 개체 트리로 처리하는 이 예제는 생성된 개체 트리가 기본적으로 논리 트리인 경우로 보입니다.

그러나 XAML 암시적 구문 항목이 팩터링된 경우에도 논리 트리는 런타임에 애플리케이션 UI에 대해 존재하는 전체 개체 그래프가 아닙니다. 주된 이유는 시각적 개체와 템플릿입니다. 예를 들어 Button을 생각해 보세요. 논리 트리는 Button 개체와 해당 문자열 Content도 내보냅니다. 하지만 런타임 개체 트리의 이 단추에는 더 많은 정보가 있습니다. 특히 특정 Button 컨트롤 템플릿이 적용되었기 때문에 단추만 원래대로 화면에 나타납니다. 적용된 템플릿에서 제공하는 시각 효과(예: 시각적 단추 주위 진한 회색의 템플릿 정의 Border)는 런타임 중 논리 트리를 확인하고 있는 경우(예: 표시되는 UI에서 입력 이벤트를 처리한 후 논리 트리 읽기)에도 논리 트리에서 보고되지 않습니다. 템플릿 시각 효과를 찾으려면 시각적 트리를 대신 검사해야 합니다.

XAML 구문이 생성된 개체 그래프에 매핑되는 방법 및 XAML의 암시적 구문에 대한 자세한 내용은 XAML 구문 정보 또는 WPF의 XAML을 참조하세요.

논리 트리의 용도

논리 트리를 통해 콘텐츠 모델은 해당 자식 개체를 쉽게 반복할 수 있고 콘텐츠 모델을 확장할 수 있습니다. 또한 논리 트리는 논리 트리의 모든 개체가 로드된 경우 등의 특정 알림에 대한 프레임워크를 제공합니다. 기본적으로 논리 트리는 시각 효과를 제외한 프레임워크 수준의 런타임 개체 그래프와 유사하지만 고유한 런타임 애플리케이션의 구성에 대한 많은 쿼리 작업에 적합합니다.

또한 정적 리소스 참조와 동적 리소스 참조는 모두 초기 요청 개체에서 Resources 컬렉션의 논리 트리를 통해 위를 살펴본 다음 계속 논리 트리를 올라가 각 FrameworkElement(또는 FrameworkContentElement)에서 ResourceDictionary를 포함하는 또 다른 Resources 값(해당 키를 포함할 수 있음)을 검사하여 확인됩니다. 논리 트리와 시각적 트리가 모두 있을 경우 논리 트리가 리소스 조회에 사용됩니다. 리소스 사전 및 조회에 대한 자세한 내용은 XAML 리소스를 참조하세요.

논리 트리의 구성

논리 트리는 WPF 프레임워크 수준에서 정의됩니다. 즉, 논리 트리 작업과 가장 관련이 있는 WPF 기본 요소는 FrameworkElement 또는 FrameworkContentElement입니다. 하지만 실제로 LogicalTreeHelper API를 사용하면 알 수 있듯이 논리 트리에 FrameworkElementFrameworkContentElement가 아닌 노드가 포함되는 경우가 있습니다. 예를 들어 논리 트리는 문자열인 TextBlockText 값을 보고합니다.

논리 트리 재정의

고급 컨트롤 작성자는 일반적인 개체 또는 콘텐츠 모델이 논리 트리 내에서 개체를 추가하거나 제거하는 방법을 정의하는 여러 가지 API를 재정의하여 논리 트리를 재정의할 수 있습니다. 논리 트리를 재정의하는 방법에 대한 예제는 논리 트리 재정의를 참조하세요.

속성 값 상속

속성 값 상속은 혼합 트리를 통해 작동합니다. 속성 상속을 사용하도록 설정하는 Inherits 속성을 포함하는 실제 메타데이터는 WPF 프레임워크 수준 FrameworkPropertyMetadata 클래스입니다. 따라서 원래 값을 보유하는 부모와 해당 값을 상속하는 자식 개체는 모두 FrameworkElement 또는 FrameworkContentElement여야 하며, 일부 논리 트리에 속해야 합니다. 그러나 속성 상속을 지원하는 기존 WPF 속성의 경우 속성 값 상속은 논리 트리에 없는 중간 개체를 통해 지속될 수 있습니다. 주로 이 기능은 템플릿 요소가 템플릿 기반의 인스턴스 또는 더 높은 수준의 페이지 수준 구성 및 논리 트리의 더 높은 위치에서 설정된 상속된 속성 값을 사용하도록 하려는 경우와 관련이 있습니다. 속성 값 상속이 이러한 경계에서 일관되게 작동하려면 상속하는 속성을 연결된 속성으로 등록해야 하며 속성 상속 동작으로 사용자 지정 종속성 속성을 정의하려는 경우 이 패턴을 따라야 합니다. 속성 상속에 사용되는 정확한 트리는 도우미 클래스 유틸리티 메서드에서 완전히 예상할 수 없으며, 런타임에도 마찬가지입니다. 자세한 내용은 속성 값 상속을 참조하세요.

시각적 트리

WPF에는 논리 트리의 개념 외에 시각적 트리라는 개념도 있습니다. 시각적 트리는 Visual 기본 클래스에 의해 표시된 대로 시각적 개체의 구조를 설명합니다. 컨트롤에 대한 템플릿을 작성하면 해당 컨트롤에 적용되는 시각적 트리를 정의하거나 다시 정의하는 것입니다. 또한 시각적 트리는 성능 및 최적화를 위해 그리기를 낮은 수준으로 제어하려는 개발자에게 유용합니다. 기존 WPF 애플리케이션 프로그래밍의 일부로 시각적 트리가 노출되는 한 가지 경우는 라우트된 이벤트에 대한 이벤트 경로가 대부분 논리 트리가 아닌 시각적 트리를 따라 이동하는 경우입니다. 이렇게 미묘한 라우트된 이벤트 동작은 컨트롤 작성자가 아닌 경우 바로 파악되지 않을 수 있습니다. 시각적 트리를 통한 이벤트 라우팅에서는 시각적 수준에서 구성을 구현하는 컨트롤을 사용하여 이벤트를 처리하거나 이벤트 setter를 만들 수 있습니다.

트리, 콘텐츠 요소 및 콘텐츠 호스트

콘텐츠 요소(ContentElement에서 파생되는 클래스)는 시각적 트리의 일부가 아닙니다. Visual에서 상속하지 않으며 가상 표현이 없습니다. UI에 표시되려면 Visual이면서 논리 트리 참가자인 콘텐츠 호스트에서 ContentElement가 호스트되어야 합니다. 일반적으로 이러한 개체는 FrameworkElement입니다. 콘텐츠 호스트는 콘텐츠에 대한 "브라우저"와 약간 비슷하며 호스트가 제어하는 화면 영역 내에서 해당 콘텐츠를 표시하는 방법을 선택하는 것으로 개념화할 수 있습니다. 호스트된 콘텐츠는 일반적으로 시각적 트리와 연관된 특정 트리 프로세스의 참가자가 될 수 있습니다. 일반적으로 FrameworkElement 호스트 클래스에는 콘텐츠 논리 트리의 하위 노드를 통해 모든 호스트된 ContentElement를 이벤트 경로에 추가하는 구현 코드가 포함되지만 호스트된 콘텐츠는 실제 시각적 트리의 일부가 아닙니다. 이렇게 해야만 ContentElement가 자신이 아닌 다른 모든 요소로 라우트되는 라우트된 이벤트의 소스가 될 수 있습니다.

트리 순회

LogicalTreeHelper 클래스는 논리 트리 통과를 위한 GetChildren, GetParent, FindLogicalNode 메서드를 제공합니다. 대부분의 경우 기존 컨트롤의 논리 트리를 트래버스할 필요가 없습니다. 이러한 컨트롤은 거의 항상 논리 자식 요소를 Add, 인덱서 등 컬렉션 액세스를 지원하는 전용 컬렉션 속성으로 노출하기 때문입니다. 트리 통과는 주로 컬렉션 속성이 이미 정의되어 있는 ItemsControl 또는 Panel 같은 의도된 컨트롤 패턴에서 파생시키지 않고 고유한 컬렉션 속성 지원을 제공하려는 컨트롤 작성자가 사용하는 시나리오입니다.

시각적 트리는 시각적 트리 통과를 위한 도우미 클래스 VisualTreeHelper도 지원합니다. 시각적 트리는 컨트롤 관련 속성을 통해 간단하게 노출되지 않으므로 VisualTreeHelper 클래스는 프로그래밍 시나리오에 필요한 경우 시각적 트리 트래버스에 권장되는 방법입니다. 자세한 내용은 WPF 그래픽 렌더링 개요를 참조하세요.

참고

경우에 따라 적용된 템플릿의 시각적 트리를 검사해야 합니다. 이 기술을 사용할 때는 주의해야 합니다. 템플릿을 정의하는 컨트롤에 대한 시각적 트리를 트래버스하는 경우에도 컨트롤 소비자는 인스턴스에서 Template 속성을 설정하여 항상 템플릿을 변경할 수 있으며 최종 사용자는 시스템 테마를 변경하여 적용된 템플릿에 영향을 줄 수 있습니다.

"트리"로 라우트된 이벤트에 대한 경로

앞서 언급했듯이 지정된 라우트된 이벤트의 경로는 시각적 트리와 논리 트리 표현이 혼합된 트리의 미리 결정된 단일 경로를 따라 이동합니다. 이벤트 경로는 라우트된 터널링 이벤트인지 버블링 이벤트인지에 따라 트리 내에서 위쪽이나 아래쪽 방향으로 이동할 수 있습니다. 이벤트 경로 개념에는 실제로 라우트되는 이벤트를 발생시키는 것과 별개로 이벤트 경로를 "이동"하는 데 사용할 수 있는 직접적인 지원 도우미 클래스가 없습니다. 경로 EventRoute를 나타내는 클래스는 있지만 해당 클래스의 메서드는 일반적으로 내부 전용입니다.

리소스 사전 및 트리

페이지에 정의된 모든 Resources에 대한 리소스 사전 조회는 기본적으로 논리 트리를 트래버스합니다. 논리 트리에 없는 개체는 키 지정 리소스를 참조할 수 있지만 리소스 조회 시퀀스는 해당 개체가 논리 트리와 연결되는 지점에서 시작됩니다. WPF에서는 논리 트리 노드에만 ResourceDictionary를 포함하는 Resources 속성이 있을 수 있으므로 시각적 트리를 트래버스하여 ResourceDictionary에서 키 지정 리소스를 찾는 이점이 없습니다.

그러나 리소스 조회는 직접적인 논리 트리를 넘어 확장될 수도 있습니다. 애플리케이션 태그의 경우 리소스 조회가 애플리케이션 수준 리소스 사전까지 계속된 다음 정적 속성이나 키로 참조되는 테마 지원 및 시스템 값까지 계속될 수 있습니다. 테마 자체는 리소스 참조가 동적인 경우 테마 논리 트리 외부의 시스템 값을 참조할 수도 있습니다. 리소스 사전 및 조회 논리에 대한 자세한 내용은 XAML 리소스를 참조하세요.

참고 항목