XAML 컨트롤; C++/WinRT 속성에 바인딩

XAML 컨트롤에 효과적으로 바인딩할 수 있는 속성을 관찰 가능한 속성이라고 합니다. 이 아이디어는 관찰자 패턴이라고 하는 소프트웨어 디자인 패턴을 기반으로 합니다. 이 항목에서는 C++/WinRT에서 관찰 가능한 속성을 구현하는 방법과 XAML 컨트롤을 바인딩하는 방법을 보여 줍니다(배경 정보는 데이터 바인딩 참조).

중요합니다

C++/WinRT를 사용하여 런타임 클래스를 사용하고 작성하는 방법에 대한 이해를 지원하는 필수 개념 및 용어는 C++/WinRT 및 C++/WinRT를 사용하여 API 사용API 작성을 참조하세요.

속성에 대해 관찰 가능한 의미는 무엇인가요?

BookSku라는 런타임 클래스에 Title이라는 속성이 있다고 가정해 보겠습니다. BookSku타이틀 값이 변경될 때마다 INotifyPropertyChanged::P ropertyChanged 이벤트를 발생시키는 경우 이는 Title이 관찰 가능한 속성임을 의미합니다. BookSku의 동작(이벤트를 발생 또는 발생시키지 않음)은 해당 속성 중 관찰 가능한 속성을 결정하는 동작입니다.

XAML 텍스트 요소 또는 컨트롤은 이러한 이벤트에 바인딩하고 처리할 수 있습니다. 이러한 요소 또는 컨트롤은 업데이트된 값을 검색한 다음, 새 값을 표시하도록 자신을 업데이트하여 이벤트를 처리합니다.

메모

C++/WinRT Visual Studio 확장(VSIX) 및 NuGet 패키지(프로젝트 템플릿 및 빌드 지원을 함께 제공)를 설치하고 사용하는 방법에 대한 자세한 내용은 C++/WinRT에 대한 Visual Studio 지원을 참조하세요.

빈 앱 만들기(Bookstore)

먼저 Microsoft Visual Studio에서 새 프로젝트를 만듭니다. C++ 프로젝트용 빈 앱, 패키지된(데스크톱용 WinUI 3)을 만들고 이름을 Bookstore로 지정합니다. 동일한 디렉터리에 솔루션 및 프로젝트 배치가 선택 취소되어 있는지 확인합니다. Windows SDK의 최신 일반 공급(즉, 미리 보기 아님) 버전을 대상으로 지정합니다.

관찰 가능한 제목 속성이 있는 책을 나타내는 새 클래스를 작성하겠습니다. 동일한 컴파일 단위 내에서 클래스를 작성하고 사용합니다. 그러나 XAML에서 이 클래스에 바인딩할 수 있기를 원하며, 이러한 이유로 런타임 클래스가 될 것입니다. 또한 C++/WinRT를 사용하여 작성 및 사용하겠습니다.

새 런타임 클래스를 작성하는 첫 번째 단계는 프로젝트에 새 Midl 파일(.idl) 항목을 추가하는 것입니다. 새 항목의 이름을 BookSku.idl로 지정합니다. 의 기본 내용을 BookSku.idl삭제하고 이 런타임 클래스 선언에 붙여넣습니다.

// BookSku.idl
namespace Bookstore
{
    runtimeclass BookSku : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
    {
        BookSku(String title);
        String Title;
    }
}

메모

뷰 모델 클래스(실제로 애플리케이션에서 선언하는 런타임 클래스)는 기본 클래스에서 파생할 필요가 없습니다. 위에 선언된 BookSku 클래스가 그 예입니다. 인터페이스를 구현하지만 기본 클래스에서 파생되지는 않습니다.

