336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

WPF 응용 프로그램을 개발을 시작 할 때 논리 트리와 시각 트리의 개념을 이해하면 도움이 될 것이다.

WPF 응용 프로그램을 XAML이나 C#을 이용하여 만들 때 레이아웃 엘리먼트, 컨트롤, 윈도우들이 만드는 컨텐트, 사용자 지정 컨트롤 등등을 포함하게 된다.

각 엘리먼트는 윈도우에 추가하여 레이아웃에 포함되고, 컨트롤 또는 컨텐트는 윈도우의 논리 트리의 일부가 될 것이다.

이 트리는 무엇을 표시할지에 대한 정확한 표현이기도 하다.

반면, 이들과 대응되는 시각 트리는 논리 트리의 각각의 항목에 추가적인 UI 엘리먼트 클래스들을 하나 이상 포함하게 된다.

시각 트리을 조사하여 각 구성요소를 살펴 보면, WPF 프레임워크가 컨트롤을 표시 하기 위해 사용 한다는 것을 알 수 있게 된다.

데모를 살펴 보자.

C# 클래스를 아래와 같이 작성하였고, 최상위 UI 엘리먼트를 받아 들여 논리 트리 혹은 시각 트리를 재귀적으로 표시하는 유틸리티 메소드들을 공개하였다.

public class VisualUtilities
     {

        private int indentDepth = 0;

        public void PrintVisualTree(Visual v)
        {
            string name = null;

            increaseIndent();

            if (v is FrameworkElement)
                name = ((FrameworkElement) v).Name;

            print("Visual Type: " + v.GetType().ToString() + (name != null ? ", Name: " + name : ""));

            // recurse through the children
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(v); i++)
            {
                PrintVisualTree(VisualTreeHelper.GetChild(v, i));
            }
            decreaseIndent();
        }

        public void PrintLogicalTree(Object obj)
        {
            increaseIndent();

            if (obj is FrameworkElement)
            {
                FrameworkElement fe = (FrameworkElement)obj;
                print("Logical Type: " + fe.GetType() + ", Name: " + fe.Name);

                // recurse through the children
                IEnumerable children = LogicalTreeHelper.GetChildren(fe);
                foreach (object child in children)
                {
                    PrintLogicalTree(child);
                }

            }
            else
            {
                // stop recursing as we certainly can't have any more FrameworkElement children
                print("Logical Type: " + obj.GetType());
            }

            decreaseIndent();

        }

        private void print(String line)
        {
            string indent;
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < indentDepth; i++)
                builder.Append("\t");

            builder.Append(line);
            Console.WriteLine(builder);
        }

        private void increaseIndent()
        {
            indentDepth++;
        }

        private void decreaseIndent()
        {
            indentDepth--;
        }
    }



이 코드에 대해 말하자면:
  • PrintLogicalTree 메소드는 object를 파라미터로 받는다.
  • 논리 트리의 엘리먼트는 FrameworkElement에서 확장 되었거나 다른 object 타입이기 때문이다.
  • 재귀의 기본 케이스는 FrameworkElement가 아닌 객체이며, 이 경우 논리 자식들이 정의 되어 있지 않으므로 더이상 재귀 호출을 진행하지 않는다.
  • FrameworkElement에 "Name" 속성이 정의 되어 있고, , Visual에는 존재 하지 않는다.  그래서 시각 트리를 훌륭하게 출력하고 논리 자식들에게 이름을 부여하여 출력하기 위해서는 반드시 FrameworkElement로 캐스팅 해야 했다.

LogicalTreeHelper와 VisualTreeHelper API 사이의 차이점에 대해 살펴보자.

  • LogicalTreeHelper는 IEnumerable 객체를 반환하는 GetChildren 함수를 지원한다.
  • 반면 VisualTreeHelper는 GetChildrenCount 함수와 카운트를 순회 하는 GetChild 함수를 사용 할 수 있다.
  • 이들 API간의 본질적인 차이는 무엇일까?
  • 마이크로소프트의 말을 빌리자면 이렇다. "아마 서로 다른 개발자가 코딩 했기 때문일 것이다."

버튼 하나를 포함하는 간단 윈도우를 만들어 보자.

The XAML layout:

<Window x:Class="Test.Window1"
    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
    Title="Test"
    >

    <Grid x:Name="myGrid">
      <Button x:Name="button1" Click="Button1Clicked">Some Content</Button>
    </Grid>
</Window>

And the C# code behind:

public partial class Window1 : Window
{
        public Window1()
        {
            InitializeComponent();
        }

       private void Button1Clicked(object sender, RoutedEventArgs e)
       {

            VisualUtilities util = new VisualUtilities();
            Console.WriteLine("Logical Tree:");
            util.PrintLogicalTree(this);
            Console.WriteLine("Visual Tree:");
            util.PrintVisualTree(this);

        }

    }

Button Click 이벤트 핸들러에서 유틸리티 유틸리티 함수를 호출하였다.

아래는 출력한 결과이다.(시각 트리에 있는 논리 트리 엘리먼트는 굵게 표시하였다.)

 Logical Tree:

     Logical Type: Test.Window1, Name:

              Logical Type: System.Windows.Controls.Grid, Name: myGrid

                     Logical Type: System.Windows.Controls.Button, Name: button1

                           Logical Type: System.String


     Visual Tree:

     Visual Type: Test.Window1, Name:

              Visual Type: System.Windows.Controls.Border, Name:

                     Visual Type: System.Windows.Controls.Grid, Name:

                           Visual Type: System.Windows.Documents.AdornerDecorator, Name:

                                  Visual Type: System.Windows.Controls.ContentPresenter, Name:

                                         Visual Type: System.Windows.Controls.Grid, Name: myGrid

                                                Visual Type: System.Windows.Controls.Button, Name: button1

                                                       Visual Type: Microsoft.Windows.Themes.ButtonChrome, Name:Chrome

                                                              Visual Type: System.Windows.Controls.ContentPresenter, Name:

                                                                     Visual Type: System.Windows.Controls.TextBlock, Name:

                                  Visual Type: System.Windows.Documents.AdornerLayer, Name:

                           Visual Type: System.Windows.Controls.Primitives.ResizeGrip, Name: WindowResizeGrip

논리 트리에 있는 Windows 엘리먼트와 Grid 엘리먼트 사이에 빈 공간을 발견 할 수 있는데, 실제로 WPF 프레임워크는 화면 표시를 위해 서로 다른 4개의 UI 엘리먼트(Border, Grid, AdornerDecorator,ContentPresenter) 를 조합하여 추가한다.

또한 string은 버튼 컨텐트에 포함되어 있는 ContentPresenter 시각 객체의 TextBlock으로 바꾼다.(ContentControl을 확장한 버튼은 버튼 컨텐트에 어떠한 WPF 컨텐트도 받아 들일 수 있다. 화면을 표시하는 시점에, WPF 프레임워크가 나의 논리 켄텐트에 string을 확인하여, ContentPresenter 객체에 TextBlock에 추가 하게 된다.)

WPF 앱을 처음 개발 하는 입장에서 이것들을 걱정 할 필요는 없다.

하지만 이슈를 처리 하거나 scaling, resizeing 또는 윈도우 컨텐트 부분을 대체하는 등의 전문화된 일들을 하기 위해선, 윈도우를 표현하는 실제적인 트리 구조를 이해하는 게 도움이 된다.


출처 : http://blog.lab49.com/archives/217


좀더 좋은 번역이 될만한 내용 있으시면 덧글 부탁드려요. ^^



+ Recent posts