본문 바로가기

개발

[WPF] 다양한 템플릿 유형에 대해 살펴보자

Templates은 WPF 기본 스타일의 Elements(컨트롤들이라 생각하면 됨)의 시각적 요소와 데이터 표현을 변경(재정의)할 수 있다.

단순히 글자색 변경이나 border의 색상을 바꿀 때는 필요 없는데 각 사용자 액션에 따른 동작들에 의해 전체적으로 각 행동(마우스 오버/클릭 등)에 맞는 색상을 지정해야 하는 경우가 종종 생긴다. 또한 기본 모양을 바꾸고 싶다면 아래의 내용을 이해는 못하더라도 현재는 이렇게 사용하면 되는구나 정도는 알 필요가 있다.

 

WPF는 엘리먼트의 룩앤필(Look & Feel)을 업데이트하는데  아래 3가지 유형의 템플릿을 제공한다

 

FrameworkTemplate 
          |
          + ControlTemplate
          |
          + DataTemplate
          |
          + ItemsPanelTemplate 

 

 

간단히 요약해보면

ControlTemplate는 컨트롤의 컨텐츠가 아닌 컨트롤의 외관을 꾸밀 때 사용한다.

DataTemplate는 컨트롤의 컨텐츠 부분을 스타일링한다. 

ItemsPanelTemplate는 ComboBox, ListBox 등의 같은 OOOitems들을 여러 개 갖는 컨트롤의 레이아웃을 표현한다.

(아이템 정렬을 어떻게 표현할까이다)

 

 

컨트롤의 템플릿을 정의하는 방법 3가지 부터 알아보자

  • 리소스로 등록하는 방법
  • 스타일 내 정의
  • 인라인 형태

1. 리소스로 등록하는 방법

프로젝트 생성 후 기본 코드에 <Windows.Resource/>를 추가하고, ControlTemplate을 정의한 후 Button의 Template에 연결한 코드이다. (x:Key의 값을 Button의 Template에 연결)

<Window x:Class="WPFTemplateSample.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:WPFTemplateSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ControlTemplate x:Key="redRoundButton" TargetType="Button">
            <Grid>
                <Ellipse Width="100" Height="100">
                    <Ellipse.Fill>
                        <SolidColorBrush Color="Red"></SolidColorBrush>
                    </Ellipse.Fill>
                </Ellipse>
            </Grid>
        </ControlTemplate>
    </Window.Resources>
    
    <Grid>
        <Button Template="{StaticResource redRoundButton}" Content="버튼이다"></Button>
    </Grid>
</Window>

 

위 코드를 실행해보면 Button의 content로 "버튼이다"를 입력했지만 화면상에 글자가 표시되지 않는다.

이유는 ControlTemplate에는 컨텐츠를 표시하는 Presenter라는 것이 있다.

Presenter는 아래 2가지가 있다.

 

FrameworkElement 
           |
           + ContentPresenter
           |
           + ItemsPresenter

 

여기서 Control 클래스를 상속받은 2가지 컨트롤을 알고 가야 한다

 

Control
    |
    + ContentControl  (자식으로 Content하나를 받을 수 있는 컨트롤)
    |
    + ItemsControl (리스트박스나 리스트뷰등처럼 여러 데이터를 가지고 다수의 반복되는 자식을 갖는 컨트롤)

 

위 코드에서 ContentControl(버튼)의 ControTemplate을 정의를 했다. 모양과 색상을 변경했다면 콘텐츠를 표시해줘야 한다.

(스타일을 변경하지 않고 사용한 경우는 기본스타일에 정의에 Presenter가 이미 선언되어있기 때문에 별다른 처리 없이 표시되지만 이것을 재정의했기 때문에 우리도 추가를 해줘야 한다.)

왜, Presenter라는걸 써야 하는가? Presenter는 콘텐츠를 표시해주는 거라고 설명했다. 그렇다면 콘텐츠에는 텍스트 컨트롤과 같은 단순한 컨트롤뿐 아니라 다양한 타입의 여러 객체들이 세팅될 수 있는데 이걸 처리해주는 것이 Presenter이다. 

 

 

표시되지 않은 이유을 알았으니 아래와 코드와 같이 <ContentPresenter/>를 추가해보자

 

[참고]

만일 <Windows>가 아닌 <UserControl>인 경우에는 ,

<UserControl.Resources>로 선언하면 된다.

 

<Window x:Class="WPFTemplateSample.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:WPFTemplateSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ControlTemplate x:Key="redRoundButton" TargetType="Button">
            <Grid>
                <Ellipse Width="100" Height="100">
                    <Ellipse.Fill>
                        <SolidColorBrush Color="Red"></SolidColorBrush>
                    </Ellipse.Fill>
                </Ellipse>
                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
            </Grid>
        </ControlTemplate>
    </Window.Resources>
    
    <Grid>
        <Button Template="{StaticResource redRoundButton}" Content="버튼이다"></Button>
    </Grid>
</Window>

 

 

2. 스타일 내 정의

 

<Style>을 사용해서 <Template> 속성으로 위의 <ControlTemplate> 코드를 감싸서 사용한다.

<Resource>에 <ControlTemplate>을 선언했을 때는 Button의 Template에 연결을 했지만, 

이 방식에서는 Button의 Style에 연결하면 된다.

<Window x:Class="WPFTemplateSample.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:WPFTemplateSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style x:Key="redRoundButtonStyle"
               TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Ellipse Width="100"
                                     Height="100">
                                <Ellipse.Fill>
                                    <SolidColorBrush Color="Red"></SolidColorBrush>
                                </Ellipse.Fill>
                            </Ellipse>
                            <ContentPresenter HorizontalAlignment="Center"
                                              VerticalAlignment="Center"></ContentPresenter>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <Button Style="{StaticResource redRoundButtonStyle}"
                Content="Control Template"></Button>
    </Grid>
</Window>

 

 

3. 인라인 형태

 

Button선언한 내부에 Template을 바로 작성한다.

 

<Window x:Class="WPFTemplateSample.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:WPFTemplateSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid>
            <Button Content="Control Template"
                    Foreground="Wheat">
                <Button.Template>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Ellipse Width="100"
                                     Height="100">
                                <Ellipse.Fill>
                                    <SolidColorBrush Color="Red"></SolidColorBrush>
                                </Ellipse.Fill>
                            </Ellipse>
                            <ContentPresenter HorizontalAlignment="Center"
                                              VerticalAlignment="Center" />
                        </Grid>
                    </ControlTemplate>
                </Button.Template>
            </Button>
        </Grid>
    </Grid>
</Window>

 

위의 3가지 방법을 적절하게 변경하면 다르게도 표현 가능하다.

 

그럼 이번에는 Trigger(트리거)라는 것도 간단히 살펴보자.

이것은 사용자가 마우스를 버 특위에 올렸을 때 색상을 바꾼다던지 할 때 사용할 수 있다.

 

<Window x:Class="WPFTemplateSample.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:WPFTemplateSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Button Content="버튼"
                Width="100"
                Height="100">
            <Button.Template>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <Ellipse Width="100"
                                 Height="100"
                                 Name="redInnerCircle">
                            <Ellipse.Fill>
                                <SolidColorBrush Color="Red"></SolidColorBrush>
                            </Ellipse.Fill>
                        </Ellipse>
                        <ContentPresenter HorizontalAlignment="Center"
                                          VerticalAlignment="Center" />
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver"
                                 Value="True">
                            <Setter TargetName="redInnerCircle"
                                    Property="Fill"
                                    Value="lightblue"></Setter>
                            <Setter TargetName="redInnerCircle"
                                    Property="Cursor"
                                    Value="Hand"></Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Button.Template>
        </Button>
    </Grid>
</Window>

 

<Trigger Property="IsMouseOver" Value="True"> 이 부분이 핵심이다. 마우스가 오버되었을 때 redInnerCircle의 Fill속성의 값을 "lightblue"로 변경하도록 처리한 것이다. 동시에 커서 모양도 변경했다.

 

글자색도 변경해보면,

 

