다음을 통해 공유


C++/WinRT를 사용하여 XAML 컨트롤 빌드

이 문서에서는 C++/WinRT를 사용하여 WinUI 3용 템플릿 XAML 컨트롤을 만드는 방법에 대해 설명합니다. 템플릿 컨트롤은 Microsoft.UI.Xaml.Controls.Control을 상속하며 XAML 컨트롤 템플릿을 사용하여 사용자 지정할 수 있는 시각적 구조 및 동작을 제공합니다. 이 문서에서는 WinUI 3을 사용하도록 조정된 문서 와 동일한 시나리오를 설명하며, 이전의 C++/WinRT XAML 사용자 지정(템플릿 처리) 컨트롤을 다루는 기사에 기반하고 있습니다.

필수 조건

  1. Windows 앱 개발 시작
  2. 최신 버전의 C++/WinRT Visual Studio Extension(VSIX)을 다운로드하고 설치합니다.

빈 앱 만들기(BgLabelControlApp)

먼저 Microsoft Visual Studio에서 새 프로젝트를 만듭니다. Create a new project 대화 상자에서 빈 앱(UWP의 WinUI) 프로젝트 템플릿을 선택하고, C++ 언어 버전을 선택했는지 확인합니다. 파일 이름이 아래 예제의 코드와 일치하도록 프로젝트 이름을 "BgLabelControlApp"으로 설정합니다. 대상 버전을 Windows 10 버전 1903(빌드 18362) 및 최소 버전을 Windows 10 버전 1803(빌드 17134)으로 설정합니다. 이 연습은 빈 앱, 패키지된(데스크톱의 WinUI) 프로젝트 템플릿으로 만든 데스크톱 앱에서도 사용할 수 있습니다. 단, BgLabelControlApp(데스크톱) 프로젝트의 모든 단계를 반드시 수행해야 합니다.

빈 앱 프로젝트 템플릿

앱에 템플릿 컨트롤 추가

템플릿 기반 컨트롤을 추가하려면 도구 모음에서 프로젝트 메뉴를 클릭하거나 솔루션 탐색기 에서 프로젝트를 마우스 오른쪽 단추로 클릭하고 새 항목 추가 를 선택합니다. Visual C++->WinUI에서 사용자 지정 컨트롤(WinUI) 템플릿을 선택합니다. 새 컨트롤의 이름을 "BgLabelControl"로 지정하고 추가를 클릭합니다. 그러면 프로젝트에 세 개의 새 파일이 추가됩니다. BgLabelControl.h 는 컨트롤 선언을 포함하는 헤더이며 BgLabelControl.cpp 컨트롤의 C++/WinRT 구현을 포함합니다. BgLabelControl.idl 는 컨트롤을 런타임 클래스로 인스턴스화할 수 있는 인터페이스 정의 파일입니다.

BgLabelControl 사용자 지정 컨트롤 클래스 구현

다음 단계에서는 프로젝트 디렉터리의 코드 BgLabelControl.idlBgLabelControl.hBgLabelControl.cpp 파일을 업데이트하여 런타임 클래스를 구현합니다.

템플릿화된 컨트롤 클래스는 XAML 마크업에서 인스턴스화되며, 이러한 이유로 런타임 클래스가 될 것입니다. 완성된 프로젝트를 빌드할 때 MIDL 컴파일러(midl.exe)는 BgLabelControl.idl 파일을 사용하여 컨트롤을 위한 Windows 런타임 메타데이터 파일(.winmd)을 생성하며, 이 파일은 구성 요소 소비자가 참조하게 됩니다. 런타임 클래스를 만드는 방법에 대한 자세한 내용은 C++/WinRT Author API를 참조하세요.

만드는 템플릿 컨트롤은 컨트롤에 대한 레이블로 사용될 문자열인 단일 속성을 노출합니다. 내용을 다음 코드로 바꿉니다 BgLabelControl.idl .

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

위의 목록에서는 종속성 속성(DP)을 선언할 때 따르는 패턴을 보여 줍니다. 각 DP에는 두 부분이 있습니다. 먼저 DependencyProperty 형식의 읽기 전용 정적 속성을 선언합니다. 당신의 DP 이름과 속성을 지닌 것이 있습니다. 구현에서 이 정적 속성을 사용합니다. 둘째, DP의 형식과 이름을 사용하여 읽기-쓰기 인스턴스 속성을 선언합니다. 연결된 속성(DP 대신)을 작성하려면 사용자 지정 연결된 속성의 코드 예제를 참조하세요.

위의 코드에서 참조된 XAML 클래스는 Microsoft.UI.Xaml 네임스페이스에 있습니다. 이는 Windows.UI.XAML 네임스페이스에 정의된 UWP XAML 컨트롤과 달리 WinUI 컨트롤로 구분됩니다.

BgLabelControl.h의 내용을 다음 코드로 바꿉니다.

// BgLabelControl.h
#pragma once
#include "BgLabelControl.g.h"

namespace winrt::BgLabelControlApp::implementation
{
    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 Microsoft::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }

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

    private:
        static Microsoft::UI::Xaml::DependencyProperty m_labelProperty;
    };
}
namespace winrt::BgLabelControlApp::factory_implementation
{
    struct BgLabelControl : BgLabelControlT<BgLabelControl, implementation::BgLabelControl>
    {
    };
}

위에서 설명한 코드는 LabelLabelProperty 속성을 구현하고, OnLabelChanged로 명명된 정적 이벤트 처리기를 추가하여 종속성 속성 값의 변경 내용을 처리하며, LabelProperty에 대한 백업 필드를 저장하기 위해 private 멤버를 추가합니다. 헤더 파일에서 참조되는 XAML 클래스는 UWP UI 프레임워크에서 사용하는 Windows.UI.Xaml 네임스페이스 대신 WinUI 3 프레임워크에 속하는 Microsoft.UI.Xaml 네임스페이스에 있습니다.

다음으로, BgLabelControl.cpp 내용을 다음 코드로 바꿉니다.

// BgLabelControl.cpp
#include "pch.h"
#include "BgLabelControl.h"
#if __has_include("BgLabelControl.g.cpp")
#include "BgLabelControl.g.cpp"
#endif

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

    void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::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 콜백을 사용하지 않지만, 속성 변경 콜백을 사용하여 종속성 속성을 등록하는 방법을 보여주기 위해 제공됩니다. OnLabelChanged 구현은 기본 프로젝션된 형식에서 파생된 프로젝션된 형식을 가져오는 방법도 보여 줍니다(이 경우 기본 프로젝션된 형식은 DependencyObject). 그런 다음 프로젝션된 형식을 구현하는 형식에 대한 포인터를 가져오는 방법을 보여 줍니다. 두 번째 작업은 프로젝션된 형식(즉, 런타임 클래스를 구현하는 프로젝트)을 구현하는 프로젝트에서만 자연스럽게 가능합니다.

xaml_typename 함수는 WinUI 3 프로젝트 템플릿에 기본적으로 포함되지 않은 Windows.UI.Xaml.Interop 네임스페이스에서 제공됩니다. 프로젝트의 pch.h미리 컴파일된 헤더 파일에 줄을 추가하여 이 네임스페이스와 연결된 헤더 파일을 포함합니다.

// pch.h
...
#include <winrt/Windows.UI.Xaml.Interop.h>
...

BgLabelControl의 기본 스타일 정의

해당 생성자에서 BgLabelControl 은 자체에 대한 기본 스타일 키를 설정합니다. 템플릿 컨트롤에는 기본 컨트롤 템플릿이 포함된 기본 스타일이 있어야 합니다. 이 스타일은 컨트롤 소비자가 스타일 및/또는 템플릿을 설정하지 않는 경우 자체 렌더링하는 데 사용할 수 있습니다. 이 섹션에서는 기본 스타일을 포함하는 태그 파일을 프로젝트에 추가합니다.

모든 파일 표시솔루션 탐색기에서 계속 활성화되어 있는지 확인합니다. 프로젝트 노드에서 새 폴더(필터가 아니라 폴더)를 만들고 이름을 "테마"로 지정합니다. Themes아래에 Visual C++ > WinUI > 리소스 사전(WinUI)형식의 새 항목을 추가하고, 이름을 "Generic.xaml"로 지정합니다. XAML 프레임워크가 템플릿 컨트롤의 기본 스타일을 찾으려면 폴더 및 파일 이름이 다음과 같아야 합니다. Generic.xaml의 기본 내용을 삭제하고 아래의 마크업을 붙여넣으세요.

<!-- \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>

이 경우 기본 스타일에서 설정하는 유일한 속성은 컨트롤 템플릿입니다. 템플릿은 정사각형(배경이 XAML Control 형식의 모든 인스턴스에 있는 Background 속성에 바인딩됨) 및 텍스트 요소(텍스트가 BgLabelControl::Label 종속성 속성에 바인딩됨)로 구성됩니다.

기본 UI 페이지에 BgLabelControl 인스턴스 추가

MainWindow.xaml을 열어 기본 UI 페이지에 대한 XAML 마크업을 확인하세요. 버튼 요소(StackPanel내부) 바로 다음에 다음 마크업을 추가하세요.

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

또한 MainWindow.h 형식(XAML 태그 및 명령적 코드 컴파일의 조합)이 BgLabelControl 템플릿 컨트롤 형식을 인식하도록 다음 include 지시문을 추가합니다. 다른 XAML 페이지에서 BgLabelControl 을 사용하려는 경우 해당 페이지의 헤더 파일에도 동일한 include 지시문을 추가합니다. 또는 미리 컴파일된 헤더 파일에 단일 포함 지시문을 추가하세요.

//MainWindow.h
...
#include "BgLabelControl.h"
...

이제 프로젝트를 빌드하고 실행합니다. 기본 컨트롤 템플릿이 마크업의 BgLabelControl 인스턴스에서 배경 브러시 및 레이블에 바인딩되는 것을 확인할 수 있습니다.

템플릿 컨트롤 결과

MeasureOverrideOnApplyTemplate 같은 재정의 가능한 함수 구현하기

기본 런타임 클래스에서 추가로 파생되는 Control 런타임 클래스에서 템플릿 기반 컨트롤을 파생합니다. 또한, 파생 클래스에서 재정의할 수 있는 메서드는 Control, FrameworkElementUIElement입니다. 이 작업을 수행하는 방법을 보여 주는 코드 예제는 다음과 같습니다.

// Control overrides.
void OnPointerPressed(Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& /* e */) const { ... };

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

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

재정의 가능한 함수는 서로 다른 언어 프로젝션에서 다르게 표시됩니다. 예를 들어 C#에서 재정의 가능한 함수는 일반적으로 보호된 가상 함수로 표시됩니다. C++/WinRT에서는 가상도 아니고 보호도 되지 않지만, 위에서 설명한 대로 여전히 재정의하여 고유한 구현을 제공할 수 있습니다.

템플릿을 사용하지 않고 컨트롤 소스 파일 생성

이 섹션에서는 사용자 지정 컨트롤 항목 템플릿을 사용하지 않고 사용자 지정 컨트롤을 만드는 데 필요한 소스 파일을 생성하는 방법을 보여 줍니다.

먼저 프로젝트에 새 Midl 파일(.idl) 항목을 추가합니다. 프로젝트 메뉴에서 새 항목 추가를 선택하고 검색 상자에 "MIDL"을 입력하여 .idl 파일 항목을 찾습니다. 이름이 이 문서의 단계와 일치하도록 새 파일을 BgLabelControl.idl로 지정하세요. 기본 내용을 BgLabelControl.idl삭제하고 위의 단계에 표시된 런타임 클래스 선언에 붙여넣습니다.

새 .idl 파일을 저장한 후 다음 단계는 템플릿 기반 컨트롤을 구현하는 데 사용할 .cpp 및 .h 구현 파일에 대한 Windows 런타임 메타데이터 파일(.winmd) 및 스텁을 생성하는 것입니다. 솔루션을 빌드하여 이러한 파일을 생성합니다. 그러면 MIDL 컴파일러(midl.exe)가 만든 .idl 파일을 컴파일합니다. 솔루션이 성공적으로 빌드되지 않고 Visual Studio의 출력 창에 빌드 오류가 표시되지만, 필요한 파일은 생성됩니다.

BgLabelControl.h와 BgLabelControl.cpp 스텁 파일을 \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\에서 프로젝트 폴더로 복사합니다. 솔루션 탐색기에서 모든 파일 표시가 켜져 있는지 확인합니다. 복사한 스텁 파일을 마우스 오른쪽 단추로 클릭하고 프로젝트포함을 클릭합니다.

컴파일러는 생성된 파일이 컴파일되지 않도록 BgLabelControl.h와 BgLabelControl.cpp의 맨 위에 각각 static_assert 줄을 배치합니다. 컨트롤을 구현할 때 프로젝트 디렉터리에 배치한 파일에서 이러한 줄을 제거해야 합니다. 이 연습에서는 위에 제공된 코드로 파일의 전체 내용을 덮어쓸 수 있습니다.

참고하십시오