컨트롤에 대한 템플릿을 만드는 방법(WPF.NET)

WPF(Windows Presentation Foundation)를 사용하면 다시 사용할 수 있는 템플릿을 사용하여 기존 컨트롤의 시각적 구조 및 동작을 사용자 지정할 수 있습니다. 템플릿은 애플리케이션, 창 및 페이지에 전역적으로 또는 컨트롤에 직접 적용할 수 있습니다. 새 컨트롤을 만들어야 하는 대부분의 시나리오는 이 방법 대신 기존 컨트롤의 새 템플릿을 만드는 방법으로 해결할 수 있습니다.

중요

.NET 6 및 .NET 5(.NET Core 3.1 포함)에 관한 데스크톱 가이드 설명서는 제작 중입니다.

이 문서에서는 Button 컨트롤의 새 ControlTemplate을 만드는 방법을 살펴봅니다.

ControlTemplate을 만들어야 하는 경우

컨트롤에는 Background, Foreground, FontFamily 등의 여러 속성이 있습니다. 이러한 속성은 컨트롤의 여러 모양을 제어하지만, 이러한 속성을 설정하여 변경할 수 있는 부분은 제한되어 있습니다. 예를 들어 CheckBox에서 Foreground 속성을 파란색으로 설정하고 FontStyle을 기울임꼴로 설정할 수 있습니다. 컨트롤의 모양을 컨트롤의 다른 속성에서 할 수 있는 이상으로 사용자 지정하려면 ControlTemplate을 만듭니다.

대부분의 사용자 인터페이스에서 단추의 모양은 일반적으로 약간의 텍스트가 있는 사각형으로 동일합니다. 모서리가 둥근 단추를 만들려면 단추에서 상속하는 새 컨트롤을 만들거나 단추의 기능을 다시 만들면 됩니다. 또한 새로운 사용자 컨트롤은 원형 시각적 개체를 제공합니다.

기존 컨트롤의 시각적 레이아웃을 사용자 지정하면 새 컨트롤을 만들지 않아도 됩니다. 모서리가 둥근 단추를 사용하여 원하는 시각적 레이아웃으로 ControlTemplate을 만듭니다.

반면, 새 기능, 다른 속성, 새 설정이 포함된 컨트롤이 필요한 경우 새 UserControl을 만듭니다.

사전 요구 사항

새 WPF 애플리케이션을 만들고, MainWindow.xaml(또는 원하는 다른 창)에서 <Window> 요소에 대한 다음 속성을 설정합니다.

속성
Title Template Intro Sample
SizeToContent WidthAndHeight
MinWidth 250

<Window> 요소의 콘텐츠를 다음 XAML로 설정합니다.

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button>Button 2</Button>
</StackPanel>

최종적으로 MainWindow.xaml 파일의 모양은 다음과 비슷합니다.

<Window x:Class="IntroToStylingAndTemplating.Window1"
        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:IntroToStylingAndTemplating"
        mc:Ignorable="d"
        Title="Template Intro Sample" SizeToContent="WidthAndHeight" MinWidth="250">
    <StackPanel Margin="10">
        <Label>Unstyled Button</Label>
        <Button>Button 1</Button>
        <Label>Rounded Button</Label>
        <Button>Button 2</Button>
    </StackPanel>
</Window>

애플리케이션을 실행하면 다음과 비슷한 모양입니다.

WPF window with two unstyled buttons

ControlTemplate 만들기

ControlTemplate을 선언하는 가장 일반적인 방법은 XAML 파일에 있는 Resources 섹션의 리소스입니다. 템플릿은 리소스이므로 모든 리소스에 적용되는 동일한 범위 지정 규칙을 따릅니다. 간단히 말해서, 템플릿을 선언하는 위치는 템플릿을 적용할 수 있는 위치에 영향을 줍니다. 예를 들어 애플리케이션 정의 XAML 파일의 루트 요소에서 컨트롤을 선언하면 애플리케이션의 모든 위치에서 템플릿을 사용할 수 있습니다. 창에서 템플릿을 정의하면 해당 창의 컨트롤만 템플릿을 사용할 수 있습니다.

먼저 Window.Resources 요소를 MainWindow.xaml 파일에 추가합니다.

