중요합니다
Windows 앱 SDK로 개발하고 계신가요? 이 문서의 코드는 UWP(Windows.UI.Xaml) 네임스페이스를 사용합니다. 프로젝트가 WinUI 3(Windows 앱 SDK)을 대상으로 하는 경우 전체에서 대체 Microsoft.UI.Xaml (및 관련 Microsoft.UI.* 네임스페이스)를 사용합니다. 자세한 내용은 전체 매핑 및 UI 마이그레이션 가이드를 보려면 UWP API를 Windows 앱 SDK 매핑을 참조하세요.
이 항목에서는 C++/WinRT를 사용하여 이벤트 처리 대리자를 등록하고 취소하는 방법을 보여 줍니다. 표준 C++ 함수와 유사한 개체를 사용하여 이벤트를 처리할 수 있습니다.
메모
C++/WinRT Visual Studio 확장(VSIX) 및 NuGet 패키지(프로젝트 템플릿 및 빌드 지원을 함께 제공)를 설치하고 사용하는 방법에 대한 자세한 내용은 C++/WinRT에 대한 Visual Studio 지원을 참조하세요.
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::Microsoft::UI::Xaml::RoutedEventArgs const& args);
// MainPage.cpp
void MainPage::ClickHandler(
IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
}
위 코드는 Visual Studio의 빈 앱, 패키지됨(데스크톱용 WinUI 3) 프로젝트에서 가져온 것입니다. 이 코드 myButton() 는 myButton이라는 Button을 반환하는 생성된 접근자 함수를 호출합니다. 해당 x:Name 요소의 이름을 변경 하면 생성된 접근자 함수의 이름도 변경됩니다.
메모
이 경우 이벤트 원본(이벤트를 발생시키는 개체)은 myButton이라는 Button입니다. 이벤트 수신자(이벤트를 처리하는 개체)는 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::Microsoft::UI::Xaml::RoutedEventArgs const& args);
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click( MainPage::ClickHandler );
}
void MainPage::ClickHandler(
IInspectable const& /* sender */,
RoutedEventArgs const& /* args */) { ... }
RoutedEventHandler를 생성하는 다른 방법이 있습니다. 다음은 RoutedEventHandler에 대한 설명서 항목에서 가져온 구문 블록입니다(웹 페이지의 오른쪽 위 모서리에 있는 언어 드롭다운에서 C++/WinRT 선택). 다양한 생성자에 주목하세요. 하나는 람다를 받고, 다른 하나는 자유 함수를 받으며, 또 다른 하나(위에서 사용한 생성자)는 객체와 멤버 함수 포인터를 받습니다.
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::Microsoft::UI::Xaml::RoutedEventArgs const& e) const;
};
함수 호출 연산자의 구문도 확인할 수 있습니다. 대리자의 매개 변수가 무엇인지 알려줍니다. 이 경우 함수 호출 연산자 구문은 MainPage::ClickHandler의 매개 변수와 일치합니다.
메모
지정된 이벤트의 경우 대리자의 세부 정보와 해당 대리자의 매개 변수를 파악하려면 먼저 이벤트 자체에 대한 설명서 항목으로 이동합니다. UIElement.KeyDown 이벤트를 예로 들어 보겠습니다. 해당 항목을 방문하여 언어 드롭다운에서 C++/WinRT를 선택합니다. 항목의 시작 부분에 있는 구문 블록에 이 항목이 표시됩니다.
// Register
event_token KeyDown(KeyEventHandler const& handler) const;
이 정보는 이 이벤트 형식으로 대리자를 등록할 때 전달하는 형식이므로 UIElement.KeyDown 이벤트(현재 항목)에 KeyEventHandler의 대리자 형식이 있음을 알려줍니다. 이제 항목의 링크를 클릭하여 해당 KeyEventHandler 대리자 형식으로 이동합니다. 여기서 구문 블록에는 함수 호출 연산자가 포함됩니다. 그리고 위에서 설명한 것처럼 대리자의 매개 변수가 무엇인지 알려줍니다.
void operator()(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
볼 수 있듯이 IInspectable 을 보낸 사람으로, KeyRoutedEventArgs 클래스 의 인스턴스를 인수로 사용하도록 대리자를 선언해야 합니다.
또 다른 예제를 보려면 Popup.Closed 이벤트를 살펴보겠습니다. 대리자 유형은 EventHandler<IInspectable입니다>. 따라서 대리자는 sender로서 IInspectable을 받고, args로는 또 다른 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::Microsoft::UI::Xaml::Controls::Button>().Content(box_value(L"Clicked"));
};
myButton().Click(click_handler);
AnotherButton().Click(click_handler);
}
등록된 대리자 해지
대리자를 등록하면 일반적으로 토큰이 반환됩니다. 이후에 해당 토큰을 사용하여 대리자를 해지할 수 있습니다. 즉, 대리자가 이벤트에서 등록 취소되고 이벤트가 다시 발생하면 호출되지 않습니다.
간단히 하기 위해 위의 코드 예제 중 어느 것도 그렇게 하는 방법을 보여주지 않았습니다. 그러나 다음 코드 예제에서는 구조체의 프라이빗 데이터 멤버에 토큰을 저장하고 소멸자에서 해당 처리기를 해지합니다.
struct Example : ExampleT<Example>
{
Example(winrt::Microsoft::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::Microsoft::UI::Xaml::Controls::Button m_button;
winrt::event_token m_token;
};
위의 예제와 같이 강력한 참조 대신 단추에 대한 약한 참조를 저장할 수 있습니다( C++/WinRT의 강력한 참조 및 약한 참조 참조).
메모
이벤트 원본이 이벤트를 동기적으로 발생시키는 경우 처리기를 취소하고 더 이상 이벤트를 수신하지 않을 것이라고 확신할 수 있습니다. 그러나 비동기 이벤트의 경우에는 등록을 해제한 후에도(특히 소멸자 내에서 등록을 해제할 때) 전달 중인 이벤트가 객체의 소멸이 시작된 뒤에도 해당 객체에 도달할 수 있습니다. 파기 전에 구독을 해제할 위치를 찾으면 문제를 완화할 수 있습니다. 또는 보다 확실한 해결 방법은 이벤트 처리 대리자를 사용하여 this 포인터에 안전하게 액세스하기를 참조하세요.
또는 대리자를 등록할 때 winrt::auto_revoke ( winrt::auto_revoke_t 형식의 값)를 지정하여 이벤트 해지자( winrt::event_revoker 형식)를 요청할 수 있습니다. 이벤트 취소자는 사용자를 대신해 이벤트 소스(이벤트를 발생시키는 개체)에 대한 약한 참조를 유지합니다. event_revoker::revoke 멤버 함수를 호출하여 수동으로 해지할 수 있습니다. 그러나 이벤트 해지자는 범위를 벗어나면 해당 함수 자체를 자동으로 호출합니다. revoke 함수는 이벤트 원본이 여전히 존재하는지 확인하고, 있는 경우 대리자를 해지합니다. 이 예제에서는 이벤트 원본을 저장할 필요가 없으며 소멸자가 필요하지 않습니다.
struct Example : ExampleT<Example>
{
Example(winrt::Microsoft::UI::Xaml::Controls::Button button)
{
m_event_revoker = button.Click(
winrt::auto_revoke,
[this](IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
// ...
});
}
private:
winrt::Microsoft::UI::Xaml::Controls::Button::Click_revoker m_event_revoker;
};
다음은 ButtonBase::Click 이벤트에 대한 설명서 항목에서 가져온 구문 블록입니다. 세 가지 다른 등록 및 해지 함수를 보여 줍니다. 세 번째 오버로드에서 선언해야 하는 이벤트 해지자의 유형을 정확히 확인할 수 있습니다. 또한 register 및 revoke with event_revoker 오버로드 모두에 동일한 종류의 대리자를 전달할 수 있습니다.
// Register
winrt::event_token Click(winrt::Microsoft::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::Microsoft::UI::Xaml::RoutedEventHandler const& handler) const;
메모
위의 코드 예제에서 Button::Click_revoker는 winrt::event_revoker<winrt::Microsoft::UI::Xaml::Controls::Primitives::IButtonBase>의 형식 별칭입니다. 모든 C++/WinRT 이벤트에도 비슷한 패턴이 적용됩니다. 각 Windows 런타임 이벤트에는 이벤트 해지자를 반환하는 revoke 함수 오버로드가 있으며 해당 해지자의 형식은 이벤트 원본의 멤버입니다. 따라서 다른 예를 들어 Window::SizeChanged 이벤트에는 Window::SizeChanged_revoker 형식의 값을 반환하는 등록 함수 오버로드가 있습니다.
페이지 탐색 시나리오에서 처리기를 해지하는 것이 좋습니다. 페이지로 반복적으로 이동한 다음 다시 나가는 경우 페이지에서 벗어날 때 처리기를 취소할 수 있습니다. 또는 동일한 페이지 인스턴스를 다시 사용하는 경우 토큰 값을 확인하고 아직 설정되지 않은 경우에만 등록합니다(if (!m_token){ ... }). 세 번째 옵션은 페이지에 이벤트 해지자를 데이터 멤버로 저장하는 것입니다. 이 항목의 뒷부분에서 설명한 대로 네 번째 옵션은 람다 함수에서 이 개체에 대한 강력한 참조 또는 약한 참조를 캡처하는 것입니다.
자동 해지 대리자가 등록에 실패하는 경우
대리자를 등록할 때 winrt::auto_revoke 지정하려고 하고 결과가 winrt::hresult_no_interface 예외인 경우 일반적으로 이벤트 원본이 약한 참조를 지원하지 않음을 의미합니다. 예를 들어, Microsoft.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 };
}
위의 'coroutine' 주석이 시사하듯이, 비동기 동작 및 작업의 완료 이벤트에서 델리게이트를 사용하는 대신 코루틴을 사용하는 편이 아마 더 자연스럽게 느껴질 것입니다. 자세한 내용 및 코드 예제는 C++/WinRT를 사용한 동시성 및 비동기 작업을 참조하세요.
메모
비동기 작업 또는 작업에 대해 둘 이상의 완료 처리기를 구현하는 것은 올바르지 않습니다. 완료 이벤트에 대해 하나의 대리자를 둘 수도 있고, 아니면 여기에 co_await할 수도 있습니다. 둘 다 있는 경우 두 번째가 실패합니다.
코루틴 대신 대리자를 사용하는 경우 더 간단한 구문을 선택할 수 있습니다.
async_op_with_progress.Completed(
[](auto&& /*sender*/, AsyncStatus const /* args */)
{
// ...
});
값을 반환하는 대리자 형식
일부 대리자 형식은 스스로 값을 반환해야 합니다. 예를 들어 문자열을 반환하는 ListViewItemToKeyHandler가 있습니다. 다음은 해당 형식의 대리자를 작성하는 예제입니다(람다 함수는 값을 반환합니다).
using namespace winrt::Microsoft::UI::Xaml::Controls;
winrt::hstring f(ListView listview)
{
return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
{
return L"key for item goes here";
});
}
이벤트 처리 델리게이트를 사용하여 this 포인터에 안전하게 접근하기
개체의 멤버 함수 또는 개체의 멤버 함수 내의 람다 함수 내에서 이벤트를 처리하는 경우 이벤트 수신자(이벤트를 처리하는 개체) 및 이벤트 원본(이벤트를 발생시키는 개체)의 상대 수명에 대해 고려해야 합니다. 자세한 정보 및 코드 예제는 C++/WinRT의 강력한 참조 및 약한 참조를 참조하세요.
중요 API
관련 항목
Windows developer