C++/WinRT를 사용한 XAML 사용자 지정(템플릿 기반) 컨트롤XAML custom (templated) controls with C++/WinRT

중요

C++/WinRT를 사용해 런타임 클래스를 사용하거나 작성하는 방법을 더욱 쉽게 이해할 수 있는 필수 개념과 용어에 대해서는 C++/WinRT를 통한 API 사용C++/WinRT를 통한 API 작성을 참조하세요.For essential concepts and terms that support your understanding of how to consume and author runtime classes with C++/WinRT, see Consume APIs with C++/WinRT and Author APIs with C++/WinRT.

UWP(유니버설 Windows 플랫폼)의 가장 강력한 기능 중 하나는 XAML 컨트롤 형식을 기반으로 사용자 지정 컨트롤을 만들기 위해 UI(사용자 인터페이스) 스택이 제공하는 유연성입니다.One of the most powerful features of the Universal Windows Platform (UWP) is the flexibility that the user-interface (UI) stack provides to create custom controls based on the XAML Control type. XAML UI 프레임워크는 사용자 지정 종속성 속성연결 속성과 같은 기능뿐 아니라, 기능이 풍부하고 사용자 지정 가능한 컨트롤을 쉽게 작성할 수 있는 컨트롤 템플릿을 제공합니다.The XAML UI framework provides features such as custom dependency properties and attached properties, and control templates, which make it easy to create feature-rich and customizable controls. 이 항목에서는 C++/WinRT를 사용하여 사용자 지정(템플릿 기반) 컨트롤을 만드는 단계를 안내합니다.This topic walks you through the steps of creating a custom (templated) control with C++/WinRT.

비어 있는 앱(BgLabelControlApp) 만들기Create a Blank App (BgLabelControlApp)

먼저 Microsoft Visual Studio에서 새 프로젝트를 만듭니다.Begin by creating a new project in Microsoft Visual Studio. 비어 있는 앱(C++/WinRT) 프로젝트를 만들어서 이름을 BgLabelControlApp으로 지정합니다.Create a Blank App (C++/WinRT) project, and name it BgLabelControlApp. 이 항목의 뒤쪽 섹션에서는 프로젝트(다음까지 빌드하지 않음)를 빌드하게 됩니다.In a later section of this topic, you'll be directed to build your project (don't build until then).

사용자 지정(템플릿 기반) 컨트롤을 나타내는 새 클래스를 작성합니다.We're going to author a new class to represent a custom (templated) control. 동일한 컴파일 단위 내에서 클래스를 작성하고 사용합니다.We're authoring and consuming the class within the same compilation unit. 하지만 XAML 태그에서 이 클래스를 인스턴스화할 수 있어야 하므로 결국 런타임 클래스가 될 것입니다.But we want to be able to instantiate this class from XAML markup, and for that reason it's going to be a runtime class. 그 밖에도 런타임 클래스를 작성하고 사용하는 데 모두 C++/WinRT를 사용합니다.And we're going to use C++/WinRT to both author and consume it.

새로운 런타임 클래스를 작성하려면 먼저 새 Midl 파일(.idl) 항목을 프로젝트에 추가합니다.The first step in authoring a new runtime class is to add a new Midl File (.idl) item to the project. 이름을 BgLabelControl.idl이라고 지정합니다.Name it BgLabelControl.idl. BgLabelControl.idl의 기본 콘텐츠를 삭제한 후 아래 런타임 클래스 선언에 붙여넣습니다.Delete the default contents of BgLabelControl.idl, and paste in this runtime class declaration.

// BgLabelControl.idl
namespace BgLabelControlApp
{
    runtimeclass BgLabelControl : Windows.UI.Xaml.Controls.Control
    {
        BgLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

위 목록에서는 DP(종속성 속성)를 선언할 때 따라야 하는 패턴을 보여 줍니다.The listing above shows the pattern that you follow when declaring a dependency property (DP). 각 DP에는 두 개의 조각이 있습니다.There are two pieces to each DP. 먼저 DependencyProperty 형식의 읽기 전용 정적 속성을 선언합니다.First, you declare a read-only static property of type DependencyProperty. 여기에는 DP 및 ‘속성’의 이름이 포함됩니다. It has the name of your DP plus Property. 구현에서 이 정적 속성을 사용합니다.You'll use this static property in your implementation. 두 번째로, DP의 형식 및 이름으로 읽기-쓰기 인스턴스 속성을 선언합니다.Second, you declare a read-write instance property with the type and name of your DP. DP 대신 ‘연결된 속성’을 작성하려면 사용자 지정 연결 속성에서 코드 예제를 참조하세요. If you wish to author an attached property (rather than a DP), then see the code examples in Custom attached properties.

참고

DP를 부동 소수점 형식과 함께 사용하려면 DP를 double(MIDL 3.0Double)로 설정합니다.If you want a DP with a floating-point type, then make it double (Double in MIDL 3.0). float(MIDL의 Single) 형식의 DP를 선언하고 구현한 후 XAML 태그에서 해당 DP의 값을 설정하면 ‘텍스트 ‘’에서 ‘Windows.Foundation.Single’을 만들지 못했습니다.’ 오류가 발생합니다. .Declaring and implementing a DP of type float (Single in MIDL), and then setting a value for that DP in XAML markup, results in the error Failed to create a 'Windows.Foundation.Single' from the text ''.

파일을 저장하고 프로젝트를 빌드합니다.Save the file and build the project. 빌드 프로세스에서 midl.exe 도구가 실행되어 런타임 클래스를 설명하는 Windows 런타임 메타데이터 파일(\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd)을 생성합니다.During the build process, the midl.exe tool is run to create a Windows Runtime metadata file (\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd) describing the runtime class. 그런 다음, cppwinrt.exe 도구가 실행되어 런타임 클래스를 작성하거나 사용하도록 지원하는 원본 코드 파일을 생성합니다.Then, the cppwinrt.exe tool is run to generate source code files to support you in authoring and consuming your runtime class. 원본 코드 파일에는 IDL로 선언한 BgLabelControl 런타임 클래스의 구현을 시작할 수 있는 스텁이 포함됩니다.These files include stubs to get you started implementing the BgLabelControl runtime class that you declared in your IDL. 이 스텁이 \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.hBgLabelControl.cpp입니다.Those stubs are \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h and BgLabelControl.cpp.

스텁 파일인 BgLabelControl.hBgLabelControl.cpp\BgLabelControlApp\BgLabelControlApp\Generated Files\sources\에서 프로젝트 폴더인 \BgLabelControlApp\BgLabelControlApp\로 복사합니다.Copy the stub files BgLabelControl.h and BgLabelControl.cpp from \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ into the project folder, which is \BgLabelControlApp\BgLabelControlApp\. 솔루션 탐색기에서 모든 파일 표시가 설정되어 있는지 확인합니다.In Solution Explorer, make sure Show All Files is toggled on. 복사한 스텁 파일을 마우스 오른쪽 단추로 클릭하고 프로젝트에 포함을 클릭합니다.Right-click the stub files that you copied, and click Include In Project.

BgLabelControl 사용자 지정 컨트롤 클래스를 구현합니다.Implement the BgLabelControl custom control class

이제 \BgLabelControlApp\BgLabelControlApp\BgLabelControl.hBgLabelControl.cpp를 열고 런타임 클래스를 구현합니다.Now, let's open \BgLabelControlApp\BgLabelControlApp\BgLabelControl.h and BgLabelControl.cpp and implement our runtime class. BgLabelControl.h에서는 생성자를 변경하여 기본 스타일 키를 설정하고, LabelLabelProperty를 구현하고, OnLabelChanged라는 정적 이벤트 처리기를 추가하여 변경된 종속성 속성 값을 처리하고, LabelProperty의 지원 필드를 저장할 프라이빗 멤버를 추가합니다.In BgLabelControl.h, change the constructor to set the default style key, implement Label and LabelProperty, add a static event handler named OnLabelChanged to process changes to the value of the dependency property, and add a private member to store the backing field for LabelProperty.

해당 항목을 추가한 후 BgLabelControl.h는 다음과 같이 표시됩니다.After adding those, your BgLabelControl.h looks like this.

// BgLabelControl.h
...
struct BgLabelControl : BgLabelControlT<BgLabelControl>
{
    BgLabelControl() { DefaultStyleKey(winrt::box_value(L"BgLabelControlApp.BgLabelControl")); }

    winrt::hstring Label()
    {
        return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
    }

    void Label(winrt::hstring const& value)
    {
        SetValue(m_labelProperty, winrt::box_value(value));
    }

    static Windows::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }

    static void OnLabelChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);

private:
    static Windows::UI::Xaml::DependencyProperty m_labelProperty;
};
...

BgLabelControl.cpp에서 이와 같은 정적 멤버를 정의합니다.In BgLabelControl.cpp, define the static members like this.

// BgLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty BgLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<BgLabelControlApp::BgLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &BgLabelControl::OnLabelChanged } }
);

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& /* e */)
{
    if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
    {
        // Call members of the projected type via theControl.

        BgLabelControlApp::implementation::BgLabelControl* ptr{ winrt::get_self<BgLabelControlApp::implementation::BgLabelControl>(theControl) };
        // Call members of the implementation type via ptr.
    }
}
...

이 연습에서는 OnLabelChanged를 사용하지 않습니다.In this walkthrough, we won't be using OnLabelChanged. 하지만 속성 변경 콜백에 종속성 속성을 등록하는 방법을 확인할 수 있도록 제공됩니다.But it's there so that you can see how to register a dependency property with a property-changed callback. OnLabelChanged 구현은 기본 프로젝션된 형식에서 파생된 프로젝션된 형식을 가져오는 방법도 보여 줍니다(기본 프로젝션된 형식은 이 클래스의 DependencyObject임).The implementation of OnLabelChanged also shows how to obtain a derived projected type from a base projected type (the base projected type is DependencyObject, in this case). 또한 프로젝션된 형식을 구현하는 형식에 대한 포인터를 가져오는 방법을 보여 줍니다.And it shows how to then obtain a pointer to the type that implements the projected type. 이 두 번째 작업은 기본적으로 프로젝션된 형식을 구현하는 프로젝트(런타임 클래스를 구현하는 프로젝트)에서만 가능합니다.That second operation will naturally only be possible in the project that implements the projected type (that is, the project that implements the runtime class).

참고

Windows SDK 버전 10.0.17763.0(Windows 10 버전 1809) 이상을 설치하지 않은 경우 winrt::get_self 대신, 위의 종속성 속성 변경 이벤트 처리기에서 winrt::from_abi를 호출해야 합니다.If you haven't installed the Windows SDK version 10.0.17763.0 (Windows 10, version 1809), or later, then you need to call winrt::from_abi in the dependency property changed event handler above, instead of winrt::get_self.

BgLabelControl의 기본 스타일을 디자인합니다.Design the default style for BgLabelControl

해당 생성자에서 BgLabelControl은 스스로 기본 스타일 키를 설정합니다.In its constructor, BgLabelControl sets a default style key for itself. 그러나 기본 스타일이란 ‘무엇인가요’? But what is a default style? 사용자 지정(템플릿 기반) 컨트롤의 경우 컨트롤의 소비자가 스타일 및/또는 템플릿을 설정하지 않는 경우 자체적으로 렌더링하는 데 사용할 수 있는 기본 컨트롤 템플릿을 포함하는 기본 스타일이 있어야 합니다.A custom (templated) control needs to have a default style—containing a default control template—which it can use to render itself with in case the consumer of the control doesn't set a style and/or template. 이 섹션에서는 기본 스타일을 포함하는 프로젝트에 태그 파일을 추가합니다.In this section we'll add a markup file to the project containing our default style.

프로젝트 노드에서 새 폴더를 만들고 이름을 “Themes”로 지정합니다.Under your project node, create a new folder and name it "Themes". Themes 아래에서 형식 Visual C++ > XAML > XAML 보기의 새 항목을 추가하고 이름을 “Generic.xaml”로 지정합니다.Under Themes, add a new item of type Visual C++ > XAML > XAML View, and name it "Generic.xaml". XAML 프레임워크가 사용자 지정 컨트롤의 기본 스타일을 찾으려면 폴더 및 파일 이름이 이와 같아야 합니다.The folder and file names have to be like this in order for the XAML framework to find the default style for a custom control. Generic.xaml의 기본 콘텐츠를 삭제하고 아래 태그에 붙여넣습니다.Delete the default contents of Generic.xaml, and paste in the markup below.

<!-- \Themes\Generic.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BgLabelControlApp">

    <Style TargetType="local:BgLabelControl" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:BgLabelControl">
                    <Grid Width="100" Height="100" Background="{TemplateBinding Background}">
                        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding Label}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

이 경우 기본 스타일이 설정하는 속성은 컨트롤 템플릿뿐입니다.In this case, the only property that the default style sets is the control template. 템플릿은 사각형(해당 배경이 XAML Control 형식의 모든 인스턴스에 있는 Background 속성에 바인딩됨) 및 텍스트 요소(해당 텍스트가 BgLabelControl::Label 종속성 속성에 바인딩됨)로 구성됩니다.The template consists of a square (whose background is bound to the Background property that all instances of the XAML Control type have), and a text element (whose text is bound to the BgLabelControl::Label dependency property).

