使用 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)”项目。 代码 myButton() 调用生成的访问器函数,该函数将返回名为 myButton 的按钮。 如果更改该按钮元素的 x:Name,则生成的访问器函数的名称也会更改。
在这种情况下,事件源(引发事件的对象)是名为 myButton 的按钮。 事件接收者(处理事件的对象)是 MainPage 的实例。
后面将详细信息介绍如何管理事件源和事件收件人的生存期。
可以强制注册用于处理事件的成员函数,而不在标记中以声明方式注册。 从下面的代码示例来看可能不明显,但 ButtonBase::Click 调用的参数是 RoutedEventHandler 委托的实例 。 在本例中,我们使用了采用对象和指向成员函数的指针的 RoutedEventHandler 构造函数重载 。
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click({ this, &MainPage::ClickHandler });
}
注册委托时,上述代码示例传递原始的 this 指针(指向当前对象) 。下面是一个使用静态成员函数的示例;请注意,语法更简单。
// 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 。 下面是摘自 RoutedEventHandler 的文档主题的语法块(从网页右上角“语言”下拉菜单中选择 C++/WinRT) 。 请注意各种构造函数:一种采用 lambda;另一种是自由函数;还有一种(我们在上面使用的)采用对象和指向成员函数的指针。
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 事件为例。 访问该主题,并从“语言”下拉列表中选择 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 的类型形参)作为实参 。
如果你不想在事件处理程序中执行很多工作,则可以使用 lambda 函数而不是成员函数。 重复一下,从下面的代码示例来看可能不明显,但一个 RoutedEventHandler 委托正在从 lambda 函数构造,该委托同样需要与之前讨论的函数调用运算符的语法匹配 。
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);
}