简体   繁体   中英

How can I propagate hover or mousearea update events to lower elements in QML?

I have some sibling Rectangle elements with a radius property so they appear as circles. Each has a child Item that has a child MouseArea, the purpose of the Item being to implement a "round mouse area" effect ( original SO answer ). The Item and MouseArea are instrumented such that clicks and drags will only take effect within the visible circular shape of the Rectangle, not within the bounding box that is the real footprint of the Rectangle.

Unfortunately there is a glitch illustrated below. The desired outcome when dragging at the dot is for circle 1 to move, and this happens in most circumstances. However, it does not happen when you create create circle 1 then circle 2 then move your mouse cursor to the dot. If you do that and attempt to drag or click, your interaction will fall through to the background full-window MouseArea and create a new circle.

两个圆圈部分重叠,编号为 1 和 2,一个圆圈中的一个点靠近另一个圆圈

The cause of this problem is that when the mouse cursor moves to the dot from circle #2, the mouseX and mouseY for circle #1's MouseArea do not get updated. When circle #2 allows the click to propagate downward, it hits the Rectangle of circle #1 but then circle #1's Item claims containsMouse is false and it propagates downward again.

As soon as the mouse cursor leaves the footprint of circle #2's bounding rectangle, such as by moving a bit up or left from the dot, circle #1's MouseArea gets updated and its containsMouse becomes true and it starts capturing clicks and drags again.

I have tried a handful of potential solutions and not gotten much farther than the code below.

import QtQuick 2.12
import QtQuick.Controls 2.5

ApplicationWindow {
    visible: true
    width: 640
    height: 480

    property real spotlightRadius: 100

    MouseArea {
        visible: true
        anchors.fill: parent
        onClicked: {
            spotlightComponent.createObject(parent, {
                "x": x + mouseX - spotlightRadius,
                "y": y + mouseY - spotlightRadius,
                "width": spotlightRadius * 2,
                "height": spotlightRadius * 2
            })
        }
    }

    Component {
        id: spotlightComponent
        Rectangle {
            id: spotlightCircle
            visible: true
            x: parent.x
            y: parent.y
            width: parent.width
            height: parent.height
            radius: Math.max(parent.width, parent.height) / 2
            color: Qt.rgba(Math.random()*0.5+0.5,Math.random()*0.5+0.5,Math.random()*0.5+0.5,0.5);
            Item {
                anchors.fill: parent
                drag.target: parent
                onDoubleclicked: parent.destroy()
                onWheel: { parent.z += wheel.pixelDelta.y; currentSpotlight = parent }

                property alias drag: mouseArea.drag

                //FIXME when moving the mouse out of a higher element's containsMouse circle
                // but still inside its mouseArea.containsMouse square, lower elements'
                // mouseArea do not update, so their containsMouse doesn't update, so clicks
                // fall through when they should not.
                property bool containsMouse: {
                    var x1 = width / 2;
                    var y1 = height / 2;
                    var x2 = mouseArea.mouseX;
                    var y2 = mouseArea.mouseY;
                    var deltax = x1 - x2;
                    var deltay = y1 - y2;
                    var distance2 = deltax * deltax + deltay * deltay;
                    var radius2 = Math.pow(Math.min(width, height) / 2, 2);
                    return distance2 < radius2;
                }

                signal clicked(var mouse)
                signal doubleclicked(var mouse)
                signal wheel(var wheel)

                MouseArea {
                    id: mouseArea
                    anchors.fill: parent
                    hoverEnabled: true
                    //FIXME without acceptedButtons, propagated un-accepted clicks end up with the wrong coordinates
                    acceptedButtons: parent.containsMouse ? Qt.LeftButton : Qt.NoButton
                    propagateComposedEvents: true
                    onClicked: { if (parent.containsMouse) { parent.clicked(mouse) } else { mouse.accepted = false } }
                    onDoubleClicked: { if (parent.containsMouse) { parent.doubleclicked(mouse) } }
                    onWheel: { if (parent.containsMouse) { parent.wheel(wheel) } }
                    drag.filterChildren: true
                }
            }
        }
    }
}

