이 항목에서는 C++/WinRT사용하여 이벤트 처리 대리자를 등록하고 해지하는 방법을 보여 줍니다. 표준 C++ 함수와 유사한 개체를 사용하여 이벤트를 처리할 수 있습니다.
비고
C++/WinRT VSIX(Visual Studio 확장) 및 NuGet 패키지(프로젝트 템플릿 및 빌드 지원을 함께 제공)를 설치하고 사용하는 방법에 대한 자세한 내용은 C++/WinRT대한
Visual Studio를 사용하여 이벤트 처리기 추가
프로젝트에 이벤트 처리기를 추가하는 편리한 방법은 Visual Studio에서 XAML 디자이너 UI(사용자 인터페이스)를 사용하는 것입니다. XAML 디자이너에서 XAML 페이지가 열려 있는 상태에서 처리하려는 이벤트가 있는 컨트롤을 선택합니다. 해당 컨트롤의 속성 페이지에서 번개 모양 아이콘을 클릭하여 해당 컨트롤에서 제공하는 모든 이벤트를 나열합니다. 그런 다음 처리하려는 이벤트를 두 번 클릭합니다. 예를 들어 OnClicked.
XAML 디자이너는 소스 파일에 적절한 이벤트 처리기 함수 프로토타입(및 스텁 구현)을 추가하여 사용자 고유의 구현으로 바꿀 준비가 되어 있습니다.
비고
일반적으로 이벤트 처리기는 Midl 파일(.idl
)에서 설명할 필요가 없습니다. 따라서 XAML 디자이너는 Midl 파일에 이벤트 처리기 함수 프로토타입을 추가하지 않습니다.
.h
.cpp
파일만 추가합니다.
이벤트를 처리할 대리자 등록
간단한 예는 단추의 클릭 이벤트를 처리하는 것입니다. XAML 태그를 사용하여 다음과 같이 이벤트를 처리하는 멤버 함수를 등록하는 것이 일반적입니다.
// MainPage.xaml
<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
// MainPage.h
void ClickHandler(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
// MainPage.cpp
void MainPage::ClickHandler(
IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
}
위의 코드는 Visual Studio의 비어 있는 앱(C++/WinRT) 프로젝트에서 가져왔습니다. 코드 x:Name
요소의 을 변경하면, 생성된 접근자 함수의 이름도 바뀝니다.
비고
이 경우 이벤트 원본(이벤트를 발생시키는 개체)은 Button이며, 이름은 myButton입니다. 이벤트 수신자(이벤트를 처리하는 개체)는 MainPage인스턴스입니다. 이벤트 원본 및 이벤트 수신자의 수명 관리에 대한 자세한 내용은 이 항목의 뒷부분에서 확인할 수 있습니다.
마크업에서 선언적으로 수행하는 대신, 이벤트를 처리하기 위해 멤버 함수를 명령형으로 등록할 수 있습니다. 아래 코드 예제에서는 명확하지 않을 수 있지만 ButtonBase::Click 호출에 대한 인수는 RoutedEventHandler 대리자의 인스턴스입니다. 이 경우 개체 및 포인터-멤버-함수를 사용하는 RoutedEventHandler 생성자 오버로드를 사용합니다.
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click({ this, &MainPage::ClickHandler });
}
중요합니다
대표자를 등록할 때, 위의 코드 예제에서는 이 원시 포인터(현재 개체를 가리키는)를 넘겨줍니다. 현재 개체에 대한 강력하거나 약한 참조를 설정하는 방법을 알아보려면멤버 함수를 대리자로 사용할 때
다음은 정적 멤버 함수를 사용하는 예제입니다. 더 간단한 구문을 확인합니다.
// MainPage.h
static void ClickHandler(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click( MainPage::ClickHandler );
}
void MainPage::ClickHandler(
IInspectable const& /* sender */,
RoutedEventArgs const& /* args */) { ... }
RoutedEventHandler를 생성하는 다른 방법이 있습니다. 다음은
struct RoutedEventHandler : winrt::Windows::Foundation::IUnknown
{
RoutedEventHandler(std::nullptr_t = nullptr) noexcept;
template <typename L> RoutedEventHandler(L lambda);
template <typename F> RoutedEventHandler(F* function);
template <typename O, typename M> RoutedEventHandler(O* object, M method);
/* ... other constructors ... */
void operator()(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& e) const;
};
함수 호출 연산자의 구문도 확인할 수 있습니다. 대리자의 매개 변수가 무엇인지 알려줍니다. 여기서 볼 수 있듯이 이 경우 함수 호출 연산자 구문은 MainPage::ClickHandler매개 변수와 일치합니다.
비고
지정된 이벤트의 경우 대리자의 세부 정보와 해당 대리자의 매개 변수를 파악하려면 먼저 이벤트 자체에 대한 설명서 항목으로 이동합니다. UIElement.KeyDown 이벤트 예로 살펴보겠습니다. 해당 주제를 방문한 후, Language 드롭다운에서 C++/WinRT를 선택하세요. 항목의 시작 부분에 있는 구문 블록에 이 항목이 표시됩니다.
// Register
event_token KeyDown(KeyEventHandler const& handler) const;
이 정보는 UIElement.KeyDown 이벤트(우리가 다루고 있는 주제)에 KeyEventHandler대리자 형식이 있다는 것을 알려줍니다. 이것은 대리자를 이 이벤트 유형에 등록할 때 사용하는 형식이기 때문입니다. 이제 항목의 링크를 클릭하여 해당 KeyEventHandler 대리자 유형으로 이동합니다. 여기서 구문 블록에는 함수 호출 연산자가 포함됩니다. 그리고 위에서 설명한 것처럼 대리자의 매개 변수가 무엇인지 알려줍니다.
void operator()(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
볼 수 있듯이 IInspectable 보낸 사람으로 사용하고 KeyRoutedEventArgs 클래스의 인스턴스를 인수로 대리자를 선언해야 합니다.
또 다른 예로, Popup.Closed 이벤트을 살펴보겠습니다. 대리자 형식은 EventHandler<IInspectable>입니다. 따라서 대리인은 IInspectable을 발신자로 사용하고, 다른 IInspectable을 인수로 사용합니다. 이는 EventHandler의 형식 매개 변수이기 때문입니다.
이벤트 처리기에서 많은 작업을 수행하지 않는 경우 멤버 함수 대신 람다 함수를 사용할 수 있습니다. 다시 말하지만, 아래 코드 예제에서는 명확하지 않을 수 있지만 위에서 설명한 함수 호출 연산자의 구문과 일치해야 하는 람다 함수에서 RoutedEventHandler 대리자가 생성되고 있습니다.
MainPage::MainPage()
{
InitializeComponent();
myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
});
}
대리자를 생성할 때 좀 더 명시적으로 선택할 수 있습니다. 예를 들어 전달하거나 두 번 이상 사용하려는 경우입니다.
MainPage::MainPage()
{
InitializeComponent();
auto click_handler = [](IInspectable const& sender, RoutedEventArgs const& /* args */)
{
sender.as<winrt::Windows::UI::Xaml::Controls::Button>().Content(box_value(L"Clicked"));
};
myButton().Click(click_handler);
AnotherButton().Click(click_handler);
}
등록된 대리자 해지
대리자를 등록하면 일반적으로 토큰이 반환됩니다. 이후에 해당 토큰을 사용하여 대리자를 해지할 수 있습니다. 즉, 대리자가 이벤트에서 등록 취소되고 이벤트가 다시 발생하면 호출되지 않습니다.
간단히 하기 위해 위의 코드 예제 중 어느 것도 그렇게 하는 방법을 보여주지 않았습니다. 그러나 다음 코드 예제에서는 구조체의 프라이빗 데이터 멤버에 토큰을 저장하고 소멸자에서 해당 처리기를 해지합니다.
struct Example : ExampleT<Example>
{
Example(winrt::Windows::UI::Xaml::Controls::Button const& button) : m_button(button)
{
m_token = m_button.Click([this](IInspectable const&, RoutedEventArgs const&)
{
// ...
});
}
~Example()
{
m_button.Click(m_token);
}
private:
winrt::Windows::UI::Xaml::Controls::Button m_button;
winrt::event_token m_token;
};
위의 예제와 같이 강력한 참조 대신 단추에 대한 약한 참조를 저장할 수 있습니다(C++/WinRT
비고
이벤트 원본이 이벤트를 동기적으로 발생시키는 경우, 처리기를 해제함으로써 더 이상 이벤트를 수신하지 않게 된다는 것을 확신할 수 있습니다. 그러나 비동기 이벤트의 경우, 해지 후에도(특히 소멸자 내에서 해지할 때) 진행 중인 이벤트가 소멸이 시작된 후에도 개체에 도달할 수 있습니다. 파기 전에 구독을 취소할 장소를 찾으면 문제가 완화될 수 있습니다. 또는 강력한 솔루션의 경우 이벤트 처리 대리자사용하여
또는 대리자를 등록할 때 winrt::auto_revoke(winrt::auto_revoke_t형식의 값)
struct Example : ExampleT<Example>
{
Example(winrt::Windows::UI::Xaml::Controls::Button button)
{
m_event_revoker = button.Click(
winrt::auto_revoke,
[this](IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
// ...
});
}
private:
winrt::Windows::UI::Xaml::Controls::Button::Click_revoker m_event_revoker;
};
다음은 ButtonBase::Click 이벤트에 대한 설명서 항목에서 가져온 구문 블록입니다. 세 가지 다른 등록 및 해지 함수를 보여 줍니다. 세 번째 오버로드에서 선언해야 하는 이벤트 해지자의 유형을 정확히 확인할 수 있습니다. 또한 event_revoker 오버로드를 사용하여
// Register
winrt::event_token Click(winrt::Windows::UI::Xaml::RoutedEventHandler const& handler) const;
// Revoke with event_token
void Click(winrt::event_token const& token) const;
// Revoke with event_revoker
Button::Click_revoker Click(winrt::auto_revoke_t,
winrt::Windows::UI::Xaml::RoutedEventHandler const& handler) const;
비고
위의 코드 예제에서 Button::Click_revoker
winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>
대한 형식 별칭입니다. 모든 C++/WinRT 이벤트에도 비슷한 패턴이 적용됩니다. 각 Windows 런타임 이벤트에는 이벤트 해지자를 반환하는 revoke 함수 오버로드가 있으며 해당 해지자의 형식은 이벤트 원본의 멤버입니다. 따라서 또 다른 예를 들어 CoreWindow::SizeChanged 이벤트에는 CoreWindow::SizeChanged_revoker형식의 값을 반환하는 등록 함수 오버로드가 있습니다.
페이지 탐색 시나리오에서 처리기를 해지하는 것을 고려할 수 있습니다. 페이지를 반복적으로 들어갔다 나오는 경우, 페이지를 떠날 때 처리기를 해제할 수 있습니다. 또는 동일한 페이지 인스턴스를 다시 사용하는 경우 토큰의 값을 확인하고 아직 설정되지 않은 경우에만 등록합니다(if (!m_token){ ... }
). 세 번째 옵션은 페이지에 이벤트 해지자를 데이터 멤버로 저장하는 것입니다. 이 항목의 뒷부분에서 설명한 대로 네 번째 옵션은 람다 함수에서 이 개체의
자동 해지 대리자가 등록에 실패하는 경우
대리자를 등록할 때 winrt::auto_revoke를 지정하려고 하고 그 결과가 winrt::hresult_no_interface 예외가 발생한다면, 이는 보통 이벤트 원본이 약한 참조를 지원하지 않는다는 것을 의미합니다. 예를 들어 Windows.UI.Composition 네임스페이스의 일반적인 상황입니다. 이 경우 자동 해지 기능을 사용할 수 없습니다. 수동으로 이벤트 처리기를 해지하는 방식으로 돌아가야 합니다.
비동기 작업 및 작업에 대한 대리자 형식
위의 예제에서는 RoutedEventHandler 대리자 형식을 사용하지만 다른 대리자 형식도 많이 있습니다. 예를 들어, 비동기 작업 및 운영(진행 여부에 관계없이)은 해당 유형의 대리자를 필요로 하는 완료 및/또는 진행 이벤트를 포함합니다. 비동기 작업에서 진행 상태를 나타내는 진행 이벤트(예: IAsyncOperationWithProgress구현 모든 항목)에는 AsyncOperationProgressHandler형식의 대리자가 필요합니다. 다음은 람다 함수를 사용하여 해당 형식의 대리자를 작성하는 코드 예제입니다. 또한 이 예제에서는 AsyncOperationWithProgressCompletedHandler 대리자를 작성하는 방법을 보여줍니다.
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void ProcessFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
auto async_op_with_progress = syndicationClient.RetrieveFeedAsync(rssFeedUri);
async_op_with_progress.Progress(
[](
IAsyncOperationWithProgress<SyndicationFeed,
RetrievalProgress> const& /* sender */,
RetrievalProgress const& args)
{
uint32_t bytes_retrieved = args.BytesRetrieved;
// use bytes_retrieved;
});
async_op_with_progress.Completed(
[](
IAsyncOperationWithProgress<SyndicationFeed,
RetrievalProgress> const& sender,
AsyncStatus const /* asyncStatus */)
{
SyndicationFeed syndicationFeed = sender.GetResults();
// use syndicationFeed;
});
// or (but this function must then be a coroutine, and return IAsyncAction)
// SyndicationFeed syndicationFeed{ co_await async_op_with_progress };
}
위의 "코루틴" 주석에서 알 수 있듯이, 비동기 작업의 완료된 이벤트와 함께 대리자를 사용하는 대신, 코루틴을 사용하는 것이 더 자연스러울 것입니다. 자세한 내용과 코드 예제는 C++/WinRT의 동시성 및 비동기 작업을 참조하세요.
비고
비동기 작업 또는 작업에 대해 둘 이상의 완료 처리기 구현하는 것은 올바르지 않습니다. 완료된 이벤트에 대해 단일 대리자를 지정하거나 co_await
할 수 있습니다. 둘 다 있는 경우 두 번째가 실패합니다.
코루틴 대신 대리자를 사용하는 경우 더 간단한 구문을 선택할 수 있습니다.
async_op_with_progress.Completed(
[](auto&& /*sender*/, AsyncStatus const /* args */)
{
// ...
});
값을 반환하는 대리자 형식
일부 대리자 형식은 스스로 값을 반환해야 합니다. 예를 들어 ListViewItemToKeyHandler은 문자열을 반환합니다. 다음은 해당 형식의 대리자를 작성하는 예제입니다(람다 함수는 값을 반환합니다).
using namespace winrt::Windows::UI::Xaml::Controls;
winrt::hstring f(ListView listview)
{
return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
{
return L"key for item goes here";
});
}
이벤트 처리 대리자를 사용하여 이 포인터를 안전하게 에 액세스합니다.
개체의 멤버 함수 또는 개체의 멤버 함수 내의 람다 함수 내에서 이벤트를 처리하는 경우 이벤트 수신자(이벤트를 처리하는 개체) 및 이벤트 원본(이벤트를 발생시키는 개체)의 상대 수명에 대해 고려해야 합니다. 자세한 정보와 코드 예제를 보시려면 C++/WinRT의 강력한 참조 및 약한 참조 섹션()과을 참조하세요.