如何在QML中通过MouseArea实现子元素的鼠标悬停事件传递给父元素?

29

我想在QML中实现以下场景。

Scenario


这是一个用于ListView元素的示例/简化委托:

Component {
    Item {
         id: container
         MouseArea {
         anchors.fill: parent
         hoverEnabled: true

         onClicked: {
             container.ListView.view.currentIndex = index
             container.forceActiveFocus();
         }
         onEntered: {
             actionList.state = "SHOW";
             myItem.state = "HOVER"
         }
         onExited: {
             actionList.state = "HIDE";
             myItem.state = "NORMAL"
         }
         Rectangle {
             id: myItem
             color: "gray"
             anchors.fill: parent
             Row {
                 id: actionList
                 spacing: 5; anchors.fill: parent
                 Image {
                     id: helpAction
                     source: ""    //Some image address
                     width: 16; height: 16; fillMode: Image.PreserveAspectFit
                     states: [
                         State {
                             name: "NORMAL"
                             PropertyChanges { target: helpAction; opacity: 0.7 }
                         },
                         State {
                             name: "HOVER"
                             PropertyChanges { target: helpAction; opacity: 1.0 }
                         }
                     ]
                     MouseArea {
                         hoverEnabled: true
                         anchors.fill: parent

                         onEntered: {
                             parent.state = "HOVER";
                         }
                         onExited: {
                             parent.state = "NORMAL";
                         }
                     }
                     states: [
                         State {
                             name: "SHOW"
                             PropertyChanges { target: actionList; visible: false }
                         },
                         State {
                             name: "HIDE"
                             PropertyChanges { target: actionList; visible: true }
                         }
                     ]
                 }

                 //Other action buttons...

                 states: [
                     // `NORMAL` and `HOVER` states definition here...
                 ]
             }
         }
    }
}

但是我在MouseArea上有一个问题。
内部的MouseArea(actionButton)在entered事件上无法正常工作。当鼠标进入操作按钮时,外部的MouseArea会触发exited事件。

我的代码有什么错误吗?更普遍地说,我该如何在QML中实现这样的情景?

4个回答

33

5
我不确定为什么!你可以看到我的示例代码使用了这种模式,但却无法工作!!然而现在它在使用Qt5.2.0时能够工作 :) 对于其他有兴趣的用户,只需将内部的MouseArea放在外部的MouseArea里面。双重检查hoverEnabled: true,它就会工作了。 - S.M.Mousavi
1
我对答案的两个部分都没有完全理解。第一部分:你是不是指“如果你想在祖先MouseArea中包含后代鼠标悬停事件,请将子MouseArea直接作为父MouseArea的子元素”?如果是这样,那么你的第二个例子是如何工作的,考虑到它只包含一个 MouseArea? - Stefan Monov
我有一个使用情况,其中CheckBox位于列表的一行上,在此列表行上悬停时会突出显示。起初,我将CheckBoxMouseArea放在同一级别,但是一旦将CheckBox嵌套在MouseArea内,并且将hoverEnabled: true应用于封闭的MouseArea,这将为CheckBox启用悬停效果,此答案在运行Qt5.9时有效。 - Kasheen
可以确认这个在Qt 5.11.1上有效。你可能想要一个带有父母鼠标区域和默认属性的基类,就像第二个例子中所示。这样,其后代中的所有项都将成为主鼠标区域的子项。 - rsht
这太棒了。在 5.12 QQC2 中运行得非常好。 - Croll

