XAML 및 C#을 사용한 사용자 지정 컨트롤 빌드

이미 알려진 대로 Windows 8 XAML 플랫폼의 가장 강력한 기능 중 하나는 사용자 지정 컨트롤을 유연하게 구현할 수 있는 점입니다. XAML은 종속성 속성 및 컨트롤 템플릿과 같은 기능을 제공하므로 사용자 지정이 가능한 풍부한 기능의 컨트롤을 쉽게 만들 수 있습니다.

이전 글 'JavaScript용 Windows 라이브러리(WinJS)를 통한 사용자 지정 컨트롤 제작'에서는 Jordan Matthiesen이 사용자 지정 HelloWorld 컨트롤을 만드는 방법에 대해 설명했습니다. 이번에는 XAML에서 동일한 컨트롤을 만드는 방법을 알아보고, 재사용이 가능한 사용자 지정 컨트롤을 만드는 기술과 개념, 그리고 이러한 컨트롤을 디자인하는 템플릿을 만드는 방법을 소개하겠습니다. 아울러 종속성 속성과 같은 개념에 대해 설명하고, 사용자 지정 Generic.xaml 파일을 사용하여 기본 컨트롤 템플릿을 정의하는 암시적 스타일을 만드는 방법도 알아보겠습니다.

단순한 XAML 컨트롤

먼저, Windows.UI.XAML.Controls.Control에서 파생된 클래스인 Hello World 컨트롤을 빌드합니다. 새 프로젝트 템플릿을 사용하여 Visual Studio에 새 프로젝트를 만듭니다. 프로젝트 이름을 CustomControls로 지정합니다. 새 항목 템플릿 마법사를 사용하여 사용자 지정 컨트롤을 추가합니다.

Visual Studio는 템플릿 기반 컨트롤의 항목 템플릿을 포함하고 있습니다. 프로젝트를 마우스 오른쪽 단추로 클릭하고 [추가] -> [새 항목]을 선택합니다.

Visual Studio는 템플릿 기반 컨트롤의 항목 템플릿을 포함하고 있습니다.프로젝트를 마우스 오른쪽 단추로 클릭하고 [추가] -> [새 항목]을 선택합니다.

템플릿 기반 컨트롤 항목 템플릿은 사용자 정의 컨트롤을 시작할 수 있는 파일 및 동일한 골격 코드를 생성합니다.

템플릿 기반 컨트롤 항목을 선택하고 컨트롤의 이름을 'HelloWorld.cs'로 지정합니다.

이 템플릿은 다음 클래스를 생성합니다.

 public class HelloWorld : Control
{
    public HelloWorld()    {
        this.DefaultStyleKey = typeof(HelloWorld);
    }
}

이 짧은 블록의 코드에 두 가지 매우 중요한 세부 사항이 명시되어 있습니다. 첫째, HelloWorld 클래스가 컨트롤에서 파생됩니다. 둘째, DefaultStyleKey 설정은 이 클래스가 암시적 스타일을 사용하는 XAML 플랫폼임을 나타냅니다. 또한 템플릿 기반 컨트롤 템플릿은 Themes라는 이름의 폴더를 추가하고 해당 폴더에 Generic.xaml이라는 이름의 새 파일을 생성합니다. 이 템플릿에 대한 자세한 내용은 프로젝트 템플릿을 참조하세요.

컨트롤의 기본 스타일은 플랫폼이 자동으로 로드하는 사용자 지정 Generic.xaml 파일에 정의되어 있습니다. 이제 이 파일에서 암시적 스타일을 정의해야 합니다. 즉 특정 유형의 모든 컨트롤에 기본적으로 적용되는 하나의 스타일을 정의할 수 있습니다. 다음과 같이 Themes 폴더의 새로운 Generic.xaml 파일에 강조 표시된 XAML을 추가합니다.

 <ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/XAML/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/XAML"
    xmlns:local="using:CustomControls">

    <Style TargetType="local:HelloWorld">
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:HelloWorld">
                    <Border
                       Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <TextBlock Text="HelloWorld" 
                               FontFamily="Segoe UI Light"
                               FontSize="36"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style> 
