Solved 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...
-
delegete: ItemDelegate { .... rectangle...
then use onClick from ItemDelegate -
Thank you! ItemDelegate does exactly what I am looking for, and works perfectly.
-
@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.htmlIt 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.