Search code examples
qtqmlqt5qtquick2

QML TapHandler on top of child elements (ListView)


ListView steals taps from TapHandler regardless of declaration order. That is, the Rectangle, which is declared first, responds to a click, but not the ListView, which also lies below the TapHandler.

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Layouts 1.15

Window {
    id: root
    width: 640
    height: 480
    visible: true
    color: tap.pressed ? "#6698D9" : "white"
    title: qsTr("Hello World")

    ColumnLayout {
        anchors.fill: parent

        Rectangle {
            Layout.preferredHeight: text.height
            Layout.fillWidth: true
            color: "transparent"
            border {
                color: "blue"
                width: 3
            }

            Text {
                id: text
                anchors.centerIn: parent
                font.pointSize: 24
                color: "red"
                text: "some text"
            }
        }

        ListView {
            id: seriesView
            Layout.fillWidth: true
            Layout.fillHeight: true
            orientation: ListView.Vertical
            spacing: 5
            clip: true
            model: ["t1", "t2", "t3"]
            delegate: Rectangle {
                width: seriesView.width
                height: childText.height
                color: "transparent"
                border {
                    color: "blue"
                    width: 3
                }

                Text {
                    id: childText
                    anchors.centerIn: parent
                    font.pointSize: 24
                    color: "green"
                    text: modelData
                }
            }
        }
    }

    TapHandler {
        id: tap
        onTapped: console.log("Tapped root")
    }
}

I also do not understand why the tap on the free area is not caught. It's about TapHandler. Not MouseArea.

I would like the TapHandler to catch clicks within the parent element (highlighted in red).

repo link: https://github.com/conelov/SO_qml_TapHandler_question


Solution

  • Right now, because you use Layout.fillHeight: true the ListView extends to the entire bottom. This means the ListView covers both the area covered by your delegates and the blank area.

            ListView {
                id: seriesView
                Layout.fillWidth: true
                Layout.fillHeight: true
            }
    

    As you observed, because ListView gets the mouse events the TapHandler will not get any.

    Two things you can do to get the ListView to cede mouse events to the TapHandler. (1) you can declare additional TapHandlers in the ListView delegates. (2) you can reduce blank space of the ListView.

           Item {
                Layout.fillWidth: true
                Layout.fillHeight: true
                ListView {
                    id: seriesView
                    width: parent.width
                    height: Math.min(parent.height, childrenRect.height)
                    delegate: Rectangle {
                        // ...
                        TapHandler { }
                    }  
                }
          }
    

    Here's a working example that implements the above.

    import QtQuick
    import QtQuick.Controls
    import QtQuick.Layouts
    
    Page {
        id: root
        
        background: Rectangle {
            color: tap.pressed ? "#6698D9" : "white"
        }
        
        ColumnLayout {
            anchors.fill: parent
            
            Rectangle {
                Layout.preferredHeight: text.height
                Layout.fillWidth: true
                color: "transparent"
                border {
                    color: "blue"
                    width: 3
                }
                
                Text {
                    id: text
                    anchors.centerIn: parent
                    font.pointSize: 24
                    color: "red"
                    text: "some text"
                }
            }
            
            Item {
                Layout.fillWidth: true
                Layout.fillHeight: true
    
                ListView {
                    id: seriesView
                    width: parent.width
                    height: Math.min(parent.height, childrenRect.height)
                    orientation: ListView.Vertical
                    spacing: 5
                    clip: true
                    model: ["t1", "t2", "t3"]
                    delegate: Rectangle {
                        width: seriesView.width
                        height: childText.height
                        color: "transparent"
                        border {
                            color: "blue"
                            width: 3
                        }
                        
                        Text {
                            id: childText
                            anchors.centerIn: parent
                            font.pointSize: 24
                            color: "green"
                            text: modelData
                        }
    
                        TapHandler {
                        }
                    }
                }
            }
        }
        
        TapHandler {
            id: tap
            onTapped: console.log("Tapped root")
        }
    }
    

    You can Try it Online!

    With all this effort to make TapHandler work this way, it might be easier to simply use MouseArea { anchors.fill: parent} instead. It wasn't clear why you needed to use TapHandler?