I have spent a couple of afternoons, can't get it to work; I am just going in circles.
Below, I will include both Python and QML sources of a minimal kind-of-working program.
There are two features I can't figure out how to implement and I would very much appreciate some assistance.
Desired features:
Keyboard navigation
Editing of cell values
As follows:
When I click on a cell, I would like it to be highlighted in some way so I know where I am at.
After that, I would like to be able to use the tab key or the arrow keys to navigate the table.
When I double-click on a cell or press <enter> while highlighted, I would like for that cell to enter edit mode; and, most importantly (which is the part I can't get to work), when I leave edit mode, I would like the new value to be reflected in the table.
Here is the Python code:
import os
import sys
import json
from pathlib import Path
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import (
QAbstractTableModel, QModelIndex, QObject, Qt,
Property, Signal, Slot
)
script_dir = Path( os.path.dirname(os.path.abspath(__file__)) )
# --- MyTableModel ---
# A custom model for use with QML's TableView.
# It provides methods for reading, writing, and modifying table data.
class MyTableModel(QAbstractTableModel):
def __init__(self, header=None, data=None, parent=None):
super().__init__(parent)
self._header = header or []
self._data = data or []
def rowCount(self, parent=QModelIndex()):
return len(self._data)
def columnCount(self, parent=QModelIndex()):
if self._data:
return len(self._data[0])
return 0
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
row = index.row()
col = index.column()
if row >= len(self._data) or col >= len(self._data[0]):
return None
if role == Qt.DisplayRole:
return str(self._data[row][col])
@Slot(int, Qt.Orientation, result="QVariant")
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return self._header[section]
else:
return str(section)
# required for editable models
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
if index.isValid() and 0 <= index.row() < self.rowCount() and 0 <= index.column() < self.columnCount():
row = index.row()
col = index.column()
self._data[row][col] = value
self.dataChanged.emit(index, index, [role])
return True
return False
# required for editable models, to inform the view which roles are editable
def flags(self, index):
if not index.isValid():
return Qt.NoItemFlags
return Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsEnabled
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# --- MyTableManager ---
# A QObject that provides the interface between QML and MyTableModel.
class MyTableManager(QObject):
dataChanged = Signal()
def __init__(self, model):
super().__init__()
self._model = model
self._selected_row = -1
@Property(QObject, constant=True)
def model(self):
return self._model
@Slot(str)
def loadData(self, filepath):
self._model.loadData(filepath)
@Slot(int, int, str)
def updateValue(self, row, col, value):
index = self._model.index(row, col, QModelIndex())
self._model.setData(index, value)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
items = [
[ "1", "Alice" , "30"],
[ "2", "Bob" , "25"],
[ "3", "Charlie", "35"]
]
my_table_model = MyTableModel(data=items)
manager = MyTableManager(my_table_model)
engine.rootContext().setContextProperty("tableManager", manager)
engine.rootContext().setContextProperty("tableModel", my_table_model)
qml_file = Path(__file__).parent / "gen.qml"
engine.load(qml_file)
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec())
Here is the QML source:
import QtQuick
import QtQuick.Controls.Basic
Window {
visible: true
width: 320
height: 180
title: qsTr("Hello World")
color: '#222222'
TableView {
id: tableView
columnWidthProvider: function (column) { return 100; }
rowHeightProvider: function (column) { return 40; }
anchors.fill: parent
model: tableModel
delegate: Rectangle {
implicitWidth: 100
implicitHeight: 40
color: (index % 2 === 0) ? "#FFFFFF" : "#F9F9F9"
border.width: 1
border.color: "lightgray"
Text {
text: display
anchors.fill: parent
anchors.margins: 5
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
TableView.editDelegate: TextField {
anchors.fill: parent
text: display
horizontalAlignment: TextInput.AlignHCenter
verticalAlignment: TextInput.AlignVCenter
TableView.onCommit: {
tableManager.updateValue(row, column, text)
}
}
}
}
}