如何在QML中将鼠标悬停或鼠标区域更新事件传播到较低的元素?

3
我有一些兄弟Rectangle元素,具有半径属性,因此它们显示为圆形。每个元素都有一个子项,该子项具有一个子MouseArea,其目的是实现“圆形鼠标区域”效果(原始SO答案)。Item和MouseArea被检测到,以便点击和拖动仅在Rectangle可见的圆形形状内生效,而不是在Rectangle的实际占地面积内生效。
不幸的是,在下面的示例中出现了故障。当在点处拖动时,期望的结果是移动圆形1,并且在大多数情况下会发生这种情况。但是,如果您创建了圆形1然后创建了圆形2,然后将鼠标光标移动到点上,则不会发生这种情况。如果您这样做并尝试拖动或单击,则您的交互将穿透到背景全窗口MouseArea并创建一个新圆形。

two circles partially overlapping, numbered 1 and 2, with a dot in one circle near the other

这个问题的原因是,当鼠标光标从圆形#2移动到点时,圆形#1的MouseArea的mouseX和mouseY没有更新。当圆形#2允许点击向下传播时,它会击中圆形#1的矩形,但然后圆形#1的项声明containsMouse为false,并再次向下传播。
只要鼠标光标离开圆形#2边界矩形的范围,例如向上或向左移动一点,圆形#1的MouseArea就会得到更新,并且其containsMouse变为true,然后它开始重新捕捉点击和拖动事件。
我已经尝试了一些潜在的解决方案,但都没有比下面的代码更进一步。
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
                }
            }
        }
    }
}

也许可以尝试使用 DragHandler,看看是否有帮助? - Mitch
3个回答

3

我认为在这种情况下使用HoverHandler而不是MouseArea可以达到期望的结果,因为它们像您期望的那样堆叠,即它们对于鼠标移动是完全“透明”的,也不会被任何位于其上方的MouseArea阻挡。


2

这不是你问题的确切解决方案,但这是我克服问题根源的方法。

在我的应用程序中,有一个 MouseArea 与一个大块的场景重叠,该场景是一个 QQuickFrameBufferObject。这是我绘制3D场景的地方。由于在QML中无法传播 QHoverEvent,因此您需要使用 onPositionChanged 处理程序捕获 位置更改 信号,并调用 C++ 中的方法,该方法将向所需项目发送 QHoverEvent

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

0

您需要拒绝底层MouseAreapressed事件。这应该足以解决您的问题。如果拒绝了按下事件,单击将自动转发到底层兄弟项。propagateComposedEventsfilterChildren在您的情况下是无用的。

请注意,如果滚轮事件导致您的spotlightCircle的z坐标小于0,则它将不再接受鼠标事件,因为它们将被"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) } }
                }
            }

        }
    }
}

我现在无法进行测试,但是我会说当我尝试这种方法时遇到的问题是,即使单击/按下/拖动等事件到达基础元素,该元素的MouseArea坐标还没有更新,因此它认为其containsMouse为false。 - Sparr
在我的机器上运行正常(虽然这并不是一个非常有效的论点...)。也许QtQuick 2.10和2.12之间会有一些轻微的行为改变?无论如何,一个可能的解决方法是将containsMouse属性更改为可调用函数,该函数以鼠标事件作为参数。这样,您就可以确保获得正确的坐标。 - Yoann Quenach de Quivillic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接