<Window x:Class="WPFTemplateSample.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:WPFTemplateSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Button Content="버튼" Name="btn"
                Width="100"
                Height="100">
            <Button.Template>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <Ellipse Width="100"
                                 Height="100"
                                 Name="redInnerCircle">
                            <Ellipse.Fill>
                                <SolidColorBrush Color="Red"></SolidColorBrush>
                            </Ellipse.Fill>
                        </Ellipse>
                        <!--<ContentPresenter HorizontalAlignment="Center"
                                          VerticalAlignment="Center" />-->
                        <TextBlock x:Name="tbContent" Text="{Binding ElementName=btn, Path=Content}" 
                                   HorizontalAlignment="Center" VerticalAlignment="Center" />
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver"
                                 Value="True">
                            <Setter TargetName="redInnerCircle"
                                    Property="Fill"
                                    Value="lightblue"></Setter>
                            <Setter TargetName="redInnerCircle"
                                    Property="Cursor"
                                    Value="Hand"></Setter>
                            <Setter TargetName="tbContent"
                                    Property="Foreground"
                                    Value="Blue"></Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Button.Template>
        </Button>
    </Grid>
</Window>

 

위 코드처럼 ContentPresenter를 주석 처리하고 콘텐츠를 표시할 TextBlock을 직접 사용할 수도 있다.

(ContentPresenter를 사용해도 처리된다.)

마우스 오버되었을 때 Foreground 색을 Blue로 변경한 코드이다.

코드에서는 <TextBlock>에 버튼의 ElementName과 Path를 바인딩해서 값이 나오도록 처리하고, Trigger에서 이 <TextBlock>의 색상을 바꾸도록 처리해보았다. 물론 이렇게 처리하지 않고, ContentPresenter를 그대로 사용하고 Trigger에서 마우스 오버되었을 때 Foregournd 색상을 바꾸도록 처리하면 된다. 이렇게도 처리할 수 있다는 것을 설명하기 위함이다.

 

여기까지 ControlTemplate에 대해 알아보았다. 

ControlTemplate의 컨트롤의 content가 아닌 외관을 꾸며주는 것을 알 수 있다. 그럼 DataTemplate은 content를 꾸며주는 것이라 볼 수 있다. (Kiosk 예제에서 활용된 것을 참고하자)

 

전체적으로 보면 2가지로 나뉘어지는 부분이 있다.

Content와 Items이다. 데이터 템플릿도 마찬가지로 ContentTemplate와 ItemTemplate 두 가지 방식이 있다.

 

정리부터 하면,

ContentTemplate 단일객에 사용,

ItemTemplate리스트와 그리드뷰등 여러 객체를 담는 데 사용한다고 볼 수 있다.

 

Kiosk 예제에 작성한 Listbox다. ItemTemplate안에 DataTemplate가 선언되었고, 콘텐츠의 모양은 이렇다고 정의하였다.

 

            <ListBox x:Name="lbMenus"
                     Margin="150,0,0,0"
                     FontSize="16"
                     FontWeight="SemiBold"
                     VerticalAlignment="Top"
                     BorderBrush="Transparent"
                     ScrollViewer.VerticalScrollBarVisibility="Disabled"
                     ScrollViewer.HorizontalScrollBarVisibility="Disabled">
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel />
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Width="250" Height="250">
                            <Image Source="{Binding imagePath}"/>
                            <TextBlock Text="{Binding name}" Margin="0,24,0,0" HorizontalAlignment="Center"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

 

끝으로 ItemPanelTemplate는 이러한 ItemTemplate이 어떤 형태로 정렬되는가를 작성한 것이다. 

리스트박스의 모양은 위에서 아래로 리스트가 보이는 형태인데 아래처럼 선언하면 좌측부터 아이템들이 쌓이고, 윈도우 사이즈에 따라 우측에 더 이상 표시할 수 없으면 하단으로 아이템을 정렬해나가도록 처리가 된다.

 

                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel />
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>

 

지금까지 템플릿에 대해 살펴보았다.

내용이 매우 어렵다고 느낄 수도 있고, 잘 정리가 안될 수도 있다.

자유도가 높다보니 다양한 케이스를 만들어 낼 수 있기 때문에 그만큼 어려워 보일 수 있을 거라 생각된다.

우선은 모든 내용을 이해하기 보다 여러 스타일들을 사용해보면 적응해나가는 게 필요해 보인다.

 

WPF의 맛을 제대로 보려면 애니메이션까지 적용해봐야 하는데 갈길이 태산이니...