Please note that versions of this question have been asked before, but the various comments and proposed solutions have not worked. They either cover C++, for which I have tried replicating the solution without success, or more often than not, they are using older versions of Qt Quick and are methods that are no longer workable in the newer versions.
Problem
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:
This table takes me just a few minutes to create in LabVIEW. However, I am completely at a loss for how to properly do this in QML and with Python using the singleton approach.
What I've read
QStandardItemModel
for Qt 6.7QStandardItemModel
for Qt for Python 6.7TreeView
QML type for Qt 6.7- Countless StackOverflow and forum posts, where I have tried to replicate supposed solutions to no avail.
A pure QML example that kind of works
The official documentation unfortunately highlights the simplest case in which the model is defined purely in QML.
import QtQuickimport QtQuick.Windowimport QtQuick.Layoutsimport QtQuick.Controlsimport Qt.labs.qmlmodelsimport com.simplifiedWindow { width: 740 height: 540 visible: true title: "Python log viewer" TableView { id: log anchors.fill: parent columnSpacing: 1 rowSpacing: 1 clip: true model: TableModel { TableModelColumn { display: "Timestamp" } TableModelColumn { display: "Logger" } TableModelColumn { display: "Level" } TableModelColumn { display: "Message" } rows: [ {"Timestamp": "today","Logger": "root","Level": "DEBUG","Message": "This is a debug message" }, {"Timestamp": "today","Logger": "root","Level": "INFO","Message": "This is an info message" } ] } delegate: Rectangle { border.width: 1 clip: true Text { text: display anchors.centerIn: parent } } }}
This looks like:
Of course, the message column needs to be made wider, and I'm not sure how to get header names displayed using this setup, but it at least shows that the rest of the QML is correct and that the issue is with my model.
QML plus Python that doesn't work
The following code should be very close to working, but it does not. I do not know how to amend it to get the table displaying as expected.
// Main.qmlimport QtQuickimport QtQuick.Windowimport QtQuick.Layoutsimport QtQuick.Controlsimport Qt.labs.qmlmodelsimport com.simplifiedWindow { 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.pyQML_IMPORT_NAME = "com.simplified"QML_IMPORT_MAJOR_VERSION = 1# Core dependenciesfrom pathlib import Pathimport sys# Package dependenciesfrom PySide6.QtCore import QObject, Signal, Property, Qtfrom PySide6.QtGui import QGuiApplication, QStandardItemModel, QStandardItemfrom PySide6.QtQml import QQmlApplicationEngine, QmlElement, QmlSingletonLOG_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@QmlSingletonclass 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 tableif __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())
No matter how I've tried building up the items and rows in the QStandardItemModel
, I have been unable to get anything to display. I have also tried all sorts of things in the text
field item inside the Text
component.
Here's a set of commented out code I've tried, amongst many other permutations:
# table.setItemRoleNames( # {i: FIELD_NAMES[i].encode() for i in range(len(FIELD_NAMES))} # ) # for row_index, line in enumerate(lines): # for column_index, key in enumerate(FIELD_NAMES): # item = QStandardItem(str(line[key])) # table.setItem(row_index, column_index, item) # for line in lines: # row = QStandardItem() # for role_index, key in enumerate(FIELD_NAMES): # row.setData(str(line[key]), Qt.ItemDataRole + role_index) # table.appendRow(row) # for line in lines: # row = QStandardItem() # for role_index, key in enumerate(FIELD_NAMES): # row.appendColumn([QStandardItem(str(line[key]))]) # table.appendRow(row)
I have also tried using a combination of the two solutions, trying to provide a list model for the rows
of a TableModel
, but that property expects a JSON array. I was not able to convert anything in Python to something accepted by the rows
property.
Questions
[Primary] What is the simplest, most direct way to display 2D data in a QML
TableView
from aQStandardItemModel
in Python? What is wrong with my above attempt?[Secondary] Where should I go to learn more about this? The official documentation is close to useless along these lines, and so I'm stuck being in experimental mode just trying things out. Qt also works poorly with QML and the singleton approach in Python, which increases the debugging difficulty.