로직 트리와 뷰 트리 이해하기
로직 트리는 애플리케이션에 추가된 요소들의 계층 구조를 의미합니다. 이 구조는 속성 상속, 이벤트 전파, 스타일 적용 등의 기능을 지원합니다. 반면 뷰 트리는 로직 트리를 세분화한 형태로, 각 컨트롤은 내부적으로 여러 하위 요소로 구성됩니다. 예를 들어 버튼은 단순한 박스가 아니라 ButtonChrome, ContentPresenter, TextBlock 등으로 분해됩니다.
모든 요소는 FrameworkElement 클래스에서 파생되며, 뷰 트리의 구조는 스타일이나 속성 설정에 따라 달라질 수 있습니다. 예를 들어 버튼에 텍스트를 포함하면 자동으로 TextBlock이 생성됩니다. 하지만 컨텐츠 컨트롤인 버튼은 임의의 요소를 중첩할 수 있어 유연성이 큽니다.
트리 탐색 도구
System.Windows.LogicalTreeHelper와 VisualTreeHelper는 트리 탐색을 위한 메서드를 제공합니다:
GetParent(): 부모 요소를 반환GetChildren(): 자식 요소 목록을 반환 (컨테이너 여부 구분 없음)GetChildrenCount(),GetChild(): 뷰 트리의 하위 요소 접근
뷰 트리 표시 코드 예제
public void ShowVisualTree(DependencyObject root)
{
treeView.Items.Clear();
ProcessElement(root, null);
}
private void ProcessElement(DependencyObject element, TreeViewItem parentItem)
{
var item = new TreeViewItem();
item.Header = element.GetType().Name;
item.IsExpanded = true;
if (parentItem == null)
treeView.Items.Add(item);
else
parentItem.Items.Add(item);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
ProcessElement(VisualTreeHelper.GetChild(element, i), item);
}
}
템플릿의 개념
WPF 컨트롤은 외관이 없는 구조를 가지며, 외형은 컨트롤 템플릿을 통해 정의됩니다. 템플릿은 XAML로 작성되며, 컨트롤의 레이아웃과 시각적 표현을 결정합니다.
템플릿의 종류
- ControlTemplate: 컨트롤의 전체 레이아웃을 재정의
- DataTemplate 및 HierarchicalDataTemplate: 데이터를 뷰로 매핑
- ItemsPanelTemplate:
ItemsControl내부의 항목 배치 방식 정의
데이터 템플릿은 기존 컨트롤 내부에 요소를 추가하는 데 사용되며, 컨트롤의 기본 구조를 변경하지 않습니다. 반면 컨트롤 템플릿은 완전히 새로운 모델을 구성할 수 있어 더 강력한 커스터마이징이 가능합니다.
컨트롤 템플릿 만들기
기본적인 버튼 템플릿은 다음과 같습니다:
<ControlTemplate x:Key="CustomButtonTemplate" TargetType="{x:Type Button}">
<Border Background="Red" BorderBrush="Orange" BorderThickness="3" CornerRadius="2">
<ContentPresenter RecognizesAccessKey="True" Margin="{TemplateBinding Padding}" />
</Border>
</ControlTemplate>
ContentPresenter는 컨텐츠가 삽입될 위치를 나타내며, 모든 컨텐츠 컨트롤에 필수입니다. 또한 TemplateBinding을 사용하여 컨트롤의 속성 값을 템플릿 내부에서 참조할 수 있습니다.
템플릿 바인딩 활용
<ContentPresenter Margin="{TemplateBinding Padding}" />
이러한 바인딩은 단방향이며, Freezable 타입의 속성에는 적용되지 않으므로 문제가 발생할 경우 일반 데이터 바인딩으로 대체할 수 있습니다.
트리거를 통한 상태 반응
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="DarkRed" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Border" Property="Background" Value="IndianRed" />
<Setter TargetName="Border" Property="BorderBrush" Value="DarkKhaki" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="FocusCue" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Foreground" Value="Gray" />
<Setter TargetName="Border" Property="Background" Value="MistyRose" />
</Trigger>
</ControlTemplate.Triggers>
키보드 포커스 시 보여지는 초점 표시는 Rectangle을 사용해 가시성을 조절하며, Grid 내부에 위치시켜 컨텐츠 위에 겹치도록 합니다.
템플릿 리소스 관리
템플릿을 별도의 리소스 사전(.xaml) 파일로 분리하여 관리할 수 있습니다:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTemplate x:Key="CustomButtonTemplate" TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="2" CornerRadius="2">
<Grid>
<Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Black"
StrokeDashArray="1 2" StrokeThickness="1" SnapsToDevicePixels="True" />
<ContentPresenter Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="FocusCue" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ResourceDictionary>
스타일을 통한 템플릿 적용
<Style x:Key="StyledButton" TargetType="{x:Type Button}">
<Setter Property="Template" Value="{StaticResource CustomButtonTemplate}" />
<Setter Property="Background" Value="{StaticResource DefaultColor}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderColor}" />
<Setter Property="Foreground" Value="White" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource HoverColor}" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{StaticResource PressedColor}" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{StaticResource DisabledColor}" />
</Trigger>
</Style.Triggers>
</Style>
템플릿을 적용하려면 Style 속성을 사용해야 하며, Template 속성은 직접 지정하는 것이 아닙니다:
<Button Style="{StaticResource StyledButton}" Margin="10" Padding="5">
커스텀 템플릿 버튼
</Button>
자동 적용을 위한 타입 스타일
<Style TargetType="{x:Type Button}">
<Setter Property="Template" Value="{StaticResource CustomButtonTemplate}" />
</Style>
기본 스타일을 해제하려면:
<Button Style="{x:Null}" ... />