This is not the exact solution for your problem, but this is how I overcame the root of the issue.

In my application there is a MouseArea that overlaps a large chunk of the scene which is a QQuickFrameBufferObject . This is where I draw the 3D scene. Since you cannot propagate a QHoverEvent in QML, you will have to catch the position changed signal using the onPositionChanged handler and invoke a method in C++ which will send a QHoverEvent to the required items.

QML:

MouseArea {
    onPositionChanged: {
        model.sendHoverEvent(Qt.point(mouse.x, mouse.y))
    }
}

C++:

class TreeViewModel : public QAbstractListModel
{
    // ...
    void TreeViewModel::sendHoverEvent(QPointF p) {
        QHoverEvent hoverEvent(QEvent::HoverMove, p, p);
        QApplication::sendEvent(mApplication.graphicsLayer(), &hoverEvent);
    }
};

You need to reject the pressed event of your underlying MouseArea . It should be enough to solve your problems. If the pressed event is rejected, the click will automatically be forwarded to the underlying sibling items. propagateComposedEvents and filterChildren are useless in your case.

Note that if the wheel event causes the z coordinate of your spotlightCircle to become less than 0, it will no longer accept mouse event since they will be caught by the "Creation" MouseArea

import QtQuick 2.10
import QtQuick.Controls 2.3

ApplicationWindow {
    visible: true
    width: 640
    height: 480

    property real spotlightRadius: 100

    MouseArea {
        visible: true
        anchors.fill: parent
        onClicked: {
            spotlightComponent.createObject(parent, {
                "x": x + mouseX - spotlightRadius,
                "y": y + mouseY - spotlightRadius,
                "width": spotlightRadius * 2,
                "height": spotlightRadius * 2
            })
        }
    }

    Component {
        id: spotlightComponent
        Rectangle {
            id: spotlightCircle
            visible: true
            x: parent.x
            y: parent.y
            width: parent.width
            height: parent.height
            radius: Math.max(parent.width, parent.height) / 2
            color: Qt.rgba(Math.random()*0.5+0.5,Math.random()*0.5+0.5,Math.random()*0.5+0.5,0.5);
            Item {
                anchors.fill: parent
                onDoubleClicked: parent.destroy()
                onWheel: { parent.z += wheel.pixelDelta.y; currentSpotlight = parent }

                signal clicked(var mouse)
                signal pressed(var mouse)
                signal doubleClicked(var mouse)
                signal wheel(var wheel)
                property alias drag: mouseArea.drag
                property bool containsMouse: {
                    var x1 = width / 2;
                    var y1 = height / 2;
                    var x2 = mouseArea.mouseX;
                    var y2 = mouseArea.mouseY;
                    var deltax = x1 - x2;
                    var deltay = y1 - y2;
                    var distance2 = deltax * deltax + deltay * deltay;
                    var radius2 = Math.pow(Math.min(width, height) / 2, 2);
                    return distance2 < radius2;
                }

                MouseArea {
                    id: mouseArea
                    anchors.fill: parent
                    hoverEnabled: true
                    drag.target: spotlightCircle
                    onPressed: { if (parent.containsMouse) { parent.pressed(mouse) } else { mouse.accepted = false } }
                    onClicked: { if (parent.containsMouse) { parent.clicked(mouse) } else { mouse.accepted = false } }
                    onDoubleClicked: { if (containsMouse2) { parent.doubleClicked(mouse) } }
                    onWheel: { if (parent.containsMouse) { parent.wheel(wheel) } }
                }
            }

        }
    }
}

我认为在这种情况下使用HoverHandler而不是 MouseArea 可以给出所需的结果,因为它们按您的预期堆叠,即它们对于鼠标移动完全“透明”,也不会被任何碰巧位于顶部的 MouseArea 阻挡。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM