bmitc
bmitc

Reputation: 853

Displaying QStandardItemModel from Python in a QML TableView

I am trying to display very simple 2D data in Qt Quick using a QML TableView and Qt for Python / PySide6. Here is an example of what I am looking to create:

enter image description here

In the following code, I have exposed a singleton object to QML which provides a QStandardItemModel as a QObject property, but have been unable to get anything to display. What is wrong with my attempt?

// Main.qml
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
import com.simplified

Window {
    width: 740
    height: 540
    visible: true
    title: "Python log viewer"

    TableView {
        id: log

        anchors.fill: parent

        columnSpacing: 1
        rowSpacing: 1
        clip: true

        model: Simplified.log

        delegate: Rectangle {
            border.width: 1
            clip: true

            Text {
                text: display
                anchors.centerIn: parent
            }
        }
    }
}
# main.py
QML_IMPORT_NAME = "com.simplified"
QML_IMPORT_MAJOR_VERSION = 1

# Core dependencies
from pathlib import Path
import sys

# Package dependencies
from PySide6.QtCore import QObject, Signal, Property, Qt
from PySide6.QtGui import QGuiApplication, QStandardItemModel, QStandardItem
from PySide6.QtQml import QQmlApplicationEngine, QmlElement, QmlSingleton

LOG_ENTRIES = [
    {
        "Timestamp": "2024-07-01 19:16:03.326",
        "Name": "root.child",
        "Level": "DEBUG",
        "Message": "This is a debug message",
    },
    {
        "Timestamp": "2024-07-01 19:16:03.326",
        "Name": "root.child",
        "Level": "INFO",
        "Message": "This is an info message",
    },
]

FIELD_NAMES = ["Timestamp", "Name", "Level", "Message"]

@QmlElement
@QmlSingleton
class Simplified(QObject):
    log_changed = Signal()

    def __init__(self) -> None:
        super().__init__()

        _ = self.log
        self.log_changed.emit()

    @Property(QStandardItemModel, notify=log_changed)
    def log(self):
        lines = LOG_ENTRIES

        table = QStandardItemModel(len(lines), len(FIELD_NAMES))
        table.setHorizontalHeaderLabels(FIELD_NAMES)

        for line in lines:
            row = [QStandardItem(str(line[key])) for key in FIELD_NAMES]
            table.appendRow(row)

        return table

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

    qml_file = Path(__file__).resolve().parent / "qml" / "Main.qml"

    engine.load(qml_file)

    if not engine.rootObjects():
        sys.exit(-1)

    engine.singletonInstance("com.simplified", "Simplified")

    sys.exit(application.exec())

Upvotes: -1

Views: 154

Answers (1)

relent95
relent95

Reputation: 4732

You need to store the model. The QML TableView will not own the model, like the QAbstractItemView derived classes. The returned model from the Simplified.log() will be destroyed in the event loop, after the QML implementation of the TableView accesses it several times. Check the lifecycle of the models in the official examples.

Also, you specified the type of the Simplified.log property as the QStandardItemModel, but the property type dispatching implementation of the QML engine doesn't know the QStandardItemModel as a property type because it's not registered by default. See the reference such as The QML Type System and Data Type Conversion.

The following is the example of the fixed code.

@QmlElement
@QmlSingleton
class Simplified(QObject):
    log_changed = Signal()
    def __init__(self):
        super().__init__()
        self.model = None

    @Property(QObject, notify=log_changed)
    def log(self):
        if self.model is None:
            self.model = model = QStandardItemModel()
            for line in LOG_ENTRIES:
                row = [QStandardItem(str(line[key])) for key in FIELD_NAMES]
                model.appendRow(row)
        return self.model

Upvotes: -1

Related Questions