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

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 currentIndex should be set to 2. However, there is even no attempt to change the index. The problem is that:

    • other ListView properties can be changed form that part that is not working, so we clearly have access to the right reference
    • ListView currentIndex can be changed in any other place of the code
    • starting an outside Timer from onCompleted part, even with the interval set to 0, also allows for the currentIndex change

    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.cpp

    FxViewItem *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 inRequest boolean variable that smells suspicously. It would mean that calling onCompleted inside the delegate does not actually mean that the creation is fully completed.

    Generally, the questions:

    1. Am I correct that inRequest variable prevents from changing currentIndex in the way shown on the first code snippet in this post?
    2. Isn't that a bug?


  • @Kuczmil I don't think I understand what you want to achieve with this code?

    Why do you want to set ListView.currentIndex in delegate Component.onCompleted.
    This looks very strange to me, can you explain what is the goal of this?



  • @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_63252698

    I just started to wonder why it is not possible to change currentIndex inside delegate Component.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.


Log in to reply