애플리케이션에서 선언한 런타임 클래스 중 기본 클래스에서 파생되는 클래스는 구성 가능 클래스라고 합니다. 그리고 구성 가능한 클래스에 대한 제약 조건이 있습니다. 애플리케이션이 Visual Studio 및 Microsoft Store에서 제출의 유효성을 검사하는 데 사용하는 Windows 앱 인증 키트 테스트를 통과하려면(따라서 애플리케이션이 Microsoft Store에 성공적으로 수집되려면) 구성 가능한 클래스는 궁극적으로 Windows 기본 클래스에서 파생되어야 합니다. 즉, 상속 계층의 루트에 있는 클래스는 Windows.* 또는 Microsoft.* 네임스페이스에서 시작되는 형식이어야 합니다. 기본 클래스에서 런타임 클래스를 파생해야 하는 경우(예를 들어, 모든 뷰 모델이 상속하도록 BindableBase 클래스를 구현하는 경우) Microsoft.UI.Xaml.DependencyObject를 상속할 수 있습니다.

뷰 모델은 뷰의 추상화이므로 뷰(XAML 태그)에 직접 바인딩됩니다. 데이터 모델은 데이터의 추상화이며 뷰 모델에서만 사용되며 XAML에 직접 바인딩되지 않습니다. 따라서 데이터 모델을 런타임 클래스가 아니라 C++ 구조체 또는 클래스로 선언할 수 있습니다. MIDL에서 선언할 필요가 없으며 원하는 상속 계층 구조를 자유롭게 사용할 수 있습니다.

파일을 저장하고 프로젝트를 빌드합니다. 빌드는 아직 (전적으로) 성공하지는 못하지만 필요한 몇 가지 작업을 수행할 것입니다. 특히 빌드 프로세스 midl.exe 중에 도구를 실행하여 런타임 클래스(파일이 디스크\Bookstore\Debug\Bookstore\Unmerged\BookSku.winmd에 배치됨)를 설명하는 Windows 런타임 메타데이터 파일을 만듭니다. 그런 다음, cppwinrt.exe 도구를 실행하여 런타임 클래스를 작성하고 사용할 수 있도록 소스 코드 파일을 생성합니다. 이러한 파일에는 IDL에서 선언한 BookSku 런타임 클래스 구현을 시작하기 위한 스텁이 포함됩니다. 잠시 후에 디스크에서 찾을 수 있지만 스텁은 다음과 같습니다 \Bookstore\Bookstore\Generated Files\sources\BookSku.hBookSku.cpp.

이제 Visual Studio 프로젝트 노드를 마우스 오른쪽 단추로 클릭하고 파일 탐색기에서 폴더 열기를 클릭합니다. 파일 탐색기에서 프로젝트 폴더가 열립니다. 이제 폴더의 \Bookstore\Bookstore\ 내용을 확인해야 합니다. 거기에서 \Generated Files\sources\ 폴더로 이동한 다음, 스텁 파일 BookSku.hBookSku.cpp를 클립보드에 복사합니다. 프로젝트 폴더(\Bookstore\Bookstore\)로 다시 이동하여 방금 복사한 두 파일을 붙여 넣습니다. 마지막으로 솔루션 탐색기에서 프로젝트 노드를 선택하고 모든 파일 표시가 켜져 있는지 확인합니다. 복사한 스텁 파일을 마우스 오른쪽 버튼으로 클릭하고 프로젝트에 포함을 클릭합니다.

BookSku 구현

이제 런타임 클래스를 열고 \Bookstore\Bookstore\BookSku.hBookSku.cpp 구현해 보겠습니다. 먼저 BookSku.hBookSku.cpp의 맨 위에 static_assert가 표시되며, 이를 제거해야 합니다.

다음으로, BookSku.h에서 다음과 같이 변경하세요.

  • 기본 생성자에서 = default= delete로 변경합니다. 기본 생성자를 원하지 않기 때문입니다.
  • 제목 문자열을 저장할 프라이빗 멤버를 추가합니다. winrt::hstring 값을 사용하는 생성자가 있습니다. 이 값은 제목 문자열입니다.
  • 타이틀이 변경되면 발생하게 될 이벤트에 대해 다른 프라이빗 멤버를 추가합니다.

이러한 변경을 BookSku.h 수행한 후에는 다음과 같이 표시됩니다.

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

