qtqmlmousearea

QML MouseArea: onExited doesn't trigger after programmatically moving mouse into MouseArea


This issue happens on Windows, but not on Linux. I haven't tried any other platforms.

I have a custom class (code below) that uses QCursor to set the mouse position.

The issue is with the following code (repo):

import QtQuick 2.15
import QtQuick.Window 2.15

// Custom C++ class, implementation below
import io.github.myProject.utilities.mousehelper 1.0

Window {
    visible: true
    width: 800
    height: 600

    MouseHelper { id: mouseHelper }

    MouseArea {
        id: mouseArea
        hoverEnabled: true
        anchors.fill: parent
        property var p

        onPressed: {
            p = mouseArea.mapToGlobal(
                mouseArea.width * 0.5, mouseArea.height * 0.5);
            mouseHelper.setCursorPosition(0, 0);
        }

        onReleased: {
            mouseHelper.setCursorPosition(p.x, p.y);
        }

        onExited: {
            console.log('This should happen twice, but it only happens once.');
        }
    }
}

Steps to reproduce the issue:

  1. Mouse down on the window. The cursor will move to the top-left of the screen, and onExited will fire.
  2. Release the mouse button. The cursor will jump to the middle of the window.
  3. Move the mouse out of the window.

onExited should fire a second time when the user moves the mouse out of the window, but it doesn't. Is there some way I can either

  1. cause it to fire, or
  2. otherwise detect that the mouse has moved out of the mouse area?

onPositionChanged still fires, but I can only use this to detect when the mouse is close to the edge of the MouseArea, not when it has left.

I tried overlaying a global MouseArea on top and passing all events through as a way to do some manual special-case position checking, but I couldn't pass hover events through.


The class for setting the mouse position:

#ifndef MOUSEHELPER_H
#define MOUSEHELPER_H

#include <QObject>
#include <QCursor>

class MouseHelper : public QObject {
    Q_OBJECT
public:
    explicit MouseHelper(QObject *parent = nullptr);

    Q_INVOKABLE void setCursorPosition(int x, int y);

signals:

public slots:
};

#endif // MOUSEHELPER_H
#include "mousehelper.h"
#include <QGuiApplication>

MouseHelper::MouseHelper(QObject *parent) : QObject(parent) {}

void MouseHelper::setCursorPosition(int x, int y) {
    QCursor::setPos(x, y);
}

I register this class as a type with QML in my main function:

int main(int argc, char *argv[]) {
    // ...
    qmlRegisterType<MouseHelper>("io.github.myProject.utilities.mousehelper",
                                 1, 0, "MouseHelper");
}

I can then import it in QML and use it.


Solution

  • As a workaround for your problem you can use a Timer to reset the position of the mouse cursor.

    Either in QML:

    MouseArea {
    ...
        Timer {
            id: timer
            interval: 10
            repeat: false
            onTriggered: {
                mouseHelper.setCursorPosition(mouseArea.p.x, mouseArea.p.y)
            }
        }
        
        onReleased: {
            timer.start()
        }
    ...
    }
    

    Or in your MouseHelper class:

    #include <QTimer>
    ...
    void MouseHelper::setCursorPosition(int x, int y) {
        QTimer::singleShot(10, this, [x, y]() { QCursor::setPos(x, y); });
    }
    

    This works for me if the interval of the timer is not too small.