<Window x:Class="IntroToStylingAndTemplating.Window2"
        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:IntroToStylingAndTemplating"
        mc:Ignorable="d"
        Title="Template Intro Sample" SizeToContent="WidthAndHeight" MinWidth="250">
    <Window.Resources>
        
    </Window.Resources>
    <StackPanel Margin="10">
        <Label>Unstyled Button</Label>
        <Button>Button 1</Button>
        <Label>Rounded Button</Label>
        <Button>Button 2</Button>
    </StackPanel>
</Window>

다음 속성 세트를 사용하여 새 <ControlTemplate>을 만듭니다.

속성
x:Key roundbutton
TargetType Button

이 컨트롤 템플릿은 간단하게 다음 중 하나가 됩니다.

  • 컨트롤의 루트 요소인 Grid
  • 단추의 둥근 모서리 모양을 그리는 Ellipse
  • 사용자 지정 단추 콘텐츠를 표시하는 ContentPresenter
<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

TemplateBinding

ControlTemplate을 만들 때에도 여전히 public 속성을 사용하여 컨트롤의 모양을 변경할 수 있습니다. TemplateBinding 태그 확장은 ControlTemplate에 있는 요소의 속성을 컨트롤이 정의하는 public 속성에 바인딩합니다. TemplateBinding을 사용하면 컨트롤의 속성이 템플릿의 매개 변수로 작동합니다. 즉, 컨트롤의 속성을 설정하면 해당 값은 TemplateBinding이 있는 요소로 전달됩니다.

타원

<Ellipse> 요소의 FillStroke 속성은 컨트롤의 ForegroundBackground 속성에 바인딩됩니다.

ContentPresenter

<ContentPresenter> 요소도 템플릿에 추가됩니다. 이 템플릿은 단추용으로 디자인되었으므로 단추가 ContentControl에서 상속한다는 점을 고려해야 합니다. 단추는 요소의 콘텐츠를 표시합니다. 단추 내에 일반 텍스트, 다른 컨트롤 등의 모든 것을 설정할 수 있습니다. 다음 두 단추 모두 유효한 단추입니다.

<Button>My Text</Button>

<!-- and -->

<Button>
    <CheckBox>Checkbox in a button</CheckBox>
</Button>

위의 두 예제에서 텍스트와 확인란은 Button.Content 속성으로 설정됩니다. 콘텐츠로 설정된 것은 그 무엇이든 <ContentPresenter>를 통해 표시할 수 있으며, 이것이 바로 템플릿이 하는 일입니다.

ControlTemplateButton 같은 ContentControl 형식에 적용되면 요소 트리에서 ContentPresenter가 검색됩니다. ContentPresenter가 발견되면 템플릿에서 자동으로 컨트롤의 Content 속성을 ContentPresenter에 바인딩합니다.

템플릿 사용

이 문서의 시작 부분에서 선언한 단추를 찾습니다.

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button>Button 2</Button>
</StackPanel>

두 번째 단추의 Template 속성을 roundbutton 리소스로 설정합니다.

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button Template="{StaticResource roundbutton}">Button 2</Button>
</StackPanel>

프로젝트를 실행하고 결과를 살펴보면 단추의 배경이 둥근 것을 볼 수 있습니다.

WPF window with one template oval button

단추가 원이 아니라 타원인 것을 볼 수 있습니다. <Ellipse> 요소는 그 작동 방식 때문에 항상 사용 가능한 공간을 채우도록 확장됩니다. 단추의 widthheight 속성을 동일한 값으로 변경하여 원을 균일하게 만듭니다.

<StackPanel Margin="10">
    <Label>Unstyled Button</Label>
    <Button>Button 1</Button>
    <Label>Rounded Button</Label>
    <Button Template="{StaticResource roundbutton}" Width="65" Height="65">Button 2</Button>
</StackPanel>

WPF window with one template circular button

트리거 추가

템플릿이 적용된 단추가 다르게 보이더라도 다른 단추와 동일하게 작동합니다. 단추를 누르면 Click 이벤트가 발생합니다. 그러나 마우스를 단추 위로 이동하면 단추의 시각적 개체가 변하지 않는 것을 볼 수 있습니다. 이러한 시각적 개체 상호 작용은 모두 템플릿에서 정의합니다.

WPF에서 제공하는 동적 이벤트 및 속성 시스템을 사용하면 값의 특정 속성을 살펴본 후 적시에 템플릿 스타일을 다시 지정할 수 있습니다. 이 예제에서는 단추의 IsMouseOver 속성을 살펴보겠습니다. 마우스가 컨트롤 위에 있을 때 <Ellipse> 스타일을 새로운 색으로 지정합니다. 이러한 유형의 트리거를 PropertyTrigger라고 합니다.

이 방법이 작동하려면 참조할 수 있는 <Ellipse>에 이름을 추가해야 합니다. backgroundElement라는 이름을 지정합니다.

<Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />

다음으로, ControlTemplate.Triggers 컬렉션에 새 Trigger를 추가합니다. 이 트리거는 IsMouseOver 이벤트의 true 값을 감시합니다.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="true">

        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

다음으로 <Ellipse>Fill 속성을 새로운 색으로 변경하는 <Trigger><Setter>를 추가합니다.

<Trigger Property="IsMouseOver" Value="true">
    <Setter Property="Fill" TargetName="backgroundElement" Value="AliceBlue"/>
</Trigger>

프로젝트를 실행합니다. 마우스를 단추 위로 이동하면 <Ellipse>의 색이 변합니다.

mouse moves over WPF button to change the fill color

VisualState 사용

시각적 상태는 컨트롤을 통해 정의하고 트리거됩니다. 예를 들어 마우스를 컨트롤 위로 이동하면 CommonStates.MouseOver 상태가 트리거됩니다. 컨트롤의 현재 상태에 따라 속성 변경에 애니메이션 효과를 적용할 수 있습니다. 이전 섹션에서는 <PropertyTrigger>를 사용하여 IsMouseOver 속성이 true일 때 단추의 전경을 AliceBlue로 변경하도록 했습니다. 여기서는 그 대신 색의 변화에 애니메이션 효과를 적용하는 시각적 상태를 만들어 부드럽게 전환하겠습니다. VisualStates에 대한 자세한 내용은 WPF의 스타일 및 템플릿을 참조하세요.

<PropertyTrigger>를 애니메이션 시각적 상태로 변환하려면 템플릿에서 <ControlTemplate.Triggers> 요소를 제거합니다.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

다음으로 컨트롤 템플릿의 <Grid> 루트에서 CommonStates에 대한 <VisualStateGroup>을 사용하여 <VisualStateManager.VisualStateGroups> 요소를 추가합니다. 두 가지 상태 NormalMouseOver를 정의합니다.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                </VisualState>
                <VisualState Name="MouseOver">
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

<VisualState>에 정의된 애니메이션은 이 상태가 트리거될 때 적용됩니다. 각 상태에 대한 애니메이션을 만듭니다. 애니메이션은 <Storyboard> 요소 내에 배치됩니다. 스토리보드에 대한 자세한 내용은 스토리보드 개요를 참조하세요.

  • 보통

    이 상태는 타원 채우기에 애니메이션 효과를 적용하여 컨트롤의 Background 색으로 복원합니다.

    <Storyboard>
        <ColorAnimation Storyboard.TargetName="backgroundElement" 
            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
            To="{TemplateBinding Background}"
            Duration="0:0:0.3"/>
    </Storyboard>
    
  • MouseOver

    이 상태는 타원 Background 색에 애니메이션 효과를 적용하여 새로운 Yellow 색으로 변경합니다.

    <Storyboard>
        <ColorAnimation Storyboard.TargetName="backgroundElement" 
            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
            To="Yellow" 
            Duration="0:0:0.3"/>
    </Storyboard>
    

<ControlTemplate>은 이제 다음과 비슷하게 표시됩니다.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                    <Storyboard>
                        <ColorAnimation Storyboard.TargetName="backgroundElement" 
                            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                            To="{TemplateBinding Background}"
                            Duration="0:0:0.3"/>
                    </Storyboard>
                </VisualState>
                <VisualState Name="MouseOver">
                    <Storyboard>
                        <ColorAnimation Storyboard.TargetName="backgroundElement" 
                            Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 
                            To="Yellow" 
                            Duration="0:0:0.3"/>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Ellipse Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter x:Name="contentPresenter" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

프로젝트를 실행합니다. 마우스를 단추 위로 이동하면 <Ellipse>의 색에 애니메이션 효과가 적용됩니다.

mouse moves over WPF button to change the fill color with a visual state

다음 단계