I was messing around with subclassing QAbstractListModel, and I don't think I understand how to properly add data to the model. Here's my script and the QML:
import QtQuick
import QtQuick.Controls
ApplicationWindow
{
id: mainWindow
visible: true
title: qsTr("Sample Qt Quick application")
width: 400
height: 400
color: "whitesmoke"
Component.onCompleted: console.log("Component completed")
Connections
{
target: main.custom_model
function onDataChanged(topLeft, bottomRight, roles)
{
console.log("Custom model data changed")
}
}
} // ApplicationWindow
import sys
from random import randint
from pathlib import Path
from PySide6.QtCore import Qt, QObject, QTimer, Property, QAbstractListModel, QModelIndex
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
class CustomModel(QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
self._my_items = []
self._role1 = Qt.UserRole + 1
self._role2 = Qt.UserRole + 2
self._roles = {
self._role1: b"role1",
self._role2: b"role2"
}
def append_item(self, arg1, arg2):
row_count = self.rowCount()
self.beginInsertRows(QModelIndex(), row_count, row_count)
self._my_items.append({
self._roles[self._role1]: arg1,
self._roles[self._role2]: arg2
})
self.endInsertRows()
def rowCount(self, parent=QModelIndex()):
"""
Required for subclasses of QAbstractListModels
"""
return len(self._my_items)
def data(self, index, role=Qt.DisplayRole):
"""
Required for subclasses of QAbstractListModels
"""
try:
the_item = self._my_items[index.row()]
except IndexError:
return QVariant()
if role in self._roles:
key = self._roles[role]
return the_item[key]
return QVariant()
def roleNames(self):
return self._roles
class MainContextClass(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = CustomModel()
def add_an_item(self):
thing1 = randint(0, 9)
thing2 = randint(0, 9)
print("Adding {0} and {1}".format(thing1, thing2))
self._model.append_item(thing1, thing2)
@Property(QObject, constant=True)
def custom_model(self):
return self._model
def main():
app = QGuiApplication(sys.argv)
qml_app_engine = QQmlApplicationEngine()
qml_context = qml_app_engine.rootContext()
main_context = MainContextClass(parent=app)
qml_context.setContextProperty("main", main_context)
this_file_path = Path(__file__)
main_qml_path = this_file_path.parent / 'example.qml'
qml_app_engine.load(str(main_qml_path))
timer = QTimer()
timer.setInterval(1000)
timer.setSingleShot(False)
timer.timeout.connect(main_context.add_an_item)
timer.start()
sys.exit(app.exec())
if __name__ == '__main__':
main()
My expectation is that, every time the timer times out, a new row gets added to the listmodel, and therefore, the listmodel's dataChanged
signal should get emitted. The Connections
object's signal handler should then print a message. But it seems like it never executes:
$ python example.py
qml: Component completed
Adding 7 and 0
Adding 8 and 5
Adding 4 and 0
...
If I explicitly add self.dataChanged.emit()
after endInsertRows()
, then obviously the signal handler executes. But in all the documentation and example code I see, this isn't done. So, what's the proper approach?
dataChanged
is emitted (explicitly) when the information of an item changes, when inserting rows then you should listen for the rowsAboutToBeInserted
or rowsInserted
signals:
Connections {
target: main.custom_model
function onRowsAboutToBeInserted(parent, first, last) {
console.log("before", parent, first, last);
}
function onRowsInserted(parent, first, last) {
console.log("after", parent, first, last);
/* print data
let index = main.custom_model.index(first, 0, parent);
let data1 = main.custom_model.data(index, Qt.UserRole + 1);
let data2 = main.custom_model.data(index, Qt.UserRole + 2);
console.log(data1, data2);*/
}
}
On the other hand, in PySide there is no QVariant
, instead you must return python objects, in the case of null QVariant you must return None
(or nothing that is the same):
def data(self, index, role=Qt.DisplayRole):
"""
Required for subclasses of QAbstractListModels
"""
try:
the_item = self._my_items[index.row()]
except IndexError:
return
if role in self._roles:
key = self._roles[role]
return the_item[key]