使用 C++/WinRT 的 XAML 矩形的鼠标指针事件退出处理程序的未解析外部

unresolved external for mouse pointer event exit handler for a Rectangle for XAML with C++/WinRT

我正在开发一个简单的 C++/WinRT UWP 应用程序,以了解如何 link XAML 事件和这些事件的实际处理程序的 C++/WinRT 源代码。

我有一个包含蓝色矩形的简单网格的 XAML 源。蓝色矩形旁边是一个按钮,可以在单击按钮时从早期工作中单击以触发处理程序。效果很好。

现在我想处理鼠标指针事件。但是,C++/WinRT 源代码生成生成 link 错误。我认为这意味着源代码中的处理程序没有正确的接口。

然而,到目前为止,我无法预测正确的签名,我正在 运行寻找必要的内脏。

谁能告诉我指针事件的事件处理程序应该是什么?

我想为我目前正在探索的以下指针事件提供必要的接口:

MainPage.h 文件包含以下声明:

namespace winrt::BlankAppTouch1::implementation
{
    struct MainPage : MainPageT<MainPage>
    {
        MainPage();

        int32_t MyProperty();
        void MyProperty(int32_t value);

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

        // Handler for pointer exited event.
        void PointerExitedHandler(Windows::Foundation::IInspectable const & sender, Windows::UI::Xaml::RoutedEventArgs const& token);

        // Handler for pointer released event.
        void touchRectangle_PointerReleased(Windows::Foundation::IInspectable const & sender, Windows::UI::Xaml::RoutedEventArgs const& e);

            // Handler for pointer pressed event.
        void touchRectangle_PointerPressed(Windows::Foundation::IInspectable const & sender, Windows::UI::Xaml::RoutedEventArgs const& e);
    };
}

并且 MainPage.cpp 文件包含以下来源:

#include "pch.h"
#include "MainPage.h"

using namespace winrt;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Shapes;

namespace winrt::BlankAppTouch1::implementation
{
    MainPage::MainPage()
    {
        InitializeComponent();

    }

    int32_t MainPage::MyProperty()
    {
        throw hresult_not_implemented();
    }

    void MainPage::MyProperty(int32_t /* value */)
    {
        throw hresult_not_implemented();
    }

    void MainPage::ClickHandler(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&)
    {
        myButton().Content(box_value(L"Clicked"));
    }

    // Handler for pointer exited event.
    void MainPage::PointerExitedHandler(Windows::Foundation::IInspectable const & sender, Windows::UI::Xaml::RoutedEventArgs const& token)
    {
        Rectangle rect = winrt::unbox_value<Rectangle>(sender);

        // Pointer moved outside Rectangle hit test area.
        // Reset the dimensions of the Rectangle.
        if (nullptr != rect)
        {
            rect.Width(200);
            rect.Height(100);
        }
    }

    // Handler for pointer released event.
    void MainPage::touchRectangle_PointerReleased(Windows::Foundation::IInspectable const & sender, Windows::UI::Xaml::RoutedEventArgs const& e)
    {
        Rectangle rect = winrt::unbox_value<Rectangle>(sender);

        // Reset the dimensions of the Rectangle.
        if (nullptr != rect)
        {
            rect.Width(200);
            rect.Height(100);
        }
    }

    // Handler for pointer pressed event.
    void MainPage::touchRectangle_PointerPressed(Windows::Foundation::IInspectable const & sender, Windows::UI::Xaml::RoutedEventArgs const& e)
    {
        Rectangle rect = winrt::unbox_value<Rectangle>(sender);

        // Change the dimensions of the Rectangle.
        if (nullptr != rect)
        {
            rect.Width(250);
            rect.Height(150);
        }
    }
}

并且 MainPage.xaml 文件包含以下来源:

<Page
    x:Class="BlankAppTouch1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BlankAppTouch1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
            <Rectangle x:Name="touchRectangle"
                   Width="200" Height="200" Fill="Blue"
                   PointerExited="PointerExitedHandler"
                   ManipulationMode="All"/>
            <Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
        </StackPanel>
    </Grid>

