QAbstractListModel as member of QmlSingleton in Python not working
-
I ran into an issue and it seems like I understood something wrong.
I want to create a project similar to QML Oscilloscope example:
I have a generator class that has a list of signal-elements, each represents a sine-signal. Controlled by a timer the generator class loops over all signals and evaluates the equations, create a QPointF which is then added to a QLineSeries and rendered live in a ChartView. Although there are some issues with the performance it works. I'll have some ideas to address this later.My problem is: I want to render all SignalElements in a ListView so the user may add, remove and edit entries. Because the generator has to interfere with the model I thought it would be good to keep the instance of the QAbstractListModel as a member of my controller instance. like:
QML_IMPORT_NAME = "io.qt.signalcontroller" QML_IMPORT_MAJOR_VERSION = 1 QML_IMPORT_MINOR_VERSION = 0 @QmlElement @QmlSingleton class SignalController(QObject): newValue = Signal(float, float) # Time, Value def __init__(self): super().__init__(None) self.generator = SignalGenerator(self) self.signalModel = SignalModel() self.series: list[QPointF] = []
and us it inside my QML-file:
ListView{ id: listView anchors.fill: parent anchors.margins: 5 implicitWidth: 200 clip:true model: SignalController.signalModel delegate: Row { spacing: 5 Layout.maximumWidth: listRect.width Label { text: "Amplitude" Layout.maximumWidth: 100 } TextField { text: model.amp Layout.maximumWidth: 60 } . . .
This way I could later replace the generator by a class that reads data from COM-ports.
But the app starts with an empty list view. And no error is thrown.
a print-statement in the model's data method shows it is never called.
I would like to understand how this should be done correctly.I code for 3 years now but since I'm self taught it is fairly possible I missed something.
Another idea to approach this would be to make the SignalModel also as a Singleton and overloading the __ new __ method so it always returns the existing instance. But unfortunately it always returned a NoneType -object.
Sure... I could make the model a QmlElement and pass everything from controller to model by linking signals inside a .qml file. But this feels odd.
Any hints?
Here is my implementation of my QAbstractListModel:
class SignalModel(QAbstractListModel): def __init__(self, parent=None): super().__init__(parent) self._data: list[SignalElement] = [] def rowCount(self, parent=QModelIndex()): return len(self._data) def roleNames(self): return { Qt.UserRole + 0 : b"amp", Qt.UserRole + 1 : b"off", Qt.UserRole + 2 : b"freq", Qt.UserRole + 3 : b"phase", } def data(self, index, role=Qt.DisplayRole): print(f"Data called: {index.row()} {role}") if not index.isValid() or not 0 <= index.row() < len(self._data): return None if role == Qt.UserRole + 0: return self._data[index.row()].amplitude elif role == Qt.UserRole + 1: return self._data[index.row()].offset elif role == Qt.UserRole + 2: return self._data[index.row()].frequency elif role == Qt.UserRole + 3: return self._data[index.row()].phase return None
-
@LS-KS said in QAbstractListModel as member of QmlSingleton in Python not working:
But the app starts with an empty list view. And no error is thrown.
You might need to turn on the appropriate logging categories.
a print-statement in the model's data method shows it is never called.
Are you certain that
SignalController.signalModel
in the QML is referencingself.signalModel
from the Python-defined singleton? In the code posted, I don't see a definition of a Qt property. With PyQt, exposing a Python object member as a property to QML requires a little code:class Object(QObect): @pyqtProperty(str) def myProperty(): return "some text"
Text { text: Object.myProperty }
-
@jeremy_k Thank you for your answer!
I tried the following after reading this link:
from PySide6.QtCore import QObject, Property class SignalController(QObject): def __init__(self): super()__init__(None) self.generator = SignalGenerator(self) self.signalModel = SignalModel() @Property(SignalModel) def signalModel(self): return self._signalModel @signalModel.setter def signalModel(self, model: SignalModel): self._signalModel = model
This throws an exception:
QQmlExpression: Expression file://...../main.qml:46:21 depends on non-NOTIFYable properties: SignalController::signalModel
Don't know what to do with this exception. Couldn't find anything that made sense to me.
Edit:
I changed the code to:
from PySide6.QtCore import QObject, Property class SignalController(QObject): signalModelChanged = Signal() def __init__(self): super()__init__(None) self.generator = SignalGenerator(self) self.signalModel = SignalModel() def get_signalModel(self): return self._signalModel def set_signalModel(self, model: SignalModel): self._signalModel = model signalModel = Property(fget=get_signalModel, fset=set_signalModel, notify=signalModelChanged())
No QML-Exception. But still no list entry.
-