I am using Pyside2 with QML, and try to keep a good organisation of my code.
I want to expose a subclass MyModel
of QAbstractListModel
from Python to QML, to use in a ListView
. The code works perfectly if I declare the MyModel
instance directly inside the engine:
...
engine = QQmlApplicationEngine()
myModel = MyModel(some_dict)
engine.rootContext().setContextProperty("myModel ", myModel)
...
that I can then use so:
ListView {
model: myModel
delegate: Row {
Text { text: name }
Text { text: type }
}
}
However, when I try to define this element as a Property
of a class, to keep things tidy and not registering models all over the place, I can't seem to make it work. I fail to recover good debugging information from qml, which also does not help.
I tried to declare the following
class ModelProvider(QObject):
modelChanged = Signal()
_entries: List[Dict[str, Any]]
def __init__(self, entries, parent=None):
QObject.__init__(self, parent)
self._entries = entries
def _model(self):
return MyModel(self._entries)
myModel = Property(list, _model, notify=modelChanged)
myQVariantModel = Property('QVariantList', _model, notify=modelChanged)
...
modelProvider = ModelProvider(some_dict)
engine.rootContext().setContextProperty("modelProvider", modelProvider )
and then use it so in qml
ListView {
model: modelProvider.myModel
// or model: modelProvider.myQVariantModel
delegate: Row {
Text { text: name }
Text { text: type }
}
}
The result is a blank screen.
I found out there that one potential reason could be that QAbstractListModel
is a QObject
, which would make it non copyable, and in c++ they propose to pass a pointer to it instead. But I thought that this would be the case automatically in Python.
What do I do wrong in this case? And if possible, how could I find out why is the ListView
not rendering anything (a debug output, maybe)? Is it not possible at all to organize my code in this way?
For the context, I try to follow the Bloc pattern, that I enjoy a lot using with dart
and flutter
, in which you have one (or more) central Bloc
class that expose the model and the methods to act on this model for the view.
You have to point out that the Property is a QObject, not a QVariantList or a list. On the other hand I do not think that you change the model so you should use constant property and without signals. Also, you do not believe in the function the Model since each time you invoke _model a different object was created.
main.py
import os
import sys
from functools import partial
from PySide2 import QtCore, QtGui, QtQml
class MyModel(QtCore.QAbstractListModel):
NameRole = QtCore.Qt.UserRole + 1000
TypeRole = QtCore.Qt.UserRole + 1001
def __init__(self, entries, parent=None):
super(MyModel, self).__init__(parent)
self._entries = entries
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid(): return 0
return len(self._entries)
def data(self, index, role=QtCore.Qt.DisplayRole):
if 0 <= index.row() < self.rowCount() and index.isValid():
item = self._entries[index.row()]
if role == MyModel.NameRole:
return item["name"]
elif role == MyModel.TypeRole:
return item["type"]
def roleNames(self):
roles = dict()
roles[MyModel.NameRole] = b"name"
roles[MyModel.TypeRole] = b"type"
return roles
def appendRow(self, n, t):
self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
self._entries.append(dict(name=n, type=t))
self.endInsertRows()
class ModelProvider(QtCore.QObject):
def __init__(self, entries, parent=None):
super(ModelProvider, self).__init__(parent)
self._model = MyModel(entries)
@QtCore.Property(QtCore.QObject, constant=True)
def model(self):
return self._model
def test(model):
n = "name{}".format(model.rowCount())
t = "type{}".format(model.rowCount())
model.appendRow(n, t)
def main():
app = QtGui.QGuiApplication(sys.argv)
entries = [
{"name": "name0", "type": "type0"},
{"name": "name1", "type": "type1"},
{"name": "name2", "type": "type2"},
{"name": "name3", "type": "type3"},
{"name": "name4", "type": "type4"},
]
provider = ModelProvider(entries)
engine = QtQml.QQmlApplicationEngine()
engine.rootContext().setContextProperty("provider", provider)
directory = os.path.dirname(os.path.abspath(__file__))
engine.load(QtCore.QUrl.fromLocalFile(os.path.join(directory, 'main.qml')))
if not engine.rootObjects():
return -1
timer = QtCore.QTimer(interval=500)
timer.timeout.connect(partial(test, provider.model))
timer.start()
return app.exec_()
if __name__ == '__main__':
sys.exit(main())
main.qml
import QtQuick 2.11
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
ApplicationWindow {
visible: true
width: 640
height: 480
ListView {
model: provider.model
anchors.fill: parent
delegate: Row {
Text { text: name }
Text { text: type }
}
}
}