1

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

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

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

然而,到目前为止,我一直无法预测正确的签名,而且我已经没有山羊来获取必要的内脏了。

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

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

  • 指针已退出
  • 指针释放
  • 指针按下

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>

链接错误fatal error LNK1120: 1 unresolved externals具有以下描述:

未解析的外部符号“公共:__thiscall winrt::Windows::UI::Xaml::Input::PointerEventHandler::PointerEventHandler(struct winrt::BlankAppTouch1::implementation::MainPage ,void (__thiscall winrt::BlankAppTouch1::implementation: :主页::)(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) 在函数“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)

如果我PointerExited="PointerExitedHandler"从 XAML 中删除描述Rectangle.

具有蓝色矩形且 XAML 中没有 PointerExited 的工作应用程序的屏幕截图

附录 A:更改和错误

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

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 结构

PointerEventHandlerfrom的定义Windows.UI.Input.2.h是:

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 {}
};

文档链接

UIElement.PointerExited 事件

如何使用 cppwinrt 为 OnPointerEntered 声明处理程序?

4

1 回答 1

1

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

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

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

正如@IInspectable 在他的评论中提到的,他引用了这篇文章C++/WinRT 入门(请参阅开始A C++/WinRT 快速入门部分中标记为重要的彩色框中的注释):

每当您想要使用 Windows 命名空间中的类型时,请包括相应的 C++/WinRT Windows 命名空间头文件,如图所示。相应的标头是与类型的名称空间同名的标头。

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

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

pch.hC++/WinRT 项目模板生成的文件示例如下:

//
// 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 的预编译头功能的一部分,以减少重新编译时间,只要此文件未更改。该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 遵循的一般规则是包含文件名的使用遵循namespaceC++/WinRT 及其模板的各个部分的指令和修饰符的命名。

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

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

我可以将必要的指令添加到 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用于保留这些事件处理程序的先前处理程序,并在MainPage类中定义为:

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

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

touchRectangle().PointerReleased(myTokenTouchRectangleReleased);

然后,如果我单击矩形旁边的按钮,我将看到鼠标单击蓝色矩形的行为发生了变化。在单击按钮之前,按下鼠标左键时矩形会变大,touchRectangle().PointerPressed()事件处理程序修改矩形的大小,然后松开鼠标左键时再缩小,touchRectangle().PointerReleased()事件处理程序修改矩形的大小.

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

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

于 2018-07-04T13:32:15.680 回答