</Page>

link 错误是 fatal error LNK1120: 1 unresolved externals,描述如下:

unresolved external symbol "public: __thiscall winrt::Windows::UI::Xaml::Input::PointerEventHandler::PointerEventHandler(struct winrt::BlankAppTouch1::implementation::MainPage ,void (__thiscall winrt::BlankAppTouch1::implementation::MainPage::)(struct winrt::Windows::Foundation::IInspectable const &,struct winrt::Windows::UI::Xaml::RoutedEventArgs const &))" (??$?0UMainPage@implementation@BlankAppTouch1@winrt@@P80123@AEXABUIInspectable@Foundation@Windows@3@ABURoutedEventArgs@Xaml@UI@63@@Z@PointerEventHandler@Input@Xaml@UI@Windows@winrt@@QAE@PAUMainPage@implementation@BlankAppTouch1@5@P86785@AEXABUIInspectable@Foundation@45@ABURoutedEventArgs@2345@@Z@Z) referenced in function "public: void __thiscall winrt::BlankAppTouch1::implementation::MainPageT::Connect(int,struct winrt::Windows::Foundation::IInspectable const &)" (?Connect@?$MainPageT@UMainPage@implementation@BlankAppTouch1@winrt@@$$V@implementation@BlankAppTouch1@winrt@@QAEXHABUIInspectable@Foundation@Windows@4@@Z)

如果我从描述 Rectangle 的 XAML 中删除 PointerExited="PointerExitedHandler" 子句,它编译正常,当 运行 显示预期内容时,如以下屏幕截图所示。

附录 A:更改和错误

从 XAML 中删除子句后,我尝试将指针退出处理程序的处理程序放入矩形对象本身。重新编译,我得到了似乎相同的未解析外部符号 link 错误。

MainPage::MainPage()
{
    InitializeComponent();

    touchRectangle().PointerExited({ this, &MainPage::PointerExitedHandler });
}

如果我修改 PointerExited() 方法中指定的处理程序的名称,将字母 x 添加到处理程序函数的名称(如 touchRectangle().PointerExited({ this, &MainPage::PointerExitedHandlerx }); 中),我明白了指示标识符 PointerExitedHandlerx 不存在的编译器错误,如预期的那样。

Severity    Code    Description Project File    Line    Suppression State
Error   C2039   'PointerExitedHandlerx': is not a member of 'winrt::BlankAppTouch1::implementation::MainPage'   BlankAppTouch1  d:\users\rickc\documents\vs2017repos\blankapptouch1\blankapptouch1\mainpage.cpp 15  
Error   C2065   'PointerExitedHandlerx': undeclared identifier  BlankAppTouch1  d:\users\rickc\documents\vs2017repos\blankapptouch1\blankapptouch1\mainpage.cpp 15  

附录 B:PointerEventHandler 结构

来自 Windows.UI.Input.2.hPointerEventHandler 的定义是:

struct PointerEventHandler : Windows::Foundation::IUnknown
{
    PointerEventHandler(std::nullptr_t = nullptr) noexcept {}
    template <typename L> PointerEventHandler(L lambda);
    template <typename F> PointerEventHandler(F* function);
    template <typename O, typename M> PointerEventHandler(O* object, M method);
    void operator()(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e) const;
};

PointerRoutedEventArgs定义为:

#define WINRT_EBO __declspec(empty_bases)

// other source for definitions, etc.

struct WINRT_EBO PointerRoutedEventArgs :
    Windows::UI::Xaml::Input::IPointerRoutedEventArgs,
    impl::base<PointerRoutedEventArgs, Windows::UI::Xaml::RoutedEventArgs>,
    impl::require<PointerRoutedEventArgs, Windows::UI::Xaml::IRoutedEventArgs, Windows::UI::Xaml::Input::IPointerRoutedEventArgs2>
{
    PointerRoutedEventArgs(std::nullptr_t) noexcept {}
};