6
为每个视图元素的状态制作状态,然后您可以使用if语句或case语句来更改这些属性。换句话说,请尽量不要设置元素以在MouseArea上工作,而是在属性上工作,并将元素属性设置为适用于设置属性。希望这可以帮助您,如果不能,请参考以下示例:编辑我添加了颜色以使其在没有任何鼠标的情况下透明。如果我正在使用图像,则会使用不透明度,然后添加大量行为,但这是有效的示例。
import QtQuick 2.0
Rectangle {
    width: 360
    height: 360
    property string state1:"OutMouse"
    property string state2: "OutMouse"
    property string state3: "OutMouse"
    property string state4: "OutMouse"
    Rectangle{
        id:blueRec
        width: parent.width
        height: parent.height / 6
        color: state1 === "InMouse" ? "blue" : "green"
        MouseArea{
            anchors.fill: blueRec
            hoverEnabled: true
            onEntered: state1 = "InMouse"
            onExited: {
                if (state1 === state2 || state3 || state4){
                    state1 = "InMouse"
                }
                if(state1 !== state2 || state3 || state4)
                {
                    state1 = "OutMouse"
                }
            }
        }
        Text {
            text: state1=== "InMouse"? qsTr("foo") :"bar"
            anchors.centerIn: blueRec
        }
        Row{
            width: parent.width
            height: parent.height / 4

            spacing: 2
            anchors{
                left: parent.left
                verticalCenter:  blueRec.verticalCenter
                leftMargin: blueRec.width / 12
            }
            Rectangle{
                id: rec1
                height: parent.height;
                width: height
                color: {
                    if  ( state3 === "InMouse")
                        return "gray"
                    if (state1 === "OutMouse")
                        return "transparent"
                    else
                        return "white"}
                MouseArea{
                    id: rec1M
                    anchors.fill: parent
                    hoverEnabled: true
                    onEntered:{
                        state1 = "InMouse"
                        state2 = "InMouse"
                    }
                    onExited: state2 = "OutMouse"
                }
            }

            Rectangle{
                id: rec2
                height: parent.height ;
                width: height
                color: {
                    if  (state3 === "InMouse")
                        return "gray"
                    if (state1 === "OutMouse")
                        return "transparent"
                    else
                        return "white"
                }
                MouseArea{
                    id: rec2M
                    anchors.fill: parent
                    hoverEnabled: true
                    onEntered:{
                        state1 = "InMouse"
                        state3 = "InMouse"
                    }
                    onExited: state3 = "OutMouse"
                }
            }

            Rectangle{
                id: rec3
                height: parent.height;
                width: height
                color:{
                    if  (state4 === "InMouse")
                        return "gray"
                    if (state1 === "OutMouse")
                        return "transparent"
                    else
                        return "white"
                }
                MouseArea{
                    id:  rec3M
                    anchors.fill: parent
                    hoverEnabled: true
                    onEntered:{
                        state4 = "InMouse"
                        state1 = "InMouse"
                    }
                    onExited: state4 = "OutMouse"
                }
            }
        }
    }
}

5
我尝试了几种方法,但貌似无法同时悬停在两个MouseArea上。当你有一个点击事件时,preventStealingpropagateComposedEvents似乎只能起到作用。但是从内部的MouseArea中,你可以触发另一个MouseAreaentered()信号。像这样:
import QtQuick 2.1

Rectangle {
    width: 500
    height: 500

    Rectangle {
        width:300
        height: 300
        color: "red"

        MouseArea {
            id: big
            anchors.fill: parent
            hoverEnabled:true
            onEntered: {
                console.log("ENTERED BIG mousearea");
            }
            onExited: {
                console.log("EXITED BIG mousearea");
            }
        }

        Rectangle {
            anchors.centerIn: parent
            height: 100
            width: 100
            color: "green"

            MouseArea {
                anchors.fill: parent
                hoverEnabled:true
                onEntered: {
                    console.log("ENTERED small mousearea");
                    big.entered();
                }
                onExited: {
                    console.log("EXITED small mousearea");
                    big.exited();
                }
            }
        }
    }
}

问题在于包含的MouseArea中的exited()信号将在再次调用entered()之前被调用。因此,您可能需要在exited()中“延迟”状态更改,以确保您真正想隐藏操作按钮。另一种解决方案是保存当前鼠标位置,并仅在exited()在其边界之一上被调用时隐藏按钮。

好的,但不完整。我如何实现第二个建议?这是可能的吗? - S.M.Mousavi
onExited() 中,您可以使用 mouseXmouseY 来获取当前鼠标位置,并猜测是否在大型鼠标区域的边界上(基于其自身的 xyheightwidth)。我尝试过了,但是当您快速移动鼠标时,会出现一些 mouseX 不准确的问题。不过这是第一步。 - koopajah
是的,我尝试了你的建议。但是在快速鼠标移出时,mouseX是不正确的(例如onExited被触发,但鼠标位置是(14,57))。同样的问题也存在于使用onPositionChanged时。 - S.M.Mousavi
我已向开发团队报告了这个问题 https://bugreports.qt-project.org/browse/QTBUG-32909 - S.M.Mousavi
我进行了更深入的调查,似乎一旦你移出 MouseArea 区域,mouseXmouseY 的值就不正确了(文档中有说明)。你可以尝试在 C++ 中检索鼠标位置,而不是在 QML 中使用重叠的 MouseArea 来处理悬停事件。 - koopajah

1
尝试这样做:
  • 在内部区域添加一个在鼠标进入时发出的信号。
  • 将信号连接到外部区域。
  • 该信号会导致外部区域进入悬停状态。

在两者上鼠标退出仍会取消悬停状态。当您将鼠标移出控件时,它应该可以正常工作而不需要任何额外的代码。


我已经测试过了,但没有使用信号。我在内部的MouseArea上使用了onEntered信号。如果鼠标进入内部的MouseArea,则外部的MouseArea将被悬停。但是,还有另一个问题! onExited信号会比onEntered信号更早触发! onExited边框比onEnter边框更内部。 - S.M.Mousavi
我也考虑过这个问题,并决定如果信号顺序错误,它将无法正常工作。也许可以添加代码到外部项来监视子项的悬停? - Jay
感谢@Jay。没有成功。 - S.M.Mousavi

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