</ResourceDictionary>

이와 같은 몇 줄의 XAML은 HelloWorld 컨트롤의 모든 인스턴스에 기본적으로 적용될 스타일을 정의합니다. 그럼 컨트롤 템플릿을 정의하고, 'HelloWorld' 텍스트를 통해 이 컨트롤이 단순한 텍스트 블록임을 명시하도록 합니다.

이제 MainPage.xaml 파일로 이동하여 다음 마크업을 추가합니다. 이때 XAML이 HelloWorld 클래스를 찾을 수 있도록 네임스페이스를 알려 주는 'local:' 표시를 포함시켜야 합니다. 'local:' 표시는 XAML 파일 상단에 정의되어 있습니다.

 <local:HelloWorld />

앱을 실행하면 컨트롤이 로드되어 "Hello, World!" 텍스트가 표시됨을 확인할 수 있습니다.

컨트롤 옵션 정의하기

구성 가능 옵션을 추가하면 더욱 유용하고 재사용이 가능한 컨트롤을 만들 수 있습니다. 컨트롤이 깜박이는 효과를 설정하는 옵션을 추가해 보겠습니다.

이 옵션을 추가하기 위해 컨트롤에 종속성 속성(DP)을 추가합니다. 종속성 속성 개요에서 DP에 대한 자세한 내용을 확인할 수 있습니다. 한편 DP를 매우 쉽게 추가할 수 있는 Visual Studio 코드 조각이 있으므로, 생성자 아래에 커서를 두고 'propdp'를 입력한 다음 탭 키를 두 번 누릅니다. 코드 조각의 각 매개 변수를 탭으로 이동하면서 사양을 입력할 수 있습니다.

 public class HelloWorld : Control
{
    public HelloWorld()    {
        this.DefaultStyleKey = typeof(HelloWorld);
    }

    public bool Blink
    {
        get { return (bool)GetValue(BlinkProperty); }
        set { SetValue(BlinkProperty, value); }
    }

    // Using a DependencyProperty enables animation, styling, binding, etc.
    public static readonly DependencyProperty BlinkProperty =
        DependencyProperty.Register(
            "Blink",                  // The name of the DependencyProperty
            typeof(bool),             // The type of the DependencyProperty
            typeof(HelloWorld),       // The type of the owner of the DependencyProperty
            new PropertyMetadata(     // OnBlinkChanged will be called when Blink changes
                false,                // The default value of the DependencyProperty
                new PropertyChangedCallback(OnBlinkChanged)
            )
        );

    private DispatcherTimer __timer = null;
    private DispatcherTimer _timer
    {
        get
        {
            if (__timer == null)
            {
                __timer = new DispatcherTimer();
                __timer.Interval = new TimeSpan(0,0,0,0,500); // 500 ms interval
                __timer.Tick += __timer_Tick;
            }

            return __timer;
        }
    }

    private static void OnBlinkChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e
    )
    {
        var instance = d as HelloWorld;
        if (instance != null)
        {
            if (instance._timer.IsEnabled != instance.Blink)
            {
                if (instance.Blink)
                {
                    instance._timer.Start();
                }
                else
                {
                    instance._timer.Stop();
                }
            }
        }
    }

    private void __timer_Tick(object sender, object e)
    {
        DoBlink();
    }

    private void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
    }
}

MainPage.xaml로 돌아와 컨트롤에 구성 가능 옵션을 추가합니다.

 <local:HelloWorld Blink="True" />

앱을 실행하고 컨트롤이 깜박이며 사라지는 것을 확인합니다!

이벤트에 대한 지원 추가하기

컨트롤에 이벤트를 추가하면 기능을 개선할 수 있습니다. 이벤트를 사용하면 작업이 수행될 때 컨트롤에서 인터럽트를 얻을 수 있으며, 작업에 반응하는 코드를 실행할 수 있습니다. 컨트롤이 깜박일 때마다 작동되는 이벤트를 추가해 보겠습니다.

 public class HelloWorld : Control
{
    
