ListView - currentIndex cannot be set in delegate with Component.onCompleted
-
Hello,
I would like to understand the underlying reason for the following behavior. There is such an exemplary piece of code:
import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15 Window { visible: true width: 500 height: 600 ListModel { id: myModel ListElement {name: "Item";} ListElement {name: "Item";} ListElement {name: "Item";} ListElement {name: "Item";} } ListView { id: listView width: parent.width height: 0.7 * parent.height signal test() model: myModel delegate: Rectangle { id: listDelegate color: listView.currentIndex == index ? "green" : "red" height: 50 width: listView.width Text { id: listDelegateText text: name anchors.centerIn: parent font.pointSize: 20 } Component.onCompleted: { # the part that is not working: listView.currentIndex = 2 } } } }My expectation is that
currentIndexshould be set to2. However, there is even no attempt to change the index. The problem is that:- other
ListViewproperties can be changed form that part that is not working, so we clearly have access to the right reference ListViewcurrentIndexcan be changed in any other place of the code- starting an outside
TimerfromonCompletedpart, even with the interval set to0, also allows for thecurrentIndexchange
Given that all, the natural expectation is that the code above should also work. But it is not a case. I have started digging into the source code of QML and the only thing I have found is the following code:
qtdeclarative-dev/src/quick/items/qquickitemview.cppFxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, QQmlIncubator::IncubationMode incubationMode) { Q_Q(QQuickItemView); if (requestedIndex == modelIndex && incubationMode == QQmlIncubator::Asynchronous) return nullptr; for (int i=0; i<releasePendingTransition.count(); i++) { if (releasePendingTransition.at(i)->index == modelIndex && !releasePendingTransition.at(i)->isPendingRemoval()) { releasePendingTransition[i]->releaseAfterTransition = false; return releasePendingTransition.takeAt(i); } } inRequest = true; QObject* object = model->object(modelIndex, incubationMode); QQuickItem *item = qmlobject_cast<QQuickItem*>(object); if (!item) { if (!object) { if (requestedIndex == -1 && model->incubationStatus(modelIndex) == QQmlIncubator::Loading) { // The reason we didn't receive an item is because it's incubating async. We keep track // of this by assigning the index we're waiting for to 'requestedIndex'. This will e.g. let // the view avoid unnecessary layout calls until the item has been loaded. requestedIndex = modelIndex; } } else { model->release(object); if (!delegateValidated) { delegateValidated = true; QObject* delegate = q->delegate(); qmlWarning(delegate ? delegate : q) << QQuickItemView::tr("Delegate must be of Item type"); } } inRequest = false; return nullptr; } else { item->setParentItem(q->contentItem()); if (requestedIndex == modelIndex) requestedIndex = -1; FxViewItem *viewItem = newViewItem(modelIndex, item); if (viewItem) { viewItem->index = modelIndex; // do other set up for the new item that should not happen // until after bindings are evaluated initializeViewItem(viewItem); unrequestedItems.remove(item); } inRequest = false; return viewItem; } }and
void QQuickItemView::setCurrentIndex(int index) { Q_D(QQuickItemView); if (d->inRequest) // currently creating item return; d->currentIndexCleared = (index == -1); d->applyPendingChanges(); if (index == d->currentIndex) return; if (isComponentComplete() && d->isValid()) { d->moveReason = QQuickItemViewPrivate::SetIndex; d->updateCurrent(index); } else if (d->currentIndex != index) { d->currentIndex = index; emit currentIndexChanged(); } }There is that
inRequestboolean variable that smells suspicously. It would mean that callingonCompletedinside the delegate does not actually mean that the creation is fully completed.Generally, the questions:
- Am I correct that
inRequestvariable prevents from changingcurrentIndexin the way shown on the first code snippet in this post? - Isn't that a bug?
- other
-
Hello,
I would like to understand the underlying reason for the following behavior. There is such an exemplary piece of code:
import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15 Window { visible: true width: 500 height: 600 ListModel { id: myModel ListElement {name: "Item";} ListElement {name: "Item";} ListElement {name: "Item";} ListElement {name: "Item";} } ListView { id: listView width: parent.width height: 0.7 * parent.height signal test() model: myModel delegate: Rectangle { id: listDelegate color: listView.currentIndex == index ? "green" : "red" height: 50 width: listView.width Text { id: listDelegateText text: name anchors.centerIn: parent font.pointSize: 20 } Component.onCompleted: { # the part that is not working: listView.currentIndex = 2 } } } }My expectation is that
currentIndexshould be set to2. However, there is even no attempt to change the index. The problem is that:- other
ListViewproperties can be changed form that part that is not working, so we clearly have access to the right reference ListViewcurrentIndexcan be changed in any other place of the code- starting an outside
TimerfromonCompletedpart, even with the interval set to0, also allows for thecurrentIndexchange
Given that all, the natural expectation is that the code above should also work. But it is not a case. I have started digging into the source code of QML and the only thing I have found is the following code:
qtdeclarative-dev/src/quick/items/qquickitemview.cppFxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, QQmlIncubator::IncubationMode incubationMode) { Q_Q(QQuickItemView); if (requestedIndex == modelIndex && incubationMode == QQmlIncubator::Asynchronous) return nullptr; for (int i=0; i<releasePendingTransition.count(); i++) { if (releasePendingTransition.at(i)->index == modelIndex && !releasePendingTransition.at(i)->isPendingRemoval()) { releasePendingTransition[i]->releaseAfterTransition = false; return releasePendingTransition.takeAt(i); } } inRequest = true; QObject* object = model->object(modelIndex, incubationMode); QQuickItem *item = qmlobject_cast<QQuickItem*>(object); if (!item) { if (!object) { if (requestedIndex == -1 && model->incubationStatus(modelIndex) == QQmlIncubator::Loading) { // The reason we didn't receive an item is because it's incubating async. We keep track // of this by assigning the index we're waiting for to 'requestedIndex'. This will e.g. let // the view avoid unnecessary layout calls until the item has been loaded. requestedIndex = modelIndex; } } else { model->release(object); if (!delegateValidated) { delegateValidated = true; QObject* delegate = q->delegate(); qmlWarning(delegate ? delegate : q) << QQuickItemView::tr("Delegate must be of Item type"); } } inRequest = false; return nullptr; } else { item->setParentItem(q->contentItem()); if (requestedIndex == modelIndex) requestedIndex = -1; FxViewItem *viewItem = newViewItem(modelIndex, item); if (viewItem) { viewItem->index = modelIndex; // do other set up for the new item that should not happen // until after bindings are evaluated initializeViewItem(viewItem); unrequestedItems.remove(item); } inRequest = false; return viewItem; } }and
void QQuickItemView::setCurrentIndex(int index) { Q_D(QQuickItemView); if (d->inRequest) // currently creating item return; d->currentIndexCleared = (index == -1); d->applyPendingChanges(); if (index == d->currentIndex) return; if (isComponentComplete() && d->isValid()) { d->moveReason = QQuickItemViewPrivate::SetIndex; d->updateCurrent(index); } else if (d->currentIndex != index) { d->currentIndex = index; emit currentIndexChanged(); } }There is that
inRequestboolean variable that smells suspicously. It would mean that callingonCompletedinside the delegate does not actually mean that the creation is fully completed.Generally, the questions:
- Am I correct that
inRequestvariable prevents from changingcurrentIndexin the way shown on the first code snippet in this post? - Isn't that a bug?
- other
-
@KroMignon
So, the origin of that question is the following issue raised on SO:
https://stackoverflow.com/questions/63252698/in-listview-how-can-you-set-the-currentindex-or-currentitem-to-a-dynamically-cr#comment111851987_63252698I just started to wonder why it is not possible to change
currentIndexinside delegateComponent.onCompleted. What is the reason? The code I provided is as simple as possible just to expose the issue. -
@KroMignon
So, the origin of that question is the following issue raised on SO:
https://stackoverflow.com/questions/63252698/in-listview-how-can-you-set-the-currentindex-or-currentitem-to-a-dynamically-cr#comment111851987_63252698I just started to wonder why it is not possible to change
currentIndexinside delegateComponent.onCompleted. What is the reason? The code I provided is as simple as possible just to expose the issue.@Kuczmil said in ListView - currentIndex cannot be set in delegate with Component.onCompleted:
I just started to wonder why it is not possible to change currentIndex inside delegate Component.onCompleted.
For me this is a non sense, you really know when ListView will create/destroy a delegate instance.
It could be because model has changed (item inserted / deleted) or View has been scrolled and item visibility changes.This is not the right place to do something like this.