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

[亡魂溺海] 提交于 2019-12-24 00:39:28

问题


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.

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

回答1:


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



回答2:


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

        }
    }
}


来源:https://stackoverflow.com/questions/53826202/how-can-i-propagate-hover-or-mousearea-update-events-to-lower-elements-in-qml

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!