文档 links

UIElement.PointerExited Event

链接器错误表示缺少外部 winrt::Windows::UI::Xaml::Input::PointerEventHandler::PointerEventHandler()

确定原因的关键是 C++/WinRT 基于模板,这些模板位于必须包含在应用程序源中的一组包含文件中。编译时,只要指定的模板可用,编译器就会根据需要创建必要的函数。

如果模板不可用,因为定义它的文件不是正在编译的文件源的一部分,那么您将看到未解析外部的链接器错误。 编译器没有生成所需的外部,因为这样做的模板不可用。

正如@IInspectable 在引用本文的评论中提到的那样 Get started with C++/WinRT(请参阅开头部分中标记为重要的彩色框中的注释 A C++/WinRT quick-start):

Whenever you want to use a type from a Windows namespaces, include the corresponding C++/WinRT Windows namespace header file, as shown. The corresponding header is the one with the same name as the type's namespace.

对要包含的适当 header 文件的要求适用于 C++/WinRT 源代码以及 XAML 源代码。在这种情况下,XAML 源代码包含需要函数 winrt::Windows::UI::Xaml::Input::PointerEventHandler::PointerEventHandler()PointerExited="PointerExitedHandler",但因为包含该模板的 header 文件不是 C++/WinRT 源代码背后的一部分XAML 文件,外部文件不是由编译器生成的,因此链接器无法使用。结果是 fatal error LNK1120: 1 unresolved externals.

的链接器错误

Visual Studio 2017 使用 C++/WinRT 项目模板,在项目模板提供的生成文件中有一个文件 pch.h,其中包含项目的包含文件列表。

C++/WinRT 项目模板生成的 pch.h 文件示例为:

//
// pch.h
// Header for platform projection include files
//

#pragma once

#define NOMINMAX

#include "winrt/Windows.Foundation.h"
#include "winrt/Windows.ApplicationModel.Activation.h"
#include "winrt/Windows.UI.Xaml.h"
#include "winrt/Windows.UI.Xaml.Controls.h"
#include "winrt/Windows.UI.Xaml.Controls.Primitives.h"
#include "winrt/Windows.UI.Xaml.Data.h"
#include "winrt/Windows.UI.Xaml.Interop.h"
#include "winrt/Windows.UI.Xaml.Markup.h"
#include "winrt/Windows.UI.Xaml.Navigation.h"

此文件用作 Visual Studio 2017 的预编译 Headers 功能的一部分,只要不更改此文件即可减少重新编译时间。 pch.h 文件通常包含在每个 .cpp 文件中,以便为应用程序的源文件提供必要的编译环境。

但是,此文件不一定是特定应用程序所需的所有包含文件的完整列表。 如果应用程序使用 C++/WinRT 功能或 XAML未在列出的任何包含文件中定义的功能,编译器在编译 C++ 和 XAML 源代码时将生成错误。

对于这个特定的应用程序,需要两个额外的包含文件,因此需要修改 pch.h 文件,以便它具有针对这些额外文件的 include 指令。

pch.h文件需要如下。注意注释中带有 ADD_TO 的两行。

//
// pch.h
// Header for platform projection include files
//

#pragma once

#define NOMINMAX

#include "winrt/Windows.Foundation.h"
#include "winrt/Windows.ApplicationModel.Activation.h"
#include "winrt/Windows.UI.Xaml.h"
#include "winrt/Windows.UI.Xaml.Input.h"                    // ADD_TO:  need to add to allow use of mouse pointer handlers in XAML file MainPage.xaml
#include "winrt/Windows.UI.Xaml.Controls.h"
#include "winrt/Windows.UI.Xaml.Shapes.h"                   // ADD_TO:  need to add to allow use of Rectangle in XAML file MainPage.xaml
#include "winrt/Windows.UI.Xaml.Controls.Primitives.h"
#include "winrt/Windows.UI.Xaml.Data.h"
#include "winrt/Windows.UI.Xaml.Interop.h"
#include "winrt/Windows.UI.Xaml.Markup.h"
#include "winrt/Windows.UI.Xaml.Navigation.h"