기본 UI 페이지에 BgLabelControl 인스턴스 추가Add an instance of BgLabelControl to the main UI page

MainPage.xaml을 엽니다. 여기에는 기본 UI 페이지에 사용할 XAML 태그가 포함되어 있습니다.Open MainPage.xaml, which contains the XAML markup for our main UI page. StackPanel 내부에서 Button 요소 바로 뒤에 다음 태그를 추가합니다.Immediately after the Button element (inside the StackPanel), add the following markup.

<local:BgLabelControl Background="Red" Label="Hello, World!"/>

또한 MainPage 형식(컴파일 XAML 태그 및 필수 코드의 조합)이 BgLabelControl 사용자 지정 컨트롤 형식을 인식하도록 MainPage.h에 다음 include 지시문을 추가합니다.Also, add the following include directive to MainPage.h so that the MainPage type (a combination of compiling XAML markup and imperative code) is aware of the BgLabelControl custom control type. 다른 XAML 페이지에서 BgLabelControl을 사용하려면 해당 페이지의 헤더 파일에 동일한 include 지시문을 추가합니다.If you want to use BgLabelControl from another XAML page, then add this same include directive to the header file for that page, too. 또는 미리 컴파일된 헤더 파일에 단일 include 지시문을 넣으면 됩니다.Or, alternatively, just put a single include directive in your precompiled header file.

// MainPage.h
...
#include "BgLabelControl.h"
...

이제 프로젝트를 빌드하고 실행합니다.Now build and run the project. 기본 컨트롤 템플릿이 배경 브러시에 바인딩되고 태그의 BgLabelControl 인스턴스 레이블에 바인딩되는 것을 알 수 있습니다.You'll see that the default control template is binding to the background brush, and to the label, of the BgLabelControl instance in the markup.

이 연습에서는 C++/WinRT의 사용자 지정(템플릿 기반) 컨트롤에 대한 간단한 예제를 보여 줍니다.This walkthrough showed a simple example of a custom (templated) control in C++/WinRT. 임의로 풍부하고 모든 기능을 갖춘 고유한 사용자 지정 컨트롤을 만들 수 있습니다.You can make your own custom controls arbitrarily rich and full-featured. 예를 들어 사용자 지정 컨트롤은 편집 가능한 데이터 표, 비디오 플레이어 또는 3D 기하 도형 시각화 도우미만큼 복잡한 형식을 사용할 수 있습니다.For example, a custom control can take the form of something as complicated as an editable data grid, a video player, or a visualizer of 3D geometry.

MeasureOverrideOnApplyTemplate 같은 ‘재정의 가능’ 함수 구현 Implementing overridable functions, such as MeasureOverride and OnApplyTemplate

기본 런타임 클래스에서 추가로 파생된 Control 런타임 클래스에서 사용자 지정 컨트롤을 파생시킵니다.You derive a custom control from the Control runtime class, which itself further derives from base runtime classes. 파생 클래스에서 재정의할 수 있는 Control, FrameworkElementUIElement의 재정의 가능 메서드도 있습니다.And there are overridable methods of Control, FrameworkElement, and UIElement that you can override in your derived class. 작업을 수행하는 방법을 보여 주는 코드 예제는 다음과 같습니다.Here's a code example showing you how to do that.

struct BgLabelControl : BgLabelControlT<BgLabelControl>
{
...
    // Control overrides.
    void OnPointerPressed(Windows::UI::Xaml::Input::PointerRoutedEventArgs const& /* e */) const { ... };

    // FrameworkElement overrides.
    Windows::Foundation::Size MeasureOverride(Windows::Foundation::Size const& /* availableSize */) const { ... };
    void OnApplyTemplate() const { ... };

    // UIElement overrides.
    Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer() const { ... };
...
};

‘재정의 가능’ 함수는 다른 언어 프로젝션에서는 다르게 표현됩니다. Overridable functions present themselves differently in different language projections. 예를 들어 C#에서 재정의 가능 함수는 일반적으로 보호된 가상 함수로 표시됩니다.In C#, for example, overridable functions typically appear as protected virtual functions. C++/WinRT에서는 가상도 아니고 보호되지도 않지만 위에 표시된 것처럼 고유한 구현을 함수를 재정의하고 고유한 구현을 제공할 수 있습니다.In C++/WinRT, they're neither virtual nor protected, but you can still override them and provide your own implementation, as shown above.

중요 APIImportant APIs