неразрешенный внешний для обработчика события указателя мыши выход для прямоугольника для XAML с C ++ / WinRT - PullRequest
0 голосов
/ 03 июля 2018

Я работаю над простым приложением C ++ / WinRT UWP, чтобы узнать, как связать события XAML и исходный код C ++ / WinRT для фактических обработчиков этих событий.

У меня есть источник XAML для простой сетки, которая содержит синий прямоугольник. Рядом с синим прямоугольником находится кнопка, которую можно щелкнуть из предыдущей работы, чтобы вызвать обработчик при нажатии кнопки. Это работало нормально.

Теперь я хочу обработать события указателя мыши. Однако сборка исходного кода C ++ / WinRT вызывает ошибку связи. Я предполагаю, что это означает, что обработчик в исходном коде не имеет правильного интерфейса.

Однако я до сих пор не могу угадать правильную подпись, и у меня заканчиваются козы для необходимых недр.

Может кто-нибудь сказать мне, каким должен быть обработчик события указателя?

Я хотел бы иметь необходимый интерфейс для следующих событий указателя, которые я сейчас изучаю:

  • PointerExited
  • PointerReleased
  • PointerPressed

Файл 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 со следующим описанием:

неразрешенный внешний символ "public: __thiscall" WinRT :: Окна :: UI :: XAML :: Input :: PointerEventHandler :: PointerEventHandler (структура winrt :: BlankAppTouch1 :: реализация :: MainPage , void (__thiscall WinRT :: BlankAppTouch1 :: реализация :: MainPage :: ) (структура winrt :: Windows :: Foundation :: IInspectable const &, struct winrt :: Windows :: Пользовательский интерфейс :: Xaml :: RoutedEventArgs const &)) " (?? $? 0UMainPage @ реализация @ BlankAppTouch1 @ WinRT @@ P80123 @ AEXABUIInspectable @ Фонд @ Windows, @ 3 @ ABURoutedEventArgs @ Xaml @ UI @ 63 @@ Z @ PointerEventHandler @ Input @ Xaml @ UI @ Windows, @ WinRT @@ QAE @ PAUMainPage @ реализация @ BlankAppTouch1 @ 5 @ P86785 @ AEXABUIInspectable @ Foundation @ 45 @ ABURoutedEventArgs @ 2345 @@ Z @ Z) ссылка в функции "public: void __thiscall WinRT :: BlankAppTouch1 :: реализация :: MainPageT :: Connect (интермедиат, структура winrt :: Windows :: Foundation :: IInspectable const &) " (? Connect @? $ MainPageT @ UMainPage @ реализация @ BlankAppTouch1 @ WinRT @@ $$ V @ реализация @ BlankAppTouch1 @ WinRT @@ QAEXHABUIInspectable @ Foundation @ Windows, @ 4 @@ Z) * ​​1036 *

Если я удаляю предложение PointerExited="PointerExitedHandler" из XAML, описывающего Rectangle, оно прекрасно компилируется и при запуске отображает то, что ожидается, как на следующем снимке экрана.

screen shot of working app with blue rectangle and without the PointerExited in the XAML

Приложение 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

Определение для PointerEventHandler из 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

Как объявить обработчик для OnPointerEntered с помощью cppwinrt?

1 Ответ

0 голосов
/ 04 июля 2018

Ошибка компоновщика указывает на отсутствие внешнего winrt::Windows::UI::Xaml::Input::PointerEventHandler::PointerEventHandler().

Ключом к определению причины является то, что C ++ / WinRT основан на шаблонах, которые находятся в наборе включаемых файлов, которые должны быть включены в исходный код приложения. После компиляции необходимые функции будут создаваться компилятором по мере необходимости, если указанный шаблон доступен.

Если шаблон недоступен, поскольку файл, в котором он определен, не является частью компилируемого исходного файла, то вы увидите ошибку компоновщика неразрешенного внешнего. Компилятор не генерирует необходимое внешнее, потому что шаблон для этого недоступен.

Как @IInspectable упоминает в своем комментарии, ссылающемся на эту статью Начало работы с C ++ / WinRT (см. Примечание в цветной рамке, помеченное как Важное в начале раздела Быстрый запуск C ++ / WinRT ):

Всякий раз, когда вы хотите использовать тип из пространств имен Windows, включите соответствующий заголовочный файл пространства имен C ++ / WinRT Windows, как показано. соответствующий заголовок тот же, что и у типа Пространство имен.

Это требование для включения соответствующего заголовочного файла относится как к исходному коду C ++ / WinRT, так и к исходному коду XAML. В этом случае исходный код XAML содержал PointerExited="PointerExitedHandler", который требовал функция winrt::Windows::UI::Xaml::Input::PointerEventHandler::PointerEventHandler(), но поскольку заголовочный файл, содержащий этот шаблон, не был частью источника C ++ / WinRT за файлом XAML, внешний файл не был сгенерирован компилятором и поэтому был недоступен для компоновщика. Результатом стала ошибка компоновщика fatal error LNK1120: 1 unresolved externals.

В Visual Studio 2017 с использованием шаблонов проекта C ++ / WinRT в сгенерированных файлах, предоставленных шаблонами проекта, находится файл pch.h, который содержит список включаемых файлов для проекта.

Пример файла pch.h, созданного шаблоном проекта C ++ / 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"

Этот файл используется как часть функциональности Precompiled Headers в 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, заключается в том, что использование имен включаемых файлов следует за именами директив и модификаторов namespace для различных частей C ++ / WinRT и его шаблонов.

Это означает, что если вы каким-то образом используете тип, такой как Windows::UI::Xaml::Input::PointerEventHandler::PointerEventHandler(), то вам потребуется директива include, чтобы включить файл с именем, аналогичным используемому пространству имен, например winrt/Windows.UI.Xaml.Input.h, чтобы необходимые определения и декларации и шаблоны.

Теперь я могу использовать два способа установки обработчиков указателя мыши для Rectangle, определенного в исходном файле XAML.

Я могу добавить необходимые директивы к источнику 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>
}

где две переменные myTokenTouchRectangle и myTokenButton используются для хранения предыдущих обработчиков для этих обработчиков событий и определены в классе 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() сработает, чтобы уменьшить размер прямоугольника.

...