1

I have a list in QML and displaying it in a listView object. I need to access this data from python when a button is pressed. In Python I create a QStringListModel object and bind it to my listModel in QML using setContextProperty. I can see the list being created and displayed as expected in QML, however when I want to access the data from python, the list is empty. Here is the code:

QML:

import QtQuick 2.0
import QtQuick.Controls 2.3

Rectangle{
    id: root
    width:800
    height:600

    ListView {
        id: listView
        x: 476
        y: 64
        width: 110
        height: 160
        model: myModel
        ListModel {
            id: myModel
            ListElement {
                name: "Grey"
                colorCode: "grey"
            }

            ListElement {
                name: "Red"
                colorCode: "red"
            }

            ListElement {
                name: "Blue"
                colorCode: "blue"
            }

            ListElement {
                name: "Green"
                colorCode: "green"
            }
        }
        delegate: Item {
            x: 5
            width: 80
            height: 40
            Row {
                id: row1
                Rectangle {
                    width: 40
                    height: 40
                    color: colorCode
                }

                Text {
                    text: name
                    anchors.verticalCenter: parent.verticalCenter
                    font.bold: true
                }
                spacing: 10
            }
        }
    }
}

Python:

import sys
from PyQt5.QtCore import QUrl, QStringListModel
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQuick import QQuickView


app = QApplication(sys.argv)
view = QQuickView()
view.setSource(QUrl("main.qml"))

pyList = QStringListModel()
view.rootContext().setContextProperty("myModel",pyList)
print(pyList.stringList())
print(pyList.rowCount())
view.show()
print("Done!")
sys.exit(app.exec_())

I was on the impression that when we use python bindings, the object created in python is bound to the QML object. Therefore if the QML list has data (dynamically created in the UI), the python list should automatically be filled with that data? What am I missing?

AghaPapa
  • 11
  • 3
  • from what we see here, your list is empty, where do you add your items? show implementation of `sendChar` as well as your `delegate`. Developers like to help when they can copy paste the code and see what's wrong with it. – bardao Mar 29 '19 at 22:44
  • Hey I have changed the code to a short example. I have a list in qml, but I cannot access it's data from python. I'm a Qml newbie so I'm not sure what I am missing here! – AghaPapa Apr 01 '19 at 21:02

1 Answers1

3

You are assuming that because you pass a model through setContextProperty() that has the same name as the ListModel are the same, no, and on the contrary it causes your program to be unstable since QML overlaps both names. Instead you have to create a model in python and export it, but since you also want to interact with the QML it is better to export a QObject that has the model as qproperty. Do not use findChild, findChildren to get QML elements in python, you must do the opposite of exporting elements from python to QML.

Considering the above, I have implemented a Manager class that has the qproperty model, in addition to a slot that can be invoked from QML. To avoid complicating the code, I used the QStandardItemModel class as the basis of the model, QStringListModel is a read-only model, so it does not work for this case.

main.py

from enum import Enum
from PyQt5 import QtCore, QtGui, QtQuick

class ElementRoles:
    NameRole = QtCore.Qt.UserRole + 1000
    ColorRole = QtCore.Qt.UserRole + 1001

class ElementModel(QtGui.QStandardItemModel, ElementRoles):
    QtCore.Q_ENUM(ElementRoles)

    def __init__(self, parent=None):
        super(ElementModel, self).__init__(parent)
        roles = {
            ElementModel.NameRole: b'name',
            ElementModel.ColorRole: b'colorCode'
        }
        self.setItemRoleNames(roles)

    @QtCore.pyqtSlot(str, QtGui.QColor)
    def addElement(self, name, color):
        item = QtGui.QStandardItem()
        item.setData(name, ElementModel.NameRole)
        item.setData(color, ElementModel.ColorRole)
        self.appendRow(item)

class Manager(QtCore.QObject):
    def __init__(self, parent=None):
        super(Manager, self).__init__(parent)
        self._model = ElementModel()

    @QtCore.pyqtProperty(QtCore.QObject, constant=True)
    def model(self):
        return self._model

    @QtCore.pyqtSlot()
    def on_clicked(self):
        print("count:", self._model.rowCount())
        for row in range(self._model.rowCount()):
            it = self._model.item(row)
            print("row:", row)
            for role, name in self._model.roleNames().items():
                print("role:", name, "data:", it.data(role))

if __name__ == '__main__':
    import os
    import sys
    app = QtGui.QGuiApplication(sys.argv)
    manager = Manager()
    view = QtQuick.QQuickView()
    file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "main.qml")
    view.rootContext().setContextProperty("manager", manager)
    view.setSource(QtCore.QUrl.fromLocalFile(file))
    view.show()
    sys.exit(app.exec_())

main.qml

import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3

Rectangle{
    id: root
    width:800
    height:600

    ColumnLayout{
        anchors.fill: parent
        ListView {
            id: listView
            Layout.alignment: Qt.AlignLeft
            Layout.fillHeight: true
            model: manager.model
            delegate: Item {
                x: 5
                width: 80
                height: 40
                Row {
                    id: row1
                    Rectangle {
                        width: 40
                        height: 40
                        color: colorCode
                    }

                    Text {
                        text: name
                        anchors.verticalCenter: parent.verticalCenter
                        font.bold: true
                    }
                    spacing: 10
                }
            }
        }

        Button{
            Layout.alignment: Qt.AlignCenter
            text: "click me"
            onClicked: manager.on_clicked()
        }
    }

    Component.onCompleted:{
        manager.model.addElement("Gray", "gray")
        manager.model.addElement("Red", "red")
        manager.model.addElement("Blue", "blue")
        manager.model.addElement("Green", "green")
    }
}

I recommend you read my another answer for a more detailed explanation.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241