MouseArea works incorrectly inside TableView's delegate
-
@Dmitriano said in MouseArea works incorrectly inside TableView's delegate:
@jeremy_k said in MouseArea works incorrectly inside TableView's delegate:
How does the user reliably select an item if it frequently changes position?
If the user does successfully select, is the highlight supposed to stay with the item when it moves, or jump to the new item under the cursor?- The user attempts to select the item he sees at he moment and if an attempt was unsuccessful, he retries. Or the user presses 'Freeze' button that stops sorting and selects an item from the static list. Or, theoretically, the update frequency can be configured somehow.
For some reason this reminds me of a claw crane.
- If the user does successfully select the item some page are added to a StackView above the TableView (and TableView becomes invisible), so the highlight disappares.
And I mentioned in my first post, the clicks are also handled incorrectly. (A wrong url is opened in the browser.)
The MouseArea might be grabbing the mouse, in which case it will also receive the press and release events.
So is this a TableView's bug? Should it be reported?
I'm leaning toward the MouseArea being responsible, whether a bug or misuse. You didn't mention which TableView this is using. The Qt Quick Controls 1 version has a few signals for handling user selection. The TableView in QtQuick starting from 5.12 doesn't seem to have as rich of an API, but perhaps the UI can be implemented with a single stationary MouseArea that covers the entire view and finds the correct delegate with Item.childAt(), and the Flickable.contentItem.
-
@jeremy_k said in MouseArea works incorrectly inside TableView's delegate:
For some reason this reminds me of a claw crane.
Yes, it can be strange for some users, so theoretically, such kind of the uses will temporarily disable the sorting and then click on a row.
You didn't mention which TableView this is using.
I am using TableView 2.0
but perhaps the UI can be implemented with a single stationary MouseArea that covers the entire view and finds the correct delegate with Item.childAt(), and the Flickable.contentItem.
What
Item.childAt()
is? How to use it? Currently I use MouseArea in a similar way, see the code:TableView { id: table property var columnWidths: [100, 80, 80, 120]; columnWidthProvider: function (column) { return columnWidths[column]; } property var rowHeight: 40 rowHeightProvider: function (column) { return rowHeight; } MouseArea { id: ma anchors.fill: parent hoverEnabled: true onClicked: { var index = Math.floor(mouse.y / (table.rowHeight + table.rowSpacing)); console.log(index, " ", mouse.x, " ", mouse.y, " ", table.contentX, " ", table.contentY) var item = table.model.rowKey(index) window.openMarket(item) } } delegate: DelegateChooser { role: "type" DelegateChoice { roleValue: "symbol" delegate: SymbolCell { item: model.item } } } ... }
it works with a fixed row height that I define with
rowHeight
property. -
@Dmitriano said in MouseArea works incorrectly inside TableView's delegate:
To test my model with QAbstractItemModelTester I added the following code to my function getAllMarkets() that created the model:
Put it in the constructor. Set it to warn so it doesn't kill the app on you.
-
@Dmitriano said in MouseArea works incorrectly inside TableView's delegate:
@jeremy_k said in MouseArea works incorrectly inside TableView's delegate:
but perhaps the UI can be implemented with a single stationary MouseArea that covers the entire view and finds the correct delegate with Item.childAt(), and the Flickable.contentItem.
What
Item.childAt()
is? How to use it? Currently I use MouseArea in a similar way, see the code:https://doc.qt.io/qt-5/qml-qtquick-item.html#childAt-method
import QtQuick 2.15 import QtQuick.Window 2.15 Window { width: 100 height: 100 visible: true Flickable { id: flickable anchors.fill: parent Repeater { id: repeater model: 10 delegate: Rectangle { border.color: "black" border.width: 1 width: 20 height: 20 x: index * 40 y: x } } } Timer { interval: 1000 running: true repeat: true onTriggered: { for (var index in flickable.contentItem.children) { var item = flickable.contentItem.children[index] item.x = Math.floor(Math.random() * flickable.contentItem.width); item.y = Math.floor(Math.random() * flickable.contentItem.height); } mouseArea.updateHighlight(); } } MouseArea { id: mouseArea anchors.fill: flickable hoverEnabled: true property Item lastItem: null function updateHighlight() { var item = flickable.contentItem.childAt(mouseX + flickable.contentX, mouseY + flickable.contentY); if (item) item.color = "yellow"; if (lastItem && item !== lastItem) lastItem.color = "white"; lastItem = item; } onPositionChanged: updateHighlight() } }
-
Moving the MouseArea into the Repeater delegate and removing the timer's call to updateHighlight() in the above example also works as expected. This suggests that MouseArea is able to detect when a hover has ended due to the MouseArea moving, at least in this scenario.
-
@jeremy_k said in MouseArea works incorrectly inside TableView's delegate:
function updateHighlight() { var item = flickable.contentItem.childAt(mouseX + flickable.contentX, mouseY + flickable.contentY);
it is not clear what to do with the
item
accessed with the code above, because this item is a QML control likeText
, for example, but I need not a QML control, but the model bound to the table row. I access it withvar item = table.model.rowKey(index)
where
rowKey(index)
is an invokable method explicitly defined in my model, so I need to know item's index.Also after doing some further testing, I noticed that
CheckBox
does not respond to clicks if the model is sorted dynamically, probably it uses MouseArea that stops working. -
@Dmitriano said in MouseArea works incorrectly inside TableView's delegate:
@jeremy_k said in MouseArea works incorrectly inside TableView's delegate:
function updateHighlight() { var item = flickable.contentItem.childAt(mouseX + flickable.contentX, mouseY + flickable.contentY);
it is not clear what to do with the
item
accessed with the code above, because this item is a QML control likeText
, for example, but I need not a QML control, but the model bound to the table row. I access it withvar item = table.model.rowKey(index)
where
rowKey(index)
is an invokable method explicitly defined in my model, so I need to know item's index.Use the properties of the item returned by childAt(). Color, index, etcetera.
-
@jeremy_k said in MouseArea works incorrectly inside TableView's delegate:
Use the properties of the item returned by childAt(). Color, index, etcetera.
some of my items has
item
property that I declare explicitly with a code like (index
and other properties can be defined in the same way)delegate: DelegateChooser { role: "type" DelegateChoice { roleValue: "symbol" delegate: SymbolCell { item: model.item } }
see the screenshot
so theoretically, my clicking code can be transformed into this:
MouseArea { id: ma anchors.fill: parent //hoverEnabled: true onClicked: { var item = table.contentItem.childAt(mouse.x, mouse.y); window.openMarket(item.item) } }
this also works with dynamically sorted model, but would require declaring
item
property explicitly in all the delegates that is a bit ugly overhead that prevents simple delegates (like aText
) from being simple.Also, as I mentioned above,
CheckBox
does not respond to clicks with dynamically sorted model and I did not try other interactive controls yet.And highlighting a row is still a task that we can't solve without an ugly overhead. For example, if the mouse is over an item (delegate) and I change its
color
I can't change it back when item's index is changed and the item is moved away. -
@Dmitriano said in MouseArea works incorrectly inside TableView's delegate:
@jeremy_k said in MouseArea works incorrectly inside TableView's delegate:
Use the properties of the item returned by childAt(). Color, index, etcetera.
some of my items has
item
property that I declare explicitly with a code like (index
and other properties can be defined in the same way)delegate: DelegateChooser { role: "type" DelegateChoice { roleValue: "symbol" delegate: SymbolCell { item: model.item } }
see the screenshot
so theoretically, my clicking code can be transformed into this:
MouseArea { id: ma anchors.fill: parent //hoverEnabled: true onClicked: { var item = table.contentItem.childAt(mouse.x, mouse.y); window.openMarket(item.item) } }
Be careful about validating that childAt() returned a usable item. The user may have clicked over blank space.
this also works with dynamically sorted model, but would require declaring
item
property explicitly in all the delegates that is a bit ugly overhead that prevents simple delegates (like aText
) from being simple.Sure. This seems to be a view with some complication. Overlaying a MouseArea on the entire thing is avoiding one set of complications by selecting another.
Also, as I mentioned above,
CheckBox
does not respond to clicks with dynamically sorted model and I did not try other interactive controls yet.If the CheckBox is in a delegate, setting
propagateComposedEvents
on the overlay MouseArea may help.And highlighting a row is still a task that we can't solve without an ugly overhead. For example, if the mouse is over an item (delegate) and I change its
color
I can't change it back when item's index is changed and the item is moved away.That's what the lastItem property in https://forum.qt.io/post/621263 is for. Store identifying information for the next update and undo the changes when lastItem != current item. Or have a property that points to the selected row and have all others use the default color.
If you haven't already, have a look at ListView. The highlight delegate and currentIndex property might make more sense for this use. The QtQuick
-
@jeremy_k Unfortunately, my
MouseArea
that covers the entire view and finds the delegate does not detect row insertion. For example on the picture below two rows surrounded with green were inserted (withbeginInsertRows
/endInsertRows
) andMouseArea
stopped responding to the clicks on the last two rows surrounded with red:Below I provided the source code of the entire
TableView
as it is now in my project:TableView { id: table anchors.fill: parent columnSpacing: 5 rowSpacing: 3 clip: true property var columnWidths: [100, 80, 80, 20, 20, 20, 90, 90]; columnWidthProvider: function (column) { return columnWidths[column]; } property var rowHeight: 40 rowHeightProvider: function (column) { return rowHeight; } ScrollBar.horizontal: ScrollBar{} ScrollBar.vertical: ScrollBar{} ScrollIndicator.horizontal: ScrollIndicator { } ScrollIndicator.vertical: ScrollIndicator { } MouseArea { //id: ma anchors.fill: parent //hoverEnabled: true onClicked: { var index = Math.floor((mouse.y - table.originY) / (table.rowHeight + table.rowSpacing)); console.log("index:", index, " mouse: (", mouse.x, "," , mouse.y, ") table.origin: (", table.originX, ",", table.originY + ") table.content: (", table.contentX, ",", table.contentY + ")") var item = table.model.rowKey(index) if (item) window.openMarket(item) } } QtObject { id: enumSymbols property string spotAllowed: "S" property string marginAllowed: "M" property string isolatedMarginAllowed: "I" } delegate: DelegateChooser { role: "type" DelegateChoice { roleValue: "symbol" delegate: SymbolCell { item: model.item } } DelegateChoice { roleValue: "price" delegate: Text { horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignVCenter text: { var val = model.item[model.name]; return val ? val.toFixed(model.item.pricePrecision) : ""; } } } DelegateChoice { roleValue: "signal" delegate: Text { horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignVCenter text: TimeFormat.ago(model.item.signalTime, timeMachine.now) color: model.item.signalTime ? "black" : "gray" } } DelegateChoice { roleValue: "enum" delegate: Text { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: model.display ? enumSymbols[model.name] : "" color: "#1e73cd" } } DelegateChoice { roleValue: "zparams" delegate: Text { property var zparams: model.item[model.name] horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignVCenter text: zparams ? "(%1, %2, %3)".arg(zparams.lag).arg(zparams.threshold.toFixed(2)).arg(zparams.influence.toFixed(2)) : qsTr("No") color: zparams ? "black" : "gray" } } DelegateChoice { roleValue: "check" delegate: CheckBox { checked: model.item[model.name]; onClicked: { table.model.beginUpdateItem(model.item) model.item[model.name] = checked table.model.endUpdateItem(model.item) } } } DelegateChoice { delegate: Text { verticalAlignment: Text.AlignVCenter text: model.item[model.name] } } } }
If I refresh my
TableView
withbeginResetModel
/endResetModel
MouseArea
starts to work correctly.