QML: Steal events from dynamic MouseArea

I am currently trying to implement a drag and drop mechanism in QML, but I ran into a problem where I needed a newly created MouseArea

one to become a mouse event object even though the original MouseArea

did not have a mouse release event yet.

Window {
    id: window
    width: 300
    height: 300

    Rectangle {
        id: base
        width: 20
        height: 20

        color: "red"

        MouseArea {
            anchors.fill: parent

            property var lastPoint
            property var draggedObj: null

            function vecLength( vec ) {
                return Math.abs( Math.sqrt( Math.pow( vec.x, 2 ) +
                                            Math.pow( vec.y, 2 ) ) );
            }

            onPressed: lastPoint = Qt.point( mouse.x, mouse.y )
            onPositionChanged: {
                if ( !draggedObj ) {
                    var diff = Qt.point( mouse.x - lastPoint.x,
                                         mouse.y - lastPoint.y );
                    if ( vecLength( diff ) > 4 ) {
                        draggedObj = dragObj.createObject( window );
                    }
                }

                mouse.accepted = !draggedObj;
            }
        }
    }

    Component {
        id: dragObj

        Rectangle {
            width: 20
            height: 20

            color: "blue"

            Drag.active: dragArea.drag.active
            Drag.hotSpot.x: 10
            Drag.hotSpot.y: 10

            MouseArea {
                id: dragArea
                anchors.fill: parent

                drag.target: parent
            }
        }
    }
}

      

If you run this code and try it, you will see that dragging in red Rectangle

causes a draggable blue to be created Rectangle

, but it will not follow the mouse because red MouseArea

is still receiving mouse events even though the blue MouseArea

is over it.

Is there a way to make blue MouseArea

receive mouse events?

+3


source to share


2 answers


I have experienced this before and had a solution start in the attic.

The trick here is triggering QQuickItem::grabMouse()

and dispatching a mouse click event on the newly created object. Unfortunately I believe it can only be done from C ++.

Then I created a helper class to show this qml functionality:

MouseGrabber.h

#ifndef MOUSEGRABBER
#define MOUSEGRABBER

#include <QObject>
#include <QQuickItem>
#include <QGuiApplication>
#include <QMouseEvent>

class MouseGrabber : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QQuickItem* target READ target WRITE setTarget NOTIFY targetChanged)
    Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)

public:
    explicit MouseGrabber(QObject *parent = 0) : QObject(parent), m_target(nullptr), m_active(true) {  }
    QQuickItem* target() const { return m_target; }
    bool active() const { return m_active;}


signals:
    void targetChanged();
    void activeChanged();

public slots:
    void setTarget(QQuickItem* target)
    {
        if (m_target == target)
            return;
        ungrabMouse(m_target);
        if (m_active)
            grabMouse(target);
        m_target = target;
        emit targetChanged();
    }
    void setActive(bool arg)
    {
        if (m_active == arg)
            return;
        m_active = arg;

        if (m_active)
            grabMouse(m_target);
        else
            ungrabMouse(m_target);

        emit activeChanged();
    }

private:
    static void grabMouse(QQuickItem* target)
    {
        if (target)
        {
            target->grabMouse();
            QMouseEvent event(QEvent::MouseButtonPress, QPointF(), Qt::LeftButton,  QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers());
            QGuiApplication::sendEvent(target, &event);
        }
    }

    static void ungrabMouse(QQuickItem* target)
    {
        if (target)
            target->ungrabMouse();
    }

    QQuickItem* m_target;
    bool m_active;
};

#endif // MOUSEGRABBER

      

This could have been made more convenient by directly invoking slots instead of manipulating propriety, but that's what I had in stock. For example, a slot named grabMouseUntilRelease(QQuickItem* item)

that grabs the mouse for that item listens for the mouse release event with installEventFilter

and automatically unpacks it.




Register the class so that it can be instantiated in QML from qmlRegisterType

somewhere in your code:

qmlRegisterType<MouseGrabber>("com.mycompany.qmlcomponents", 1, 0, "MouseGrabber");

      




Then you can create a copy of MouseGrabber in QML and use it by changing its properties ( target

and active

):

QML

import com.mycompany.qmlcomponents 1.0

Window {
    id: window
    width: 300
    height: 300

    Rectangle {
        id: base
        width: 20
        height: 20

        color: "red"

        MouseArea {
            anchors.fill: parent

            property var lastPoint
            property var draggedObj: null

            function vecLength( vec ) {
                return Math.abs( Math.sqrt( Math.pow( vec.x, 2 ) +
                                           Math.pow( vec.y, 2 ) ) );
            }

            onPressed: lastPoint = Qt.point( mouse.x, mouse.y )
            onPositionChanged: {
                if ( !draggedObj ) {
                    var diff = Qt.point( mouse.x - lastPoint.x,
                                        mouse.y - lastPoint.y );
                    if ( vecLength( diff ) > 4 ) {
                        draggedObj = dragObj.createObject( window );
                        grabber.target = draggedObj.dragArea; // grab the mouse
                    }
                }

                mouse.accepted = !draggedObj;
            }
        }
    }
    MouseGrabber {
        id: grabber
    }

    Component {
        id: dragObj

        Rectangle {
            property alias dragArea: dragArea
            width: 20
            height: 20

            color: "blue"

            Drag.active: dragArea.drag.active
            Drag.hotSpot.x: 10
            Drag.hotSpot.y: 10

            MouseArea {
                id: dragArea
                anchors.fill: parent
                drag.target: parent
                onReleased: {
                    if (grabber.target === this)
                        grabber.target = null; // ungrab the mouse
                }
            }
        }
    }
}

      

+5


source


My other answer is too reworked. There is no need to fetch mouse events in your situation, you just want to update the position of the dragged blue rectangle in the onPositionChanged handler (either via Binding

or directly inside the component Rectangle

).

Hiding it in yours is MouseArea

enough:



onPositionChanged: {
    if ( !draggedObj ) {
        var diff = Qt.point( mouse.x - lastPoint.x,
                             mouse.y - lastPoint.y );
        if ( vecLength( diff ) > 4 ) {
            draggedObj = dragObj.createObject( window );
        }
    } else { //update the position of the dragged rectangle
        draggedObj.x = mouse.x - draggedObj.width/2;
        draggedObj.y = mouse.y - draggedObj.height/2;
    }
}

onReleased: draggedObj = null

      

0


source







All Articles