Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

ListView with TapHandler



  • I have very simple (and I would think almost ubiquitous) need for a scrolling list of items that can be selected with a click/tap. Below is my code with a ListView, where the delegate contains a TapHandler for the item. Is this the right way to do it? I have run this on Windows, iOS and Android, using Qt 5.15. On all targets, it works fine with a mouse, but does not work with the touchscreen. Depending on the size of the TapHandler and the speed that you move your finger, it sometimes fails to scroll the list. If I disable the TapHandler it scrolls fine. Is this a known bug? Is there a workaround?

    import QtQuick 2.15
    import QtQuick.Window 2.15
    import QtQuick.Controls 2.12
    
    Window {
        width: 640
        height: 480
        visible: true
        title: qsTr("Hello World")
    
        Column {
            anchors.fill: parent
    
            CheckBox {
                id: enableTapHandler
                text: "Enable Tap handler"
                checked: false
            }
    
            Label {
                id: whatHappened
                text : "Nothing happened yet..."
                color: "green"
            }
    
            ListView {
                clip: true
                id: control
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.bottom: parent.bottom
                anchors.top: whatHappened.bottom
    
    
    
                delegate: Rectangle {
                    implicitWidth: control.width
                    implicitHeight: itemText.height
                    color: (index%2) ? "white" : "#F0F0F0"
    
                    Text {
                        id:itemText ;
                        text: "Item " + index + ": " + modelData
                    }
    
                    TapHandler {
                        enabled: enableTapHandler.checked
                        onTapped: whatHappened.text = "You tapped item " + index
                    }
                }
    
                model: [
                    // this is just sample data to populate the list
                    "add : Transition",
                    "addDisplaced : Transition",
                    "cacheBuffer : int",
                    "count : int",
                    "currentIndex : int",
                    "currentItem : Item",
                    "currentSection : string",
                    "delegate : Component",
                    "displaced : Transition",
                    "displayMarginBeginning : int",
                    "displayMarginEnd : int",
                    "effectiveLayoutDirection : enumeration",
                    "footer : Component",
                    "footerItem : Item",
                    "footerPositioning : enumeration",
                    "header : Component",
                    "headerItem : Item",
                    "headerPositioning : enumeration",
                    "highlight : Component",
                    "highlightFollowsCurrentItem : bool",
                    "highlightItem : Item",
                    "highlightMoveDuration : int",
                    "highlightMoveVelocity : real",
                    "highlightRangeMode : enumeration",
                    "highlightResizeDuration : int",
                    "highlightResizeVelocity : real",
                    "keyNavigationEnabled : bool",
                    "keyNavigationWraps : bool",
                    "layoutDirection : enumeration",
                    "model : model",
                    "move : Transition",
                    "moveDisplaced : Transition",
                    "orientation : enumeration",
                    "populate : Transition",
                    "preferredHighlightBegin : real",
                    "preferredHighlightEnd : real",
                    "remove : Transition",
                    "removeDisplaced : Transition",
                    "reuseItems : bool",
                    "section",
                    "section.criteria : enumeration",
                    "section.delegate : Component",
                    "section.labelPositioning : enumeration",
                    "section.property : string",
                    "snapMode : enumeration",
                    "spacing : real",
                    "verticalLayoutDirection : enumeration",
                    "Attached Properties",
                    "delayRemove : bool",
                    "isCurrentItem : bool",
                    "nextSection : string",
                    "previousSection : string",
                    "section : string",
                    "view : ListView",
                    "Attached Signals",
                    "add()",
                    "remove()",
                    "cacheBuffer : int",
                    "count : int",
                    "currentIndex : int",
                    "currentItem : Item",
                    "currentSection : string",
                    "delegate : Component",
                    "displaced : Transition",
                    "displayMarginBeginning : int",
                    "displayMarginEnd : int",
                    "effectiveLayoutDirection : enumeration",
                    "footer : Component",
                    "footerItem : Item",
                    "footerPositioning : enumeration",
                    "header : Component",
                    "headerItem : Item",
                    "headerPositioning : enumeration",
                    "highlight : Component",
                    "highlightFollowsCurrentItem : bool",
                    "highlightItem : Item",
                    "highlightMoveDuration : int",
                    "highlightMoveVelocity : real",
                    "highlightRangeMode : enumeration",
                    "highlightResizeDuration : int",
                    "highlightResizeVelocity : real",
                    "keyNavigationEnabled : bool",
                    "keyNavigationWraps : bool",
                    "layoutDirection : enumeration",
                    "model : model",
                    "move : Transition",
                    "moveDisplaced : Transition",
                    "orientation : enumeration",
                    "populate : Transition",
                    "preferredHighlightBegin : real",
                    "preferredHighlightEnd : real",
                    "remove : Transition",
                    "removeDisplaced : Transition",
                    "reuseItems : bool",
                    "section",
                    "section.criteria : enumeration",
                    "section.delegate : Component",
                    "section.labelPositioning : enumeration",
                    "section.property : string",
                    "snapMode : enumeration",
                    "spacing : real",
                    "verticalLayoutDirection : enumeration",
                    "Attached Properties",
                    "delayRemove : bool",
                    "isCurrentItem : bool",
                    "nextSection : string",
                    "previousSection : string",
                    "section : string",
                    "view : ListView",
                    "Attached Signals",
                ]
            }
        }
    }
    


  • I'm not sure I fully understood what you wanted but basically, a touch is like a click so if you put there a mouse area and use your dinger on a touch screen it will receive the signal



  • Thanks for your response, Talya

    I have tried implementing my own TapHandler using a MouseArea, but the issue is the same. The code I presented works with a mouse, but does not work with a touchscreen. There seems to be a problem with way the events are passed from the TapHandler to the enclosing ListView.

    With the mouse pointer, the events are accepted by the TapHandler until you move the mouse a certain distance, or if you move the mouse outside the TapHandler. The movements are then handled by the ListView, which then starts scrolling.

    With the touchscreen, the ListView sometimes does not see the events. If the items are larger, it works some of the time. I think the problem is when you drag to the edge of the TapHandler, it does not release the events. On Windows, I am getting a console message:

    qt.quick.touch.target: QQuickTapHandler(0x1c3c1657da0) pointId 1000000 is missing from current event, but was neither canceled nor released
    

    This looks like a bug specific to touchscreens.

    In any event, the slight delay in scrolling, while the TapHandler accepts events, is not ideal if you are aiming for a buttery-smooth user interface. I think it might be better to work from the Flickable movementStarted and movementEnded signals...


  • Qt Champions 2016

    delegete: ItemDelegate { .... rectangle...
    then use onClick from ItemDelegate



  • Thank you! ItemDelegate does exactly what I am looking for, and works perfectly.


  • Qt Champions 2016

    @bee65 great to hear, that it works for you.
    you can even combine this with per ex. SwitchDelegate - then clicking on the Switch calls onClicked of the SwitchDelegate and clicking anywhere else in the row calls onClicked from ItemDelegate.



  • Thanks. I see there is a whole family of controls designed to be used in a delegate:
    https://doc.qt.io/qt-5/qtquickcontrols2-delegates.html

    It is not very clear what the difference between these controls and the regular controls is, but I'm guessing it is something to do with the way mouse/touch events are processed by the enclosing Flickable. Anyway I have had improved response with touch in the TableView and the TreeView (marketplace version) using these.


Log in to reply