似乎 C++/WinRT 遵循的一般规则是包含文件名的使用遵循 C++/WinRT 及其各个部分的 namespace 指令和修饰符的命名模板。

这意味着如果您以某种方式使用诸如 Windows::UI::Xaml::Input::PointerEventHandler::PointerEventHandler() 的类型,那么您将需要一个 include 指令来包含一个名称类似于所用命名空间的文件,例如 winrt/Windows.UI.Xaml.Input.h 以获得必要的定义、声明和模板。

我现在可以使用两种方法为 XAML 源文件中定义的 Rectangle 设置我的鼠标指针处理程序。

我可以将必要的指令添加到 XAML 源代码中,如:

<Page
    x:Class="BlankAppTouch1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BlankAppTouch1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
            <Rectangle x:Name="touchRectangle"
                   Width="200" Height="200" Fill="Blue"
                   PointerExited="PointerExitedHandler" PointerReleased="touchRectangle_PointerReleased" PointerPressed="touchRectangle_PointerPressed"
                   ManipulationMode="All"/>
            <Button x:Name="myButton" >Click Me</Button>
        </StackPanel>
    </Grid>

</Page>

或者我可以在我的 C++ 源文件中指定处理程序。

MainPage::MainPage()
{
    InitializeComponent();

    // can either attach mouse pointer event handlers to a Rectangle by doing the following
    myTokenTouchRectangleExited = touchRectangle().PointerExited({ this, &MainPage::PointerExitedHandler });
    myTokenTouchRectangleReleased = touchRectangle().PointerReleased({ this, &MainPage::touchRectangle_PointerReleased });
    myTokenTouchRectanglePressed = touchRectangle().PointerPressed({ this, &MainPage::touchRectangle_PointerPressed });
    // or you can add to the XAML file PointerExited="PointerExitedHandler" PointerReleased="touchRectangle_PointerReleased" PointerPressed="touchRectangle_PointerPressed"

    // https://docs.microsoft.com/en-us/windows/uwp/xaml-platform/events-and-routed-events-overview
    // can either attach an event handler to a button by doing the following
    myTokenButton = myButton().Click({ this, &MainPage::ClickHandler });
    // or you can add to the XAML file Click="ClickHandler" in the XAML source defining the button, <Button x:Name="myButton" >Click Me</Button>
}

其中myTokenTouchRectanglemyTokenButton这两个变量用来为这些事件处理程序保留之前的处理程序,在MainPageclass中定义为:

winrt::event_token myTokenTouchRectangleExited;
winrt::event_token myTokenTouchRectangleReleased;
winrt::event_token myTokenTouchRectanglePressed;
winrt::event_token myTokenButton;

这些变量的原因是为了捕获以前的事件处理程序,以防我们想要撤消我们的事件处理逻辑。例如,如果我修改按钮鼠标单击的 ClickHandler() 以包含源代码行:

touchRectangle().PointerReleased(myTokenTouchRectangleReleased);

如果我单击矩形旁边的按钮,我将看到鼠标在蓝色矩形上单击的行为发生变化。在点击按钮之前,当鼠标左键被按下时,矩形会变大,touchRectangle().PointerPressed()事件处理程序修改矩形的大小,当鼠标左键被释放时,矩形会变小,[=38] =] 事件处理程序修改矩形的大小。

如果我点击矩形旁边的按钮,导致鼠标指针释放事件处理程序被设置回初始状态,那么当我按下鼠标左键时矩形变大,当我释放鼠标左键时按钮大小没有变化。

如果我将鼠标光标移动到矩形之外,touchRectangle().PointerExited() 事件处理程序将被触发以减小矩形的大小。