QML鼠标事件传递(鼠标穿透)

0.前言

有时候,我们的MouseArea区域重叠了,这时候默认是最上层的MouseArea接收鼠标事件,而我们可能需要底层也能接收这个事件。虽然可以关联两个MouseArea的信号,但是这种操作局限性比较大,耦合性太强,某个MouseArea并不能被外部所见等等。在Qt Widgets中可以通过setAttribute(Qt::WA_TransparentForMouseEvents,true)来进行鼠标穿透,而在QML中我们可以通过propagateComposedEvents属性或者使用mouse.accepted=false来完成部分鼠标事件的传递。

Qt官方文档如是说:

propagateComposedEvents保存组合的鼠标事件是否将自动传播到与它重叠但在视觉堆叠顺序中较低的其他MouseArea 。默认情况下,此属性为false。这里面包含几个组成事件:clicked,doubleClicked和pressAndHold。它们由基本的鼠标事件组成(比如pressed),并且与基本事件相比可以不同地传播。

如果propagateComposedEvents设置为true,则组合事件将自动传播到场景中相同位置的其他MouseArea。每个事件将按照堆栈顺序传播到其下方的下一个启用的 MouseArea,并向下传播此可视层次结构,直到MouseArea接受该事件为止。与pressed事件不同,如果没有处理程序,则组合事件将不会被自动接受。

也就是说,类似clicked的组合事件,只要把propagateComposedEvents设置为true,在没有写处理程序的时候会自动的传递事件,有处理程序则加上mouse.accepted=false也会传递。而pressed等基本事件,只需要在处理程序中写上mouse.accepted=false就会传递下去,不用设置propagateComposedEvents。

1.组合事件的传递

直接上代码:

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480

    MouseArea{
        anchors.fill: parent
        onClicked: console.log("~root clicked")
        onDoubleClicked: console.log("~root double")
        onPressAndHold: console.log("~root hold")
    }

    //item的层级在root之上(因为他写在mousearea的后面,又是同级的)
    Rectangle{
        width: 200
        height: 200
        color: "green"

        MouseArea{
            anchors.fill: parent
            //是否传递到被覆盖的MouseArea
            propagateComposedEvents: true
            onClicked: {
                console.log("item clicked")
                //组合事件可以不写处理程序,或者accepted为false
                mouse.accepted = false
            }
            onDoubleClicked: console.log("item double")
            //onPressAndHold: console.log("item hold")
        }
    }
}

代码中,我们设置propagateComposedEvents为true,开启组合事件的传递,通过打印我们可以看到,没有写处理程序的hold和写了mouse.accepted = false的click传递到了被遮挡的MouseArea,而doubleClick则没有传递。

2.基本事件的传递

一般我们主要关注press和release事件,而release又是跟随press的,如果press设置mouse.accepted=false,那么relase只会在接收press的MouseArea里响应,即便它设置为true。如果press没有设置,那么即便release设置mouse.accepted=false,也是没有用的。代码如下:

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480

    MouseArea{
        anchors.fill: parent
        onPressed: console.log("~root pressed")
        onReleased: console.log("~root released")
    }

    //item的层级在root之上(因为他写在mousearea的后面,又是同级的)
    Rectangle{
        width: 200
        height: 200
        color: "green"

        MouseArea{
            anchors.fill: parent
            //是否传递到被覆盖的MouseArea
            //propagateComposedEvents: true
            onPressed: {
                console.log("item pressed")
                mouse.accepted=false
            }
            onReleased: {
                console.log("item released")
                mouse.accepted=true
            }
        }
    }
}

 QML鼠标事件传递(鼠标穿透)_第1张图片

通过打印可以看到,release被接收press的MouseArea直接处理了,也就没有打印 “item released”

除了点击还有hover相关事件我们也比较常用,相关的主要有entered、exited、positionChanged等。默认鼠标hover是没有开启的,这些事件是由我们按下鼠标时才会激活,我们把MouseArea的hoverEnabled设置为true就能处理鼠标的移动了。

如果上层的MouseArea没有设置hoverEnabled,那么hover相关的事件自然就会传递下去,要是鼠标按下时的hover操作也传递的话,可以写上onPressed: mouse.accepted=false。但是如果上层MouseArea处理了entered或者exited就尴尬了,这两个信号里没有mouse参数,没法设置mouse.accepted,而postionChanged就算有mouse参数,设置了也没效果。

测试的结论就是hover如果没有设置hoverEnabled,那么会传递下去;如果设置为了true,那么就只能自己享用相关的事件了。如果谁知道有什么方式设置的话,告诉我下。

3.混合情况

有时候混合事件(如pressed)和基本事件(如clicked)可能都在使用,这就要注意其中的关联关系,比如clicked是和press相关的。如果press设置了mouse.accepted=false,那么即便不把propagateComposedEvents设置为true,pressed和clicked也会传递下去:

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480

    MouseArea{
        anchors.fill: parent
        onPressed: console.log("~root pressed")
        onClicked: console.log("~root clicked")
    }

    //item的层级在root之上(因为他写在mousearea的后面,又是同级的)
    Rectangle{
        width: 200
        height: 200
        color: "green"

        MouseArea{
            anchors.fill: parent
            //是否传递到被覆盖的MouseArea
            //propagateComposedEvents: true
            onPressed: mouse.accepted=false
        }
    }
}

 

可以看到,因为pressed设置mouse.accepted=false,clicked事件也在pressed接受的MouseArea触发了。

让我们来看个更复杂点的例子:

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480

    MouseArea{
        anchors.fill: parent
        onPressed: console.log("~root pressed")
        onClicked: console.log("~root clicked")
    }

    Rectangle{
        width: 200
        height: 200
        color: "green"

        MouseArea{
            anchors.fill: parent
            //是否传递到被覆盖的MouseArea
            propagateComposedEvents: true
            onPressed: {
                console.log("item pressed")
                mouse.accepted=true
            }
            /*onClicked: {
                console.log("item clicked")
                mouse.accepted=false
            }*/
        }
    }
    MouseArea{
        anchors.fill: parent
        onPressed: {
            console.log("surface pressed")
            mouse.accepted=false
        }
        onClicked: {
            console.log("surface clicked")
            mouse.accepted=false
        }
    }
}

最上层的surface把pressed设置了mouse.accepted=false,所以即便没有设置propagateComposedEvents,组合事件clicked也传递下去了。而中间层的item只处理了press,而又把propagateComposedEvents设置为了true,所以clicked最后传递到了~root上去了。

(2019-11-29 添加内容)

覆盖在类似Control2里的PushButton、TextField这些控件上时,需要用 "onPressed: mouse.accepted=false; " 来传递下去。

最后的总结就是:组合事件如clicked主要由propagateComposedEvents设置,基本事件如pressed主要由mouse.accepted设置,组合事件会被基本事件影响。

4.参考

Qt文档:https://doc.qt.io/qt-5/qml-qtquick-mousearea.html#propagateComposedEvents-prop

博客:https://blog.csdn.net/qq_43248127/article/details/97273421

博客:https://blog.csdn.net/wanghualin033/article/details/78155544

你可能感兴趣的:(QML,三言两语)