C# - WPF, MVVM 노트 (2)

업데이트:

모양 사용자 정의

템플릿

템플릿이란?

WPF 컨트롤은 마음대로 변경이 가능한 기본 모양이 제공된다. 이것이 템플릿화이다.

모든컨트롤에는 Template 속성이 있다. 컨트롤에 새 모양을 제공하려면 새로운 ControlTemplate 인스턴스를 컨트롤 속성에 할당한다.

<Button Content="Press me">
  <Button.Template>
    <ControlTemplate TargetType="{x:Type Button}">
      <Ellipse Fill="GreenYellow" Width="100" Height="100" />
    </ControlTemplate>
  </Button.Template>
</Button>

ControlTemplate이 버튼에 적용된다는 것을 알지만 TargetType은 지정해줘야한다.

위의 결과로 원은 크기 조절도 안먹히고 Press me 텍스트도 표시되지 않는다. 이때 필요한 것이 TemplateBinding 이다.


블렌드

실제로 수동으로 XAML을 작성하지않고 간단하게 수준높은 디자인이 가능하다. 그걸 가능하게 해주는 디자인 툴이 Blend이다.


TemplateBinding

이전의 예제에서 TemplateBinding 구문을 추가하여 기존의 템플릿 컨트롤의 속성을 참조할 수 있다.

<Button Content="Press me">
  <Button.Template>
    <ControlTemplate TargetType="{x:Type Button}">
      <Ellipse Fill="{TemplateBinding Background}" Width="100" Height="100" />
      <Label Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center">
    </ControlTemplate>
  </Button.Template>
</Button>

Label 컨트롤을 사용한 이유는 Label 컨트롤이 모든 컨텐츠를 허용하기 때문이다.


ItemsPresenter

ListBox 및 ComboBox 같은 Items 속성이 있는 목록 컨트롤도 템플릿으로 만들 수 있다.

각 개별 요소의 모양을 변경하려면 ItemTemplate을 사용한다. 전체를 변경해야할 경우에는 해당 컨트롤의 Template 속성을 사용한다.

ItemTemplate의 경우에는 실제 항목을 표시하고 싶은 곳에 삽입만 하면 잘 동작한다.


예제

<Button Grid.Column="1" Grid.Row="1" Margin="5" Content="Send">
  <Button.Template>
    <ControlTemplate TargetType="{x:Type Button}">
      <Grid>
        <Ellipse Fill="#AA000000" Margin="10,10,0,0" />
        <Ellipse Fill="{TemplateBinding Background}" Margin="0,0,10,10" />
        <Viewbox Margin="5,5,15,15">
          <ContentPresenter/>
        </Viewbox>
      </Grid>
    </ControlTemplate>
  </Button.Template>
</Button>

Viewbox는 말 그대로 뷰를 담는 그릇만을 나타내는 컨트롤이고 ContentPresenter은 화면에 뿌려주는 역할을 한다.


리소스

리소스란?

XAML에서 여러 컨트롤을 통해 일부 XAML을 공유할 필요가 있을 수 있다.
리소스를 사용하면 애플리케이션 전체에서 언제나 이를 응답할 수 있다.

애플리케이션의 모든 컨트롤은 Resources 속성을 통해 리소스 객체를 추가할 수 있다.

  • App.xaml ```xml

```xml
<Label Content="{StaticResource button}" background="{StaticResource accentBrush}" />

실제로 StaticResource 말고도 DynamicResource 구문도 있지만 잘 사용하지 않는다.

Static이 아닌 범위 지정으로 리소스를 지정한다면 ResourceDictionary로 선언할 수 있다.


ResourceDictionaries

위에서 설명한 방법으로는 App.xaml 파일의 유지 보수가 어려울 수 있다.

따라서 몇 개의 리소스 사전 파일을 추가하고 리소스를 xaml 파일에 두는 방식을 많이 사용한다.

<Window.Resources>
  <ResourceDictionary Source="Brushes.xaml" />
</Window.Resources>


예제

<Application.Resources>
  <LinearGradientBrush x:Key="background">
    <GradientStop Color="#FFDBFFE7" Offset="0" />
    <GradientStop Color="#FF03882D" Offset="1" />
  </LinearGradientBrush>
</Application.Resources>


스타일

스타일이란?

스타일은 컨트롤의 모양을 스타일링하는 방법이다.

이전에 설명했던 리소스만을 이용한다면 배경, 너비, 높이를 각각 리소스로 지정하고 각 컨트롤에서는 다시 세 가지 속성에서 리소스를 참조해야한다.

스타일을 사용하면 이를 간편하게 할 수 있다.

  • App.xaml
<Style x:Key="niceButton" TargetType="Button">
  <Setter Property="Width" Value="50" />
  <Setter Property="Height" Value="50" />
  <Setter Property="Background" >
    <Setter.Value>
      <LinearGradientBrush>
        <GradientStop Color="Red" />
        <GradientStop Color="Yellow" Offset="1" />
      </LinearGradientBrush>
    </Setter.Value>
  </Setter>
</Style>
<StackPanel>
  <Button Style="{StaticResource niceButton}"> A</Button>
  <Button>B</Button>
  <Button Style="{StaticResource niceButton}">C</Button>
  <Button Style="{StaticResource niceButton}">D</Button>
</StackPanel>

두 번째 버튼은 명시적으로 스타일을 지정하지 않았으므로 스타일이 적용되지 않는다.


스타일은 키를 할당하지 않고도 리소스를 저장할 수 있다. 키 할당이 없는 경우 TargetType과 일치하는 모든 컨트롤에 자동으로 사용된다. 이것이 암시적인 스타일이다.

<Page ...>
  <Page.Resources>
    <Style TargetType="Button">
      ...
    </Style>
  </Page.Resource>
  <StackPanel Orientation="Horizontal">
    <Button>A</Button>
    <Button>B</Button>
    <Button>C</Button>
    <Button>D</Button>
  </StackPanel>
</Page>


테마

실제로 디자이너와 작업한다면 디자이너는 템플릿과 속성 값을 적용하는 스타일이 포함된 ResourceDictionary 파일을 제공한다. 이 파일을 테마라고 할 수 있다.

물론 무료로 사용할 수 있는 테마들이 있다.

https://wpfthemes.codeplex.com


변형

모든 컨트롤은 쉽게 크기 변경, 회전 등이 가능하다.

모든 컨트롤에는 변형을 위해 사용가능한 RenderTransform, LayoutTransform이 있다.

RenderTranform은 컨트롤에 필요한 크기를 계산할 때 변환을 고려하지 않는 반면, LayoutTransform은 변환을 고려한다. 따라서 실제로 LayoutTransform은 자주 사용되지 않는다.


컨트롤 상태

애니메이션을 적용하는 경우 마우스를 누른 상태, 놓은 상태, 영역에 들어온 상태 등 여러가지 상태에 따른 모양을 만들어야 한다.

이 경우 컨트롤 상태를 사용하면 편리하다. 버튼의 경우에는 단지 두 가지 상태로 모양을 수정한다.

각 컨트롤은 특성을 사용해 자신의 상태 목록을 선언한다.

Button의 예로는 다음과 같다.

[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Pressed", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusStates")]
public class Button : ButtonBase { ... }


여기서 상태를 그룹화할 수 있다.

<ControlTemplate
  TargetType="Button"
  xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
  <Border x:Name="RootElement">
    <vsm:VisualStateGroup x:Name="CommonStates">
      <vsm:VisualState x:Name="MouseOver">
        <Storyboard>
          <ColorAnimaion
            Storyboard.TargetName="BorderBrush"
            Storyboard.TargetProperty="Color"
            To="Red" />
        </Storyboard>
      </vsm:VisualState>
...
<vsm:VisualTransition GeneratedDuration="0:0:1.5">

특정 상태로 들어갈 때, 다른 타이밍을 갖게 VisualTransition의 From 및 To 속성을 사용할 수도 있다.

실제로 Blend를 사용하면 굉장히 편리하게 이를 구현할 수 있다.

  1. 템플릿 정의 모드로 들어간다 (컨트롤 오른쪽 클릭 - 템플릿 편집 - 현재 항목).

  2. 상태창을 연다.

  3. 원하는 상태 이름을 클릭한다.

  4. 속성을 변경하거나 속성을 움직여서 필요한 변경을 한다.

  5. 상태 창에서 그룹 이름을 클릭해 전환 시간을 지정한다.


애니메이션

상태는 대부분의 상황에서 애니메이션을 생성하는 쉬운 방법이다.
상태는 전환의 세부 사항이 아닌 최종 상태에 집중하기 때문에 애니메이션을 쉽게 유지 관리할 수 있다.

하지만 전환을 여러 단계로 나누기와 같은 방식을 구현할 경우에는 어려울 수 있다. 이 때 StoryBoard를 사용한다.

애니메이션은 XAML을 사용해 선언되고 리소스로 저장된다.

<Page.Resources>
  <Storyboard x:Key="rotateFast">
    <DoubleAnimationUsingKeyFrames
      BeginTime="00:00:00"
      Storyboard.TargetName="rotation"
      Storyboard.TargetProperty="Angle">
      <SplineDoubleKeyFrame KeyTime="00:00:02" Value="90"/>
    </DoubleAnimationUsingKeyFrames>
  </Storyboard>
</Page.Resources>


애니메이션이 선언되면 XAML 코드나 C#을 통해 애니메이션을 트리거할 수 있다.

코드 비하인드에서 작성하면 다음과 같다.

rotateFast.Begin();


마찬가지로 애니메이션을 구현하는 것도 Blend를 사용하는게 훨씬 편리하다.
StoryBoard 편집기를 열고 작업하면 간단하게 가능하다.

  • 참고자료: WPF MVVM 일주일 만에 배우기 - XAML, C#, MVVM 패턴 (에이콘)

태그: , ,

카테고리:

업데이트:

댓글남기기