Search code examples
pythonqtqmlpyside2qstandarditemmodel

Emitting QStandardItemModel from Python backend results in "undefined" QML object


I am trying to pass a QStandardItemModel object to QML via a connection with a ContextProperty containing a backend which generates this model based on a button click, but while the model is correctly created in Python, it seems it cannot be loaded by QML. In reality the application I am working on is more complex, but I am including a minimal example which replicates the error I get. Bascially, when it comes time to set the model which is passed to QML via a signal, I get the error: Unable to assign [undefined] to QAbstractItemModel*

I know this kind of question has probably been asked before, and I found other related questions, but their solutions were very similar to what I implemented, so I am failing to see exactly where the error is occuring.

Firstly, the QML application is a simple window with a Button and a TreeView which starts with a "null" model on application start-up. Once the button is clicked, it triggers a slot ("trigger_signal_send") in the backend which creates a model, and emits a signal containing this model. The QML file also has a Connection object which listens for the emitted signal from the "backend" and updates the model in the TreeView. Note I am only including the relevant lines (things like anchors and graphics properties have been omitted).

main.qml

import QtQuick 2.14
import QtQuick.Controls 1.4
import QtQuick.Window 2.14

Window {
    id: mainWindow
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    property var myModel: null

    function setModel(model) {
        mainWindow.myModel = model
    }

    Rectangle {
        anchors.fill: parent
        anchors.margins: {
            top: 10
            left: 10
            right: 10
            bottom: 60
        }
        clip: true

        TreeView {
            id: treeView
            model: mainWindow.myModel
            anchors.fill: parent
        }
    }

    Button {
        id: btn
        onClicked: {
            backend.trigger_signal_send()
        }
    }

    Connections {
        id: conn
        target: backend

        function onSendModel(model) {
            mainWindow.setModel(model)
        }
    }
}

main.py Here is where I define the Backend class, with the relevant Slot that generates and emits the model when the button is clicked, and where I connect my Backend object to QML via the ContextProperty.

# This Python file uses the following encoding: utf-8
import os
from pathlib import Path
import sys

from PySide2.QtCore import QObject, Signal, Slot
from PySide2.QtGui import QGuiApplication, QStandardItemModel, QStandardItem
from PySide2.QtQml import QQmlApplicationEngine


class Backend(QObject):
    # Class attributes
    model: QStandardItemModel = QStandardItemModel()

    # Signals
    sendModel: Signal = Signal(QStandardItemModel)

    def __init__(self, *args, **kwargs):
        super(Backend, self).__init__(*args, **kwargs)
        self.sendModel.connect(self.print_model)

    @Slot()
    def print_model(self, model):
        print(model.item(0).text())
        return

    @Slot(result=None)
    def trigger_signal_send(self):
        self.model = QStandardItemModel()
        self.model.appendRow(QStandardItem('TEST ITEM'))
        self.sendModel.emit(self.model)
        return




if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    backend = Backend()
    engine.rootContext().setContextProperty('backend', backend)

    engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_())

I tried setting the "result" keyword in the @Slot decorator to the QStandardItemModel type and returning the model directly from that slot, but I get an error at the QML level: Unknown method return type: QStandardItemModel*. This was my rationale behind chaning the return value to None and to just emit that model as a signal. I have also tried changing the "setModel" function in QML to just reference the treeView.model attribute directly, but I get the original error.

I know that the model is created correctly, and is of the right type because if I create another Slot directly in the backend class that will print the text of the first item when "sendSignal" is emitted, everything works correctly.

Furthermore, when setting the first line of "onSendSignal" to log the input to that function, it says "undefined", so the issue lies in between when sendSignal.emit(model) gets called in the backend and when onSendSignal(model) is called in the Connections block of my .qml file.

I have spent a lot of time playing around with different solutions, but I cannot seem to figure out what is being lost in between these two function calls. Any help would be greatly appreciated!


Solution

  • What happens is that QML does not recognize the QStandardItemModel type, when a model is exported then it is better to use QObject as a signature:

    # Signals
    sendModel: Signal = Signal(QObject)
    

    Unlike the QTreeView, in TreeView you have to set the roles that each column will consume:

    TreeView {
        id: treeView
        model: mainWindow.myModel
        anchors.fill: parent
        TableViewColumn {
            title: "Name"
            role: "display"
            width: 300
        }
    }