Solved My qml doesn't find my model from python
-
Hello here is a simple test to fill a ListView from ma python main file, I have the following error:
file:/// ..... /main.qml:26: ReferenceError: elements is not definedhere is the qml file
import QtQuick 2.9 import QtQuick.Window 2.2 import QtQuick.Controls 1.4 import PyModels 1.0 Window { id: window visible: true width: 640 height: 460 title: qsTr("Test") Button { id: btn text: qsTr("fill model") onClicked: { py_mainapp.fillModel() } } ListView { anchors.top: parent.top anchors.left: btn.right anchors.right: parent.right anchors.bottom: parent.bottom model: elements.items delegate: Item { anchors.left: parent.left anchors.right: parent.right height: 24 Row { anchors.fill: parent Label { text: model.name } Label { text: model.title } } } } }
Here is the py file
#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import os from PyQt5.QtWidgets import QApplication from PyQt5.QtQml import QQmlApplicationEngine, QQmlListProperty, qmlRegisterType from PyQt5.QtCore import pyqtSlot, QObject, QVariant,pyqtProperty class ListElementPy(QObject): def __init__(self, name, title): super(ListElementPy, self).__init__() self._title = title self._name = name @pyqtProperty('QString') def title(self): return self._title @title.setter def title(self, avalue): if avalue != self._title: self._title = avalue @pyqtProperty('QString') def name(self): return self._name @name.setter def name(self, avalue): if avalue != self._name: self._name = avalue class ElementsPy(QObject): def __init__(self): super(ElementsPy, self).__init__() self._items = [] @pyqtProperty('QString') def items(self): return QQmlListProperty(ListElementPy, self, self._items) def append(self, avalue): self._items.append(avalue) class MainApp(QObject): def __init__(self, context, parent): super(MainApp, self).__init__(parent) self.win = parent self.ctx = context self.elements = ElementsPy() @pyqtSlot() def fillModel(self): for x in range (1,50): self.elements.append(ListElementPy("name %.0f" % (x), "title %.0f" % (x))) if __name__ == "__main__": app = QApplication(sys.argv) qmlRegisterType(ListElementPy, "PyModels", 1, 0, "ListElementPy") qmlRegisterType(ElementsPy, "PyModels", 1, 0, "ElementsPy") engine = QQmlApplicationEngine() ctx = engine.rootContext() engine.load("main.qml") win = engine.rootObjects()[0] py_mainapp = MainApp(ctx, win) ctx.setContextProperty("py_mainapp", py_mainapp) ctx.setContextProperty("elements", py_mainapp.elements) win.show() sys.exit(app.exec_())
What am I doing wrong?
py_mainapp is found in the qml, but not elements -
I added the following lines in the qml (which seems to me quite strange as I already defined elements in the python file)
ElementsPy { id: elements } ListElementPy { property string name property string title }
Now I have the following error:
TypeError: init() takes 1 positional argument but 2 were given -
ok, I've found on internet (https://riverbankcomputing.com/pipermail/pyqt/2017-January/038497.html) a solution working, so I'm gonna start with modifying the following code which works:
qml:import QtQuick 2.9 import QtQuick.Window 2.2 import QtQuick.Controls 1.4 import Example 1.0 Window { width: 500 height: 500 visible: true ListView { anchors.fill: parent model: store.channels delegate: Item { anchors.left: parent.left anchors.right: parent.right height: 24 Row { anchors.fill: parent Label { text: name } Label { text: title } } } } }
python:
from sys import argv from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal from PyQt5.QtQml import QQmlListProperty, QQmlApplicationEngine, qmlRegisterType class Channel(QObject): nameChanged = pyqtSignal() titleChanged = pyqtSignal() def __init__(self, name='', title='', *args, **kwargs): super().__init__(*args, **kwargs) self._name = name self._title = title @pyqtProperty('QString', notify=nameChanged) def name(self): return self._name @name.setter def name(self, name): if name != self._name: self._name = name self.nameChanged.emit() @pyqtProperty('QString', notify=nameChanged) def title(self): return self._title @title.setter def title(self, title): if title != self._title: self._title = title self.titleChanged.emit() class Store(QObject): channelsChanged = pyqtSignal() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._channels = [ Channel('aa', 'AAA'), Channel('bb', 'BBB') ] @pyqtProperty(QQmlListProperty, notify=channelsChanged) def channels(self): return QQmlListProperty(Channel, self, self._channels) @channels.setter def channels(self, channels): if channels != self._channels: self._channels = channels self.channelsChanged.emit() def appendChannel(self, channel): self._channels.append(channel) self.channelsChanged.emit() def main(): app = QApplication(argv) qmlRegisterType(Channel, 'Example', 1, 0, 'Channel') qmlRegisterType(Store, 'Example', 1, 0, 'Store') store = Store() engine = QQmlApplicationEngine() engine.rootContext().setContextProperty('store', store) engine.load('main.qml') # After 3 seconds, we append a new Channel QTimer.singleShot(3000, lambda: store.appendChannel(Channel('cc', 'CCC'))) exit(app.exec_()) if __name__ == '__main__': main()
-
The problem is in your delegate. The syntax is
role.property
notmodelName.property
. See this wiki post (the code is C++ but there's no real difference in python)