29

I want to implement the following scenario in QML.

Scenario


Here is a sample/simplified delegate for ListView element:

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

But I have a problem with MouseArea.
Inner MouseArea (actionButton) does not work properly for entered event. When mouse enters on action button, outer MouseArea fires exited event.

Is there any mistake in my code? More generally, how can I implement such a scenario in QML?

BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
S.M.Mousavi
  • 5,013
  • 7
  • 44
  • 59

4 Answers4

33

I was faced by this same problem, and came across the answer in the QtQuick 5.0 documentation for MouseArea. The answer to this is actually quite simple.

If you want to include child mouse hover events in your parent MouseArea, make you child MouseArea a child of the parent MouseArea:

MouseArea {
    id: parent

    MouseArea {
        id: child
    }
}

Since I have a custom Widget type that would be used as the parent view, I ended up with the default property being the children of the MouseArea:

Item {
    default property alias children: mouseArea.data

    MouseArea {
        id: mouseArea
    }
}
darrenp
  • 4,265
  • 2
  • 26
  • 22
iBelieve
  • 1,502
  • 17
  • 32
  • 5
    I'm not sure why! You can see that my sample code used this pattern, but not works!! However it works now using Qt5.2.0 :) For other interested users, just keep inner `MouseArea` inside outer one. Double check for `hoverEnabled: true` and it will works. – S.M.Mousavi Feb 09 '14 at 18:35
  • 1
    I don't understand either part of the answer fully. First part: did you mean `If you want to include descendant mouse hover events in your ancestor MouseArea, make you child MouseArea a direct child of the parent MouseArea`? And if so, how does your second example work, considering it only contains *one* MouseArea? – Stefan Monov May 16 '17 at 16:18
  • I had a use case where a `CheckBox` was on a row of a list, where the list row would highlight on hover. At first I had the `CheckBox` and the `MouseArea` at the same level, but once the `CheckBox` was nested inside the `MouseArea` and `hoverEnabled: true` was applied to the enclosing `MouseArea` this enabled the hover effects for the `CheckBox`, this answer works running Qt5.9. – Kasheen Jun 05 '17 at 11:02
  • can confirm this works with Qt 5.11.1. You probably want to have base class with parent mouse area and default property as shown in the second example. This way all items in its descendants would be children of the main mouse area. – rsht Oct 08 '18 at 14:30
  • This is great. Worked like a charm in 5.12 QQC2 – Croll Jul 29 '19 at 13:14
5

Iv'e tried a few things but it does not seem possible to hover over two MouseArea simultaneously. The preventStealing and propagateComposedEvents seem to only work when you have a click event. But from the inner MouseArea you can trigger the entered() signal of the other one. Something like this:

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

The issue is that the exited() signal from the containing MouseArea will be called before calling the entered() back again. So you might need to "delay" the change of state in exited() just to make sure you really want to hide your action buttons. Another solution would be to save the current mouse position and hide the buttons ONLY if exited() is called with the mouse on one of its border.

koopajah
  • 23,792
  • 9
  • 78
  • 104
  • Good but not complete. How can i implement second suggestion? Is it possible? – S.M.Mousavi Aug 09 '13 at 15:51
  • In `onExited()` you can use `mouseX` and `mouseY` to have the current mouse position and guess if you are on the border of your big mousearea (based on its own `x`, `y`, `height` and `width`). I tried it but there are some issues where the `mouseX` is off when you move your mouse quickly. Still it is a first step – koopajah Aug 09 '13 at 16:50
  • Yes i tried your suggestion. But on fast mouse out, mouseX is incorrect (for example `onExited` fired but mouse positin is `(14,57)`). Also same issue using `onPositionChanged`. – S.M.Mousavi Aug 09 '13 at 17:58
  • I reported this issue to development team https://bugreports.qt-project.org/browse/QTBUG-32909 – S.M.Mousavi Aug 09 '13 at 18:25
  • I've investigated a bit more and it seems that as soon as you move out of the `MouseArea` then the values for `mouseX` and `mouseY` are incorrect (it is written on the documentation). You could try retrieving the mouse position in C++ instead of in QML as overlapping `MouseArea`are not working for hover events – koopajah Aug 09 '13 at 21:00
5

make states for each state of the elements in the View then you can use things like if statements or case statements to change these properties In Other words, Try not to set your elements up to work on MouseArea but on properties And set the Elements properties to work on the set properties I hope that this helps if not here is example:

EDIT I added the color to be transparent. if there is no mouse what so ever. If I was using a Image I would use opacity then add a bunch of Behaviors also But this is a working

example

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"
                }
            }
        }
    }
}
guest
  • 51
  • 2
1

Try this:

  • add a signal to the inner area that's emitted on mouse enter.
  • Connect the signal to the outer area.
  • The signal causes the outer area to enter the hovered state.

Mouse exit on both will still cancel hover state. As you move the mouse off the controls it should work correctly without any extra code

Jay
  • 13,803
  • 4
  • 42
  • 69
  • I already test it, but not by using signal. I used `onEntered` signal on inner `MouseArea`. If mouse enters on inner `MouseArea`, outer one will be hovered. BUT, there is another issue! `onExited` signal will triggers earlier than `onEntered` signal! `onExited` border is inner than `onEnter` border. – S.M.Mousavi Aug 24 '13 at 07:29
  • I thought about this as well and decided if the signals go in the wrong order it would not work as well. Perhaps add code to the outer item to monitor the child items for hover? – Jay Aug 24 '13 at 12:20
  • Thnks @Jay. Not success – S.M.Mousavi Aug 25 '13 at 15:59