namespace winrt::Bookstore::implementation
{
    struct BookSku : BookSkuT<BookSku>
    {
        BookSku() = delete;
        BookSku(winrt::hstring const& title);

        winrt::hstring Title();
        void Title(winrt::hstring const& value);
        winrt::event_token PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& value);
        void PropertyChanged(winrt::event_token const& token);
    
    private:
        winrt::hstring m_title;
        winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookSku : BookSkuT<BookSku, implementation::BookSku>
    {
    };
}

에서 BookSku.cpp다음과 같은 함수를 구현합니다.

// BookSku.cpp
#include "pch.h"
#include "BookSku.h"
#include "BookSku.g.cpp"

namespace winrt::Bookstore::implementation
{
    BookSku::BookSku(winrt::hstring const& title) : m_title{ title }
    {
    }

    winrt::hstring BookSku::Title()
    {
        return m_title;
    }

    void BookSku::Title(winrt::hstring const& value)
    {
        if (m_title != value)
        {
            m_title = value;
            m_propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Title" });
        }
    }

    winrt::event_token BookSku::PropertyChanged(Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
    {
        return m_propertyChanged.add(handler);
    }

    void BookSku::PropertyChanged(winrt::event_token const& token)
    {
        m_propertyChanged.remove(token);
    }
}

타이틀 변경기 함수에서 현재 값과 다른 값이 설정되고 있는지 확인합니다. 그리고 그렇다면 제목을 업데이트하고, 변경된 속성의 이름을 인수로 하여 INotifyPropertyChanged::PropertyChanged 이벤트도 발생시킵니다. 따라서 UI(사용자 인터페이스)는 다시 쿼리할 속성의 값을 알 수 있습니다.

확인하려는 경우 프로젝트가 지금 다시 빌드됩니다.

BookstoreViewModel 선언 및 구현

기본 XAML 페이지는 기본 보기 모델에 바인딩됩니다. 그리고 해당 뷰 모델에는 BookSku 형식 중 하나를 포함하여 여러 속성이 있습니다. 이 단계에서는 기본 뷰 모델 런타임 클래스를 선언하고 구현합니다.

BookstoreViewModel.idl이라는 이름의 새 Midl 파일(.idl) 항목을 추가합니다. 또한 런타임 클래스를 Midl 파일(.idl)로 팩터링하는 것도 참조하세요.

// BookstoreViewModel.idl
import "BookSku.idl";

namespace Bookstore
{
    runtimeclass BookstoreViewModel
    {
        BookstoreViewModel();
        BookSku BookSku{ get; };
    }
}

저장 및 빌드(빌드가 아직 완전히 성공하지는 않지만 빌드하는 이유는 스텁 파일을 다시 생성하는 것입니다).

Generated Files\sources 폴더에서 BookstoreViewModel.hBookstoreViewModel.cpp을 프로젝트 폴더로 복사하고, 이를 프로젝트에 포함합니다. 해당 파일을 열고(다시 제거 static_assert ) 아래와 같이 런타임 클래스를 구현합니다. BookstoreViewModel.h에서 BookSku.h를 포함하고 있다는 점에 유의하세요. 이 구문은 BookSku의 구현 형식(winrt::Bookstore::implementation::BookSku)을 선언합니다. 기본 생성자에서 = default를 제거합니다.

메모

아래 BookstoreViewModel.hBookstoreViewModel.cpp목록에서 코드는 m_bookSku 데이터 멤버를 생성하는 기본 방법을 보여 줍니다. 이는 C++/WinRT의 첫 번째 릴리스로 거슬러 올라가는 메서드이며, 적어도 패턴에 익숙한 것이 좋습니다. C++/WinRT 버전 2.0 이상에서는 균일한 생성 이라고 하는 최적화된 형태의 생성을 사용할 수 있습니다( C++/WinRT 2.0의 뉴스 및 변경 내용 참조). 이 항목의 후반부에서 균일한 구성의 예를 보여 드리겠습니다.

// BookstoreViewModel.h
#pragma once
#include "BookstoreViewModel.g.h"
#include "BookSku.h"

namespace winrt::Bookstore::implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
    {
        BookstoreViewModel();