    ...
...
    private void __timer_Tick(object sender, object e)
    {
        DoBlink();
    }

    private void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
        OnBlinked();
    }

    public event EventHandler Blinked;

    private void OnBlinked()
    {
        EventHandler eh = Blinked;
        if (eh != null)
        {
            eh(this, new EventArgs());
        }
    }
}

MainPage.xaml로 돌아와 이 요소에 x:Name 속성을 추가하여 나중에 코드에서 컨트롤 인스턴스를 검색할 수 있도록 합니다.

 <local:HelloWorld x:Name="HelloWorldWithEvents" Blink="True" />

이제 MainPage.xaml.cs에 이벤트가 작동할 때 디버그 출력을 인쇄하는 이벤트 수신기 함수 대리자를 추가합니다.

 HelloWorldWithEvents.Blinked += (object sender, EventArgs args) =>
{
    System.Diagnostics.Debug.WriteLine("HelloWorldWithEvents Blinked");
};

이 코드를 실행하면 Visual Studio의 출력 콘솔에 매 500밀리초마다 메시지가 출력됩니다.

공개 메서드 노출하기

컨트롤이 깜박이는 효과를 주는 전용 메서드 DoBlink는 이미 작성했습니다. 이제 이 메서드를 공개하도록 설정함으로써 언제든지 컨트롤이 깜박이는 효과를 설정할 수 있는 옵션을 제공할 수 있습니다. DoBlink 메서드의 표시 여부를 공개로 설정하기만 하면 됩니다.

 public class HelloWorld : Control
{
    ...

    public void DoBlink()
    {
        this.Opacity = (this.Opacity + 1) % 2;
        OnBlinked();
    }

    ...
}

이제 단추 클릭 처리기에서 C# 코드 숨김의 공개 DoBlink 메서드를 호출할 수 있습니다.

 private void Button_Click_1(object sender, RoutedEventArgs e)
{
    HelloWorldWithEvents.DoBlink();
}    

컨트롤 다시 사용하기

컨트롤을 단일 프로젝트에만 사용하거나 개인 용도로 사용할 경우에는 패키징 및 배포 측면에서 고려해야 할 사항이 그리 많지 않습니다. 그러나 다른 개발자와 공유할 수 있는 재사용 가능 컨트롤을 만들 경우에는 몇 가지 옵션이 존재합니다. 이러한 옵션 중 하나는 다른 개발자가 자신의 컴퓨터에 설치하고 프로젝트에 추가할 수 있는 Visual Studio Extension SDK를 만드는 것입니다. 이 방법에 대한 자세한 내용은 C# 또는 Visual Basic을 사용하여 SDK 만들기를 확인하세요.

요약

지금까지 다음 사항을 구현하기 위해 필요한 컨트롤의 가장 일반적인 측면에 대해 알아보았습니다.

  1. 페이지에 컨트롤 가져오기
  2. 구성 가능 옵션에 전달하기
  3. 이벤트에 발송하고 응답하기
  4. 공개 메서드를 통해 기능 노출하기

이로써, XAML 사용자 지정 컨트롤에 대한 기초 지식을 익혔습니다. 다음 단계는 기존 XAML 컨트롤에 추가할 몇 가지 기능을 찾고 자신의 기능을 추가할 수 있도록 스타일을 변경하거나 컨트롤을 서브 클래스로 분류하는 작업입니다. 제 경험상 사용자 지정 컨트롤 사용에 익숙해질수록 더 확신을 갖고 XAML 앱을 작성할 수 있었습니다. 사용자 지정 컨트롤을 직접 경험해 보고, 그 경험담을 온라인에 올려 주시기 바랍니다.

이 글이 여러분에게 많은 도움이 되기를 바랍니다. 컨트롤 제작 관련 질문이 있는 경우 Windows 개발자 센터의 포럼을 통해 문의하세요.

- Windows 프로그램 관리자

Aaron Wroblewski