WPF 시작하기 5. WPF Template이란? 무엇일까?
WPF를 배우다보면 자연스럽게 "Template" 이라는 단어를 듣게 될 것입니다. Template이란 이름에서도 알 수 있듯이
"틀"의 역할을 해줍니다. 여기서 틀이란 마치 쇠를 녹어 틀에 붓는 것으로 쇠붙이들의 모양을 잡듯이 Template은 WPF Control의 껍데기 같은 개념입니다. 이렇게만 이야기해서는 어려우니 실제로 우리가 쓰는 Control를 뜯어보도록 합시다.
적당히 WPF 프로젝트를 만들어 MainWindow에 Button과 ListView 객체를 만들어 주세요
그리고나서 Ctrl+클릭을 통해 Button 내부로 들어가주세요
버튼 내부에 들어왔나요? 위의 사진에서 확인할 수 있듯이 ButtonBase로 부터 상속받아 재정의한 OnClick 메소드와 다양한 의존 속성(DependencyProperty)을 가지고 있는 모습입니다. 그럼 더 깊숙히 가봅시다 ButtonBase 내부로 들어가주세요
와우 더 많은 의존 속성, 이제 버튼을 벗어나서 ContentControl이라는 친구가 보이네요 여기서 멈출 수 없죠 한 번 더 ContentControl 내부로 들어봅시다.
이쯤 되면 이 사람이 날 엿먹이려는 건가 싶은 생각이 드실 수도 있지만 계속 믿고 따라와 주세요 여기서 잠깐 보시면 Content라는 속성이 보입니다. 바로 이 ContentControl이 Button, Textblock, Textbox 등 하나의 Content를 자식으로 가지고 그것을 우리에게 보여주는 컨트롤들이 공통으로 상속받고 있는 친구인 것이죠 우리가 만약에 Content의 내용을 바꾸면 OnContentChanged 메소드가 작동 한다 거나 하겠죠?(저도 사실 자세하게 어찌 움직이는지 까지는 모르겠씁니다..)
여기서 얻어 갈게 한가지 더 있습니다. 바로 DataTemplate인데요 DataTemplate은 이름에서부터 대놓고 이건 데이터의 틀을 지정해주는 Template입니다. 하는 느낌이죠 내가 Button에 Content로 준 데이터가 어떻게 표현되길 원하는지를 지정할 수 있는 것이죠 저 DataTemplate 안으로 들어가면
요렇게 생겼습니다. 뭐 데이터 타입을 받아올 수 있게도 되어있고 아마도 저렇게 데이터 타입을 받아와서 마치 대충 이런 형태의 데이터가 들어간다 라는 것을 지정하는 것 같습니다. 제가 가지고 있는 지식으로는 여기까지 설명이 다입니다..ㅠ 일단 DateTemplate이 Content에 표시될 데이터의 틀을 지정해주는 것이라고 까지만 알도록 합시다.
그럼 한 번 더 Control의 내부로 들어가봅시다.
ControlTemplate이라는 친구가 눈에 보입니다. 우리는 XAML을 통해서 저 ControlTemplate에 들어갈 내용을 만들어 줄 수 있습니다. 이게 무슨 소리냐구요? 아래에서 XAML을 통해 Template을 수정하는 방법을 보여드릴테니 지금은 기다려주세요 위의 DataTemplate에서 데이터의 틀을 수정했으니 ControlTemplate에서는 이름 그대로 Control의 틀 즉 외형을 수정할 수 있겠죠?
ControlTemplate의 내부를 보면 targetType을 받습니다. 이게 어떤 컨트롤 Button인지, TextBlock인지를 알기 위해서 타입을 받아오는 것이겠죠
여기까지를 정리하면
1. DataTemplate은 컨트롤에 표현될 데이터의 형태를 지정해줄 수 있는 Template이다.
2. ControlTemplate은 컨트롤의 겉면 즉 외관의 형태를 지정해줄 수 있는 Template이다.
라는 것입니다.
그럼 이제는 ListView를 살펴볼까요? 다시 처음으로 돌아와서 ListView 내부로 파고들어가 보도록 합시다.
음.. ListView가 ListBox를 상속받고 있네요? ListView와 Box의 차이는 View는 멀티 선택이 되고 Box는 안되는 건데 음.. 뭐 나름 상위호환격이라고 생각하면 그럴듯하군요 더 파고 들어가 봅시다.
Selector라는 것을 상속받고 있군요, 여기까지는 우리가 늘보던 속성들도 있네요 더 들어가보도록 합시다.
이렇게 보니 Selector 클래스는 데이터의 선택에 대한 기능들을 담당하는 친구인가 봅니다. ItemsControl로 들어가볼까요? 거의다 와갑니다.
보시다시피 Button을 파고들어 갔을때 나온 ContentControl과 ListView를 파고들었을때 나온 ItemsControl이 똑같은 Control이라는 친구를 상속받고 있네요 Control 내부에 있는 ControlTemplate은 이미 봤으니 우리는 ItemsPanelTemplate을 살펴봅시다. 위에서 DataTemplate을 통해 데이터가 어떻게 표현될지 지정할 수 있다고 했었죠? 그리고 이 DataTemplate을 통해 반복되는 데이터의 표현도 정할 수 있구요 그런데 우리가 놓쳤던 것이 있습니다. 각각의 데이터들은 DataTemplate을 통해 표현되지만 전체적인 틀은 어떻게 정할까요? 바로 ItemsPanelTemplate을 통해 지정할 수 있습니다. 이름 그대로 아이템들이 나오는 전체적인 틀을 조정할 수 있는 Template인 것이죠
여기까지 나름? 분량을 채우기 위한 내용들이 었습니다. 그럼 이제 XAML을 통해 어떻게 Template을 활용할 수 있는지 알아봅시다.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="TEST" Height="228" Width="400">
<Window.DataContext>
<local:StudentViewModel/>
</Window.DataContext>
<Grid>
<ListView ItemsSource="{Binding students}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="300"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate DataType="local:Student">
<StackPanel Orientation="Vertical" Width="130" Height="60">
<TextBlock Text="{Binding Name}" Margin="0 0 10 0"/>
<TextBlock Margin="0 0 0 5">
<Run Text="{Binding Grade}"/>
<Run Text="학년 "/>
<Run Text="{Binding Class}"/>
<Run Text="반 "/>
<Run Text="{Binding Number}"/>
<Run Text="번"/>
</TextBlock>
<Button Width="90" Height="20">
<Button.Template>
<ControlTemplate>
<Border x:Name="btnBorder" BorderBrush="Blue" BorderThickness="1">
<Grid Background="Black">
<ContentPresenter>
<ContentPresenter.Content>
<TextBlock Foreground="White">
<Run Text="{Binding Name}"/>
<Run Text=" 지우기"/>
</TextBlock>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="btnBorder" Property="BorderBrush" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
MainWindow.xaml
public class StudentViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Student>? students = new ObservableCollection<Student>();
public ObservableCollection<Student>? Students
{
get => students;
set
{
students = value;
NotifyPropertyChanged(nameof(students));
}
}
public StudentViewModel()
{
Students = new ObservableCollection<Student>()
{
new Student("김태오", 3, 3, 3),
new Student("김태오", 3, 3, 4),
new Student("김태오", 3, 3, 5),
new Student("김태오", 3, 3, 6),
new Student("김태오", 3, 3, 6)
};
}
}
public class Student
{
public Student(string name, int grade, int pClass, int number)
{
Name = name;
Grade = grade;
Class = pClass;
Number = number;
}
public string? Name { get; set; }
public int Grade { get; set; }
public int Class { get; set; }
public int Number { get; set; }
}
StudentViewModel.cs 입니다. 본래라면 Student는 따로 다른 파일로 만들어야 되지만 예제이므로..ㅎㅎ
위에서 설명한 ItemsPanelTemplate, DataTemplate, ControlTemplate을 모두 사용한 예제입니다. 결과물을 보시면
먼저 ItemsPanelTemplate부터 볼까요
간단합니다. 전체적인 Item들의 배열 방식을 WrapPanel안에 있는 것처럼 적용시킨거에요 그래서 공간이 부족하니까 다음줄로 내려간 모습이죠?
다음은 DataTemplate입니다. 그전에 우리가 List.ItemTemplate 안에 DataTemaplate으로 들어갔죠?
실제로도 ListView의 ItemsControl까지 거슬러 올라가보면 ItemTemplate이라는 변수가 DataTemplate Type으로 선언이 되어있어요 DataTemplate을 보시면 StackPanel로 각각 TextBlock 두개와 Button을 감싸고 있죠 보시면 실제로 저렇게 순서대로 나옵니다.
마지막은 Button에 적용되어 있는 ControlTemplate입니다. 보시다시피 좀 길기 때문에 저부분만 떼와보겠습니다.
보시면 Button을 완전히 새롭게 수정하고 있습니다. 테두리를 바꾸고 Grid로 배경을 바꾼뒤 ContentPresenter 라는 것을 통해 버튼의 Content를 지정해줍니다. 여기서 ContentPresenter는 이름그대로 우리가 각종 Control들의 Template수정하다보면 Textblock의 Text, Button의 Content 속성 등 해당 Control에 표시되는 컨텐츠가 있습니다 그걸 ContentPresenter가 위치한 곳에 보여주는 것이죠 지금 예제에는 따로 데이터를 바인딩 해버리지만 그냥 <ContentPresenter/>이렇게만 해놓으면 따로 <ContentPresenter Content=""/> 이런식으로 지정해주어야합니다.
이렇게 까지가 버튼의 외형을 수정한 것이고 버튼의 이벤트에 따른 반응도 수정할 수 있습니다.
Trigger를 사용하면 되는데요 Trigger는 이름 그대로 방아쇠로 마치 해당 이벤트를 방아쇠처럼 써서 해당 이벤트가 일어날때 지정한 속성을 바꾸는 것입니다.
보시면 btnBorder라는 이름을 가진 Control의 BorderBrush속성을 Red로 바꾸고 있죠 위쪽 gif에서도 보이듯이 마우스를 가져다가 올리면 색깔이 Border(테두리)의 색이 바뀝니다.
여기까지가 준비한 내용입니다.
여러분이 실제로 ControlTemplate을 수정하시면 해당 Control이 들고 있는 고유의 속성이나 예제가 필요하실텐데요 그럴때는
Control Styles and Templates - WPF .NET Framework | Microsoft Docs
System.Windows.Controls Namespace | Microsoft Docs
위의 링크를 통해서 Control들이 가지고 있는 고유의 속성이나 이벤트를 사용하시면 됩니다.
다음번에는 XAML에서의 애니메이션을 사용해보도록 합시다. 추가로 VisualStudio Blend도 설치해 오시면 될 것 같습니다.