        Bookstore::BookSku BookSku();

    private:
        Bookstore::BookSku m_bookSku{ nullptr };
    };
}
namespace winrt::Bookstore::factory_implementation
{
    struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel, implementation::BookstoreViewModel>
    {
    };
}
// BookstoreViewModel.cpp
#include "pch.h"
#include "BookstoreViewModel.h"
#include "BookstoreViewModel.g.cpp"

namespace winrt::Bookstore::implementation
{
    BookstoreViewModel::BookstoreViewModel()
    {
        m_bookSku = winrt::make<Bookstore::implementation::BookSku>(L"Atticus");
    }

    Bookstore::BookSku BookstoreViewModel::BookSku()
    {
        return m_bookSku;
    }
}

메모

형식 m_bookSku 은 프로젝션된 형식(winrt::Bookstore::BookSku)이며 winrt::make 와 함께 사용하는 템플릿 매개 변수는 구현 형식(winrt::Bookstore::implementation::BookSku)입니다. make는 그럼에도 불구하고 투영된 타입의 인스턴스를 반환합니다.

이제 프로젝트가 다시 빌드됩니다.

MainPageBookstoreViewModel 형식의 속성 추가

기본 UI 페이지를 나타내는 런타임 클래스를 선언하는 Open MainPage.idl.

  • BookstoreViewModel.idl를 가져오기 위해 import 지시문을 추가합니다.
  • BookstoreViewModel 형식의 MainViewModel이라는 읽기 전용 속성을 추가합니다.
  • MyProperty 속성을 제거합니다.
// MainPage.idl
import "BookstoreViewModel.idl";

namespace Bookstore
{
    runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

파일을 저장합니다. 프로젝트는 아직 빌드에 완전히 성공하지는 못하지만 MainPage 런타임 클래스가 구현되는\Bookstore\Bookstore\Generated Files\sources\MainPage.hMainPage.cpp소스 코드 파일을 다시 생성하기 때문에 지금 빌드하는 것이 유용합니다. 그럼 지금 바로 만들어 보세요. 이 단계에서 볼 수 있는 빌드 오류는 'MainViewModel'입니다. 'winrt::Bookstore::implementation::MainPage'의 멤버가 아닙니다.

BookstoreViewModel.idl 포함을 생략하면(위의 MainPage.idl 목록 참조) "MainViewModel" 근처에서 <가 필요합니다라는 오류가 표시됩니다. 또 다른 팁은 모든 형식을 코드 목록에 표시된 네임스페이스인 동일한 네임스페이스에 두는 것입니다.

예상되는 오류를 해결하려면 이제 MainViewModel 속성의 접근자 스텁을 생성된 파일(\Bookstore\Bookstore\Generated Files\sources\MainPage.hMainPage.cpp)에서 복사하여 \Bookstore\Bookstore\MainPage.hMainPage.cpp로 옮겨야 합니다. 이 작업을 수행하는 단계는 다음에 설명되어 있습니다.

\Bookstore\Bookstore\MainPage.h에서 다음 단계를 수행합니다.

  • BookstoreViewModel.h(winrt::Bookstore::implementation::BookstoreViewModel)에 대한 구현 유형을 선언하는 Include입니다.
  • 뷰 모델을 저장할 프라이빗 멤버를 추가합니다. 속성 접근자 함수(및 m_mainViewModel 멤버)는 BookstoreViewModel의 투영된 형식(즉, Bookstore::BookstoreViewModel)을 기준으로 구현된다는 점에 유의하세요.
  • 구현 형식은 애플리케이션과 동일한 프로젝트(컴파일 단위)에 있으므로 std::nullptr_t를 받는 생성자 오버로드를 통해 m_mainViewModel을 생성합니다.
  • MyProperty 속성을 제거합니다.

메모

아래 MainPage.hMainPage.cpp목록 쌍에서 코드는 m_mainViewModel 데이터 멤버를 생성하는 기본 방법을 보여 줍니다. 이어지는 섹션에서는 그 대신 균일한 구성을 사용하는 버전을 보여 드리겠습니다.

// MainPage.h
...
#include "BookstoreViewModel.h"
...
namespace winrt::Bookstore::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        Bookstore::BookstoreViewModel MainViewModel();

        void ClickHandler(Windows::Foundation::IInspectable const&, Microsoft::UI::Xaml::RoutedEventArgs const&);

    private:
        Bookstore::BookstoreViewModel m_mainViewModel{ nullptr };
    };
}
...

아래 목록에 표시된 대로 \Bookstore\Bookstore\MainPage.cpp에서 다음과 같이 변경합니다.

  • winrt::make(BookstoreViewModel 구현 형식 포함)를 호출하여 프로젝션된 BookstoreViewModel 유형의 새 인스턴스를 m_mainViewModel 할당합니다. 위에서 본 것처럼 BookstoreViewModel 생성자는 새 BookSku 개체를 개인 데이터 멤버로 만들어 처음에 제목을 .로 L"Atticus"설정합니다.
  • 단추의 이벤트 처리기(ClickHandler)에서 책의 제목을 게시된 제목으로 업데이트합니다.
  • MainViewModel 속성에 대한 접근자를 구현합니다.
  • MyProperty 속성을 제거합니다.
// MainPage.cpp
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"

using namespace winrt;
using namespace Microsoft::UI::Xaml;

namespace winrt::Bookstore::implementation
{
    MainPage::MainPage()
    {
        m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();
        InitializeComponent();
    }

    void MainPage::ClickHandler(Windows::Foundation::IInspectable const& /* sender */, Microsoft::UI::Xaml::RoutedEventArgs const& /* args */)
    {
        MainViewModel().BookSku().Title(L"To Kill a Mockingbird");
    }

    Bookstore::BookstoreViewModel MainPage::MainViewModel()
    {
        return m_mainViewModel;
    }
}

균일한 구조

winrt::makeMainPage.h 대신 균일한 생성을 사용하려면 아래와 같이 한 단계에서만 m_mainViewModel 선언하고 초기화합니다.

// MainPage.h
...
#include "BookstoreViewModel.h"
...
struct MainPage : MainPageT<MainPage>
{
    ...
private:
    Bookstore::BookstoreViewModel m_mainViewModel;
};
...

그리고 나서 MainPage.cppMainPage 생성자에서 코드 m_mainViewModel = winrt::make<Bookstore::implementation::BookstoreViewModel>();는 필요하지 않습니다.

균일한 생성 및 코드 예제에 대한 자세한 내용은 균일한 생성 및 직접 구현 액세스에 대한 옵트인(Opt in)을 참조하세요.

Title 속성에 단추 바인딩

MainPage.xaml을 열어 기본 UI 페이지에 대한 XAML 마크업을 확인하세요. 아래 목록과 같이 단추에서 이름을 제거하고 Content 속성 값을 리터럴에서 바인딩 식으로 변경합니다. 바인딩 식의 Mode=OneWay 속성을 확인합니다(보기 모델에서 UI로 단방향). 해당 속성이 없으면 UI는 속성 변경 이벤트에 응답하지 않습니다.

<Button Click="ClickHandler" Content="{x:Bind MainViewModel.BookSku.Title, Mode=OneWay}"/>

이제 프로젝트를 빌드하고 실행합니다. 단추를 클릭하여 Click 이벤트 처리기를 실행합니다. 이 처리기는 책의 제목 변경기 함수를 호출합니다. 변경자는 타이틀 속성이 변경되었음을 UI에 알리기 위해 이벤트를 발생합니다. 단추는 해당 속성의 값을 다시 쿼리하여 자체 콘텐츠 값을 업데이트합니다.

C++/WinRT에서 {Binding} 태그 확장 사용

현재 릴리스된 C++/WinRT 버전의 경우 {Binding} 태그 확장을 사용하려면 ICustomPropertyProviderICustomProperty 인터페이스를 구현해야 합니다.

요소-요소 바인딩

한 XAML 요소의 속성을 다른 XAML 요소의 속성에 바인딩할 수 있습니다. 다음은 마크업에서 그것이 어떻게 보이는지 보여 주는 예입니다.

<TextBox x:Name="myTextBox" />
<TextBlock Text="{x:Bind myTextBox.Text, Mode=OneWay}" />

Midl 파일(.idl)에서 명명된 XAML 엔터티 myTextBox 를 읽기 전용 속성으로 선언해야 합니다.

// MainPage.idl
runtimeclass MainPage : Microsoft.UI.Xaml.Controls.Page
{
    MainPage();
    Microsoft.UI.Xaml.Controls.TextBox myTextBox{ get; };
}

이러한 필요성에 대한 이유는 다음과 같습니다. XAML 컴파일러가 유효성을 검사해야 하는 모든 형식({x:Bind}에 사용된 형식 포함)은 WinMD(Windows 메타데이터)에서 읽습니다. 읽기 전용 속성을 Midl 파일에 추가하기만 하면 됩니다. 자동 생성된 XAML 코드 비하인드가 해당 구현을 자동으로 제공하므로 직접 구현하지 마세요.

XAML 마크업에서 개체 사용하기

XAML {x:Bind} 마크업 확장을 통해 사용되는 모든 엔터티는 IDL에 공개되어 있어야 합니다. 또한 XAML 마크업에 역시 마크업에 있는 다른 요소에 대한 참조가 포함된 경우 해당 마크업의 getter가 IDL에 있어야 합니다.

<Page x:Name="MyPage">
    <StackPanel>
        <CheckBox x:Name="UseCustomColorCheckBox" Content="Use custom color"
             Click="UseCustomColorCheckBox_Click" />
        <Button x:Name="ChangeColorButton" Content="Change color"
            Click="{x:Bind ChangeColorButton_OnClick}"
            IsEnabled="{x:Bind UseCustomColorCheckBox.IsChecked.Value, Mode=OneWay}"/>
    </StackPanel>
</Page>

ChangeColorButton 요소는 바인딩을 통해 UseCustomColorCheckBox 요소를 참조합니다. 따라서 이 페이지의 IDL은 바인딩에 액세스할 수 있도록 UseCustomColorCheckBox 라는 읽기 전용 속성을 선언해야 합니다.

UseCustomColorCheckBox에 대한 클릭 이벤트 처리기 대리자는 클래식 XAML 대리자 구문을 사용하므로 IDL에 항목이 필요하지 않습니다. 구현 클래스에서 공용이어야 합니다. 반면에 ChangeColorButton에는 {x:Bind} 클릭 이벤트 처리기도 있으며, 이것 역시 IDL에 들어가야 합니다.

runtimeclass MyPage : Microsoft.UI.Xaml.Controls.Page
{
    MyPage();

    // These members are consumed by binding.
    void ChangeColorButton_OnClick();
    Microsoft.UI.Xaml.Controls.CheckBox UseCustomColorCheckBox{ get; };
}

UseCustomColorCheckBox 속성에 대한 구현을 제공할 필요가 없습니다. XAML 코드 생성기는 이 작업을 수행합니다.

부울 값에 바인딩

진단 모드에서 이 작업을 수행할 수 있습니다.

<TextBlock Text="{Binding CanPair}"/>

C++/CX에서는 true 또는 false가 표시되지만, C++/WinRT에서는 Windows.Foundation.IReference`1<Boolean>가 표시됩니다.

대신 불리언에 바인딩할 때는 x:Bind를 사용합니다.

<TextBlock Text="{x:Bind CanPair}"/>

WINDOWS 구현 라이브러리 사용(WIL)

WIL(Windows 구현 라이브러리)은 바인딩 가능한 속성을 쉽게 작성할 수 있는 도우미를 제공합니다. WIL 설명서의 알림 속성을 참조하세요.

중요 API