Richard Chambers
Richard Chambers

Reputation: 17623

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

I am working on a simple C++/WinRT UWP application to learn about how to link XAML events and the C++/WinRT source code for the actual handlers for these events.

I have a XAML source for a simple grid that contains a blue rectangle. Beside the blue rectangle is a button to click from earlier work to trigger a handler when the button is clicked. That worked fine.

Now I want to handle mouse pointer events. However the C++/WinRT source code build is generating a link error. I assume this means that the handler in the source code does not have the correct interface.

However I have been unable to divine thus far the correct signature and I am running out of goats for the necessary entrails.

Can someone tell me what the event handler for a pointer event should be?

I would like to have the necessary interface for the following pointer events that I am currently exploring:

The MainPage.h file contains the following declarations:

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

and the MainPage.cpp file contains the following source:

#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);
        }
    }
}

And the MainPage.xaml file contains the following source:

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

The link error is fatal error LNK1120: 1 unresolved externals with the following description:

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)

If I remove the PointerExited="PointerExitedHandler" clause from the XAML describing the Rectangle, it compiles fine and when run displays what is expected as in the following screenshot.

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

Addendum A: Changes and errors

After removing the clause from the XAML I then tried to put the handler for the pointer exit handler into the rectangle object itself. Recompiling, I get what appears to be the same unresolved external symbol link error.

MainPage::MainPage()
{
    InitializeComponent();

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

If I modify the name of the handler specified in the PointerExited() method by adding the letter x to the name of the handler function (as in touchRectangle().PointerExited({ this, &MainPage::PointerExitedHandlerx });), I see compiler errors indicating the identifier PointerExitedHandlerx does not exist, as expected.

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  

Addendum B: PointerEventHandler struct

The definition for a PointerEventHandler from Windows.UI.Input.2.h is:

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

and PointerRoutedEventArgs is defined as:

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

Documentation links

UIElement.PointerExited Event

How to declare handler for OnPointerEntered using cppwinrt?

Upvotes: 3

Views: 981

Answers (1)

Richard Chambers
Richard Chambers

Reputation: 17623

The linker error indicates a missing external winrt::Windows::UI::Xaml::Input::PointerEventHandler::PointerEventHandler().

The key to determining the cause is that C++/WinRT is based on templates which are in a set of include files that must be included into the application source. When compiled, the necessary functions will be created by the compiler as needed so long as the specified template is available.

If the template is not available, because the file in which it is defined is not part of the file source being compiled, then you will see a linker error of unresolved external. The compiler does not generate the needed external because the template to do so is not available.

As @IInspectable mentions in his comment referencing this article Get started with C++/WinRT (see the note in the colored box marked Important in the section beginning 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.

This requirement for the appropriate header file to be included applies to both C++/WinRT source code as well as to the XAML source code. In this case the XAML source code contained the PointerExited="PointerExitedHandler" which required the function winrt::Windows::UI::Xaml::Input::PointerEventHandler::PointerEventHandler() but because the header file containing that template was not part of the C++/WinRT source behind the XAML file, the external was not generated by the compiler and therefor was unavailable to the linker. The result was the linker error of fatal error LNK1120: 1 unresolved externals.

With Visual Studio 2017 using the C++/WinRT project templates, in the generated files provided by the project templates is a file pch.h which contains a list of the include files for the project.

An example of a pch.h file generated by the C++/WinRT project template is:

//
// 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"

This file is used as part of the Precompiled Headers functionality of Visual Studio 2017 to reduce recompile times so long as this file is not changed. The pch.h file is normally included in each of the .cpp files in order to provide the necessary compile environment needed for the source files of the application.

However this file is not necessarily a complete list of all needed include files for a particular application. And if the application is using C++/WinRT functionality or XAML functionality that is not defined in any of the include files listed, the compiler will generate an error when it compiles the C++ and the XAML source.

For this particular application, there are two additional include files required so the pch.h file need to be modified so that it has include directives for these additional files.

The pch.h file need to be as follows. Notice the two lines with ADD_TO in comments.

//
// 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"

It would appear that a general rule that is followed with C++/WinRT is that the use of include file names follows the naming of the namespace directives and modifiers for the various parts of C++/WinRT and its templates.

This implies that if you are using a type such as Windows::UI::Xaml::Input::PointerEventHandler::PointerEventHandler() in some way then you will need to have an include directive to include a file named similar to the namespace used such as winrt/Windows.UI.Xaml.Input.h in order to have the necessary defines and declarations and templates.

I can now use two ways to set my mouse pointer handlers for the Rectangle defined in the XAML source file.

I can add the necessary directives to the XAML source as in:

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

Or I can specify the handlers in my C++ source file.

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://learn.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>
}

where the two variables myTokenTouchRectangle and myTokenButton are used to keep the previous handlers for these event handlers and are defined in the MainPage class as:

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

The reason for these variables is to capture the previous event handler to have in case we want to undo our event handling logic. For instance if I modify the ClickHandler() for the button mouse click to include the source line:

touchRectangle().PointerReleased(myTokenTouchRectangleReleased);

Then I will see a change in the behavior of mouse clicks on the blue rectangle should I click the button beside the rectangle. Before clicking the button, the rectangle will grow larger when the left mouse button is pressed, the touchRectangle().PointerPressed() event handler modifies the size of the rectangle, and then reduce back down when the left mouse button is released, the touchRectangle().PointerReleased() event handler modifies the size of the rectangle.

If I click the button beside the rectangle, which causes the mouse pointer released event handler to be set back to the initial state, then when I press the left mouse button the rectangle grows larger and when I release the left mouse button there is no change in the size.

If I move the mouse cursor outside of the rectangle, the touchRectangle().PointerExited() event handler is triggered to reduce the size of the rectangle.

Upvotes: 3

Related Questions