Search code examples
qtlistviewqmlqt-quick

ListView.view is always null in ListView's delegate


The setup

I have a ListView with some general model and myDelegate component.

ListView {
    id: myListView
    delegate: myDelegate
    ...
}

In myDelegate component, I want to set ListView's currentItem. I currently do it like this:

Component {
    id: myDelegate
    ...
    MouseArea {
        onClicked: myListView.currentIndex = model.index
    }
}

Everything works as expected.

The issue

As commonly known, it's bad practice to use ListView's id inside a delegate, since there could be multiple ListView using the same delegate component. So, ListView.view attached property should be used instead.

But if I change the delegate's code like this:

Component {
    id: myDelegate
    ...
    MouseArea {
        onClicked: ListView.view.currentIndex = model.index
    }
}

I get a TypeError: Value is null and could not be converted to an object.

With the following console.logs, I discovered that ListView.view is always null. I don't understand why. Setting currentIndex on ListView itself fails too.

onClicked: {
    console.log("ListView:", ListView)
    console.log("ListView.view:", ListView.view)
    console.log("ListView.currentIndex:", ListView.currentIndex)
}
...
qml: ListView: QQuickListViewAttached(0x6db0b30)
qml: ListView.view: null
qml: ListView.currentIndex: undefined

Minimal reproducible example

Qt v6.2.1
Empty Qt Quick project in Qt Creator
main.qml:

import QtQuick
import QtQuick.Window

Window {
    width: 150
    height: 350
    visible: true

    Component {
        id: myDelegate

        Rectangle {
            color: ListView.isCurrentItem ? "pink" : "lightblue"
            implicitWidth: 150
            implicitHeight: 50

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    ListView.view.currentIndex = model.index
                    console.log("ListView", ListView)
                    console.log("ListView.view", ListView.view)
                }
            }

            Text {
                anchors.centerIn: parent
                text: model.index
            }
        }
    }

    ListView {
        id: myListView
        anchors.fill: parent
        spacing: 2
        model: 6
        delegate: myDelegate
    }
}

Solution

  • The attached properties are set in the root of the delegate so if you want to access from a child item then you must use the root as reference:

        Component {
            id: myDelegate
    
            Rectangle {
                id: root_delegate // <---
    
                color: ListView.isCurrentItem ? "pink" : "lightblue"
                implicitWidth: 150
                implicitHeight: 50
    
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        root_delegate.ListView.view.currentIndex = model.index;
                        console.log("ListView", root_delegate.ListView);
                        console.log("ListView.view", root_delegate.ListView.view);
                    }
                }
    
                Text {
                    anchors.centerIn: parent
                    text: model.index
                }
    
            }
    
        }