Duplicate items in ListView
-
Hello,
I have a
ListView
with a delegate which is controlled by a two-layer model in C++. By two layer I mean a base model and an intermediateQSortFilterProxyModel
. I am using Qt.6.2 .The filter model implements the
lessThan()
andfilterAcceptsRow()
functions.In the QML side, the delegates that become visible depend on the result of
filterAcceptsRow()
which implementation is:bool CAlarmSortProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { const QModelIndex myIndex = sourceModel()->index(sourceRow, 0, sourceParent); orderHasChanged(); return myIndex.data(CAlarmModel::StateRole).toBool(); }
I am aware of the Note in the documentation about not storing the state in a delegate (https://doc.qt.io/qt-6.2/qml-qtquick-listview.html#delegate-prop) which I think is not what I am doing here, right? As the StateRole is evaluated on the model level in the C++ side and not on the actual delegate object.
The usage of this list is to display some alarms that are being activated or deactivated based on a boolean variable per alarm -
True
means the delegate must become visible in theListView
, so must be accepted in the proxy model, andFalse
means become invisible. This is saved in the model items under theStateRole
.My problem is that - some times - the list displays duplicate delegates for the same alarms. What is in fact happening, is that these delegate objects are actually overlayed on top or below the "correct" delegates, and will never be destroyed unless I restart the application.
Also important, is that the list has two logical states. One is that the list is collapsed, and then only the higher in order delegate is shown. If this first delegate is extended, then the rest of the activated alarms become visible too. And each of them has an extendable/collapsable rectangle which is activated by clicking on it.
I mention this because the duplication problem appears only when the delegate is extended and at the same time multiple other delegates may be created or destroyed.
So, it feels like whenever there are fast changes in the accepted delegates of the proxy model something gets messed up.
Images of the issue below:
I have tried to reproduce the issue by explicitly changing the alarms' state as fast as possible but I do not get this error. The issue arises when the application is used on an actual embedded device - the alarms' states are set from another processor. However, this should be of no difference for my application as it only "sees" a
True/False
for each alarm's state. It is very difficult to reproduce this error at runtime, it seems to be so random.I found a similar description on this issue https://forum.qt.io/topic/2953/qml-state-delegates but it's not really the same problem.
I read that delegates that go out of the view get destroyed. Can this be that - sometimes - the delegate is not actually destroyed? And then, when the alarm is activated again, the proxymodel creates a new instance of the same alarm delegate, and thus leaving the older one in the view forever? Does this even make sense?
Thank you for your help.
-
[UPDATE]
I have managed to reproduce the issue.
My initial thought of non-destroyed delegates is indeed valid. I have managed to reproduce the issue by setting the
ListView
's propertydelayRemove
toTrue
. Doing so the view will never destroy the created delegates, and each time a new delegate is instantiated a duplicate will be displayed. See image below:Now the first delegate of PA01 is an "old" instantiation of the delegate and the second is the newly created.
So, is it valid to consider that when this issue appears on runtime, the construction/destruction changes so fast that the View does not destroy the object? Although it's still not clear to me why this happens anyway. I think this reported bug is also related https://bugreports.qt.io/browse/QTBUG-45500?focusedCommentId=343909&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel
As a workaround,
- I want to detect if/when such "dangling" delegates occur and destroy them manually. I have tried to identify this with the following ways but I can't get a clear answer on whether a dangling delegate exists in the view or not:
ListView.onRemove: { // if the following would work I could identify this here and manually delete the object, but the parent is always reported as non-null, as this is the visual parent which is always non-zero. How can I get the object parent of a delegate? console.log("Parent is null: " + (parent == null ? "True" : "False")) } ListView.onAdd: { for (var i = 0; i < alarms.children.length; i++) { if (alarms.children[i].parent == null) { // same comment as above console.log("Data child of the list found with null parent!") } } }
or
-
I read that the delegates are actually removed when the view calls
polish()
orupdatePolish()
on the renderer. But these methods are private inside Qt. Is there a way to call these from QML? This way I could force the view to polish whenever aonAdd
occurs. -
I can possibly use the attached property
ListView.reuseItems: true
and instead of creating a new delegate object each time the model changes, reuse the existing items. But does this guarantee that the problem will never occur again?
I would appreciate some feedback on the above options since they can potentially be a solution.
Thank you.
-
Actually option 2 is not really a solution because when a new delegate is created (after the problem is already present) the view does update, but the duplicate delegate still remains. So, even if I manage to enforce a view update it will not have any effect on the problem.
-
For option 2 this is the way to trigger it through QML https://doc.qt.io/qt-5/qml-qtquick-listview.html#forceLayout-method .
-
They are still
visible
but they shouldn't be displayed anymore. If not it warrants a bug report