Solved Dynamic generation of ListView content
-
Hi all,
I've got a QML application and I'm trying to fill in a ListView with data pulled from a SQL database (in C++). Getting data works fine, but creating a ListView with an arbitrary number of entries is being problematic. I've been searching the web for this and have seen a lot of ways of doing this, which are variably complicated and weird. What I settled on is this:
import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQml 2.2 import QtQml.Models 2.3 Item { z: 3 visible: false height: 680 width: 800 property int iRowCount: 7 property int ixRowCounter: 1 Text { id: lblTitle anchors.top: parent.top anchors.topMargin: 10 anchors.left: parent.left anchors.leftMargin: 10 text: "Choose flavors:" font.pointSize: 20 horizontalAlignment: Text.AlignLeft } Rectangle { id: yoMenuContainer anchors.left: parent.left anchors.leftMargin: 10 anchors.right: parent.right anchors.rightMargin: 10 anchors.top: lblTitle.bottom anchors.topMargin: 5 border.width: 1 height: 400 ListModel { id: yoListModel ListElement { sqlID: 0 } } ListView { id: yoListView anchors.fill: parent model: yoListModel delegate: yoDelegate clip: true } Component { id: yoDelegate Item { id: yoItem Component.onCompleted: { var component = Qt.createComponent("YoMenuRow.qml"); if(ixRowCounter <= iRowCount) { yoListModel.append({"sqlID": ixRowCounter}); if(component.status === Component.Ready) { var object = component.createObject(yoItem, {"id": "yoRow"+ixRowCounter}); } object.y = object.height * (ixRowCounter - 1); ixRowCounter++; } } } } } }
... the referenced YoMenuRow.qml file:
import QtQuick 2.7 import QtQuick.Controls 2.2 Row { id: yoRow spacing: 30 height: 100 bottomPadding: 5 CheckBox { anchors.verticalCenter: parent.verticalCenter onCheckStateChanged: { console.log("Checked box for id " + sqlID); } } Image { source: "/img/img-entry-64.png" } Text { text: "This entry" anchors.verticalCenter: parent.verticalCenter font.pointSize: 20 } }
... as you can see, I'm not even attempting the SQL stuff at this point, I just want a ListView with an arbitrary number of lines generated when the page loads. This code does that just fine, and I have some placeholder code that loops through and creates some number of entries (iRowCount, currently 7). I get 7 entries, BUT the flickable part is broken.
The behavior I get now is that when the screen appears, I have all 7 entries. If I pull DOWN on the list (so beyond the starting point), it behaves fine - it snaps back when I let go. If I pull UP, to see entries farther down, the entire list disappears. It behaves a bit like it's trying to scroll, but at light speed so the entire list snaps out of view instantly. If I let go, it snaps back to the first list item.
I've done various things that have changed this behavior slightly, but never to a useful degree. If I remove the Component.onCompleted stuff and just put in 7 Rows, the Flickable part works fine, so in theory the basic setup of the ListView is right, just the Component part is breaking it.
Any suggestions on what I can try here? I get the feeling that when I'm creating components, something - a size, or index, or position - is just getting off, and if I fix that I'll be good. That's my hope, anyway!
Thanks!
-
@Elliott Impossible to test (pasted to two qml files):
qrc:/main.qml:55: ReferenceError: ixRowCounter is not defined qrc:/main.qml:22: ReferenceError: tStyle is not defined qrc:/main.qml:19: ReferenceError: tStyle is not defined qrc:/main.qml:17: ReferenceError: lblTitle is not defined
Style can be changed ad hoc but please at least make the example self-contained with ixRowCounter and other possible crucial things.
-
@Eeli-K Whoops! Sorry, I was trying to cut out unnecessary stuff but got carried away. I edited the original post so the code should be more self-contained.
-
@Elliott I just can't understand either what you want to do or why you're doing it that way. To create delegate objects you don't need Component.onCompleted of the delegate or createObject/createComponent. Especially you shouldn't care about creating delegates and positioning inside the delegate that way. It's the list view which creates and positions delegate objects. You handle only the model.
Here's a simplified app:
import QtQuick 2.6 import QtQuick.Controls 2.2 ApplicationWindow { visible: true width: 640 height: 480 Item { anchors.fill:parent id: root property int iRowCount: 7 Rectangle { id: yoMenuContainer anchors.left: parent.left anchors.leftMargin: 10 anchors.right: parent.right anchors.rightMargin: 10 anchors.top: lblTitle.bottom anchors.topMargin: 5 border.width: 1 height: 400 //model is handled here for convenience, finally it would be in c++ // and triggered by whatever is suitable for the app Component.onCompleted: { // iRowCount is in the root item but for testing could be hardcoded here as well for (var rowCounter = 0; rowCounter < root.iRowCount; rowCounter++) { yoListModel.append({"sqlID": rowCounter}); } } ListModel { id: yoListModel } ListView { id: yoListView anchors.fill: parent model: yoListModel delegate: YoMenuRow{} //No createComponent! No positioning! clip: true } } } }
-
@Eeli-K That is perfect! The code I had was getting progressively more complex and weird because what I started with wasn't working, and I was trying progressively more desperate things. Having
object.y = object.height * (ixRowCounter - 1);
in particular seemed like a warning sign that things had gotten out of hand. This code is working exactly like I needed. Thanks!