Add Button to a Table Cell Under Certain Conditions
-
wrote on 6 Aug 2021, 21:23 last edited by
I barely managed to put together a TableView using a TabelModel class that I implemented with Python3. This is a table that would be dynamically instantiated in the future, but for testing purposes I manually added 3 entries in the main method. My goal is to have the cells in the last column be either filled with a button or just be blank depending on the boolean in the last parameter field "Value" So if true, then there would be a button there and if false, just an empty cell. I am not sure how to go about modifying my QML and Python to include this functionality. Any suggestions/thoughts would be appreciated.
view.qml:
import QtQuick 2.12 import QtQuick.Controls.Material 2.15 import QtQuick.Controls 2.15 Item{ width: 500 height: 500 TableView { id: tableView anchors.fill: parent topMargin: columnsHeader.implicitHeight rowSpacing: 5 columnWidthProvider: function (column) { return 50; } clip: true model: tableModel delegate: Rectangle { implicitHeight: 40 implicitWidth: 40 color: "green" Text { anchors.centerIn: parent text: display color: "#ffffff" } } } Row { id: columnsHeader y: -(tableView.contentY + implicitHeight) Repeater { model: tableView.columns > 0 ? tableView.columns : 1 Label { width: tableView.columnWidthProvider(modelData) height: 35 text: tableModel.headerData(modelData, Qt.Horizontal) color: '#000000' font.pixelSize: 15 padding: 10 verticalAlignment: Text.AlignVCenter background: Rectangle { color: "#ffffff" } } } } }
import os import sys from pathlib import Path from PySide6.QtQuick import QQuickView from PySide6.QtCore import QUrl, Qt, QAbstractTableModel, Slot, QModelIndex from PySide6.QtGui import QGuiApplication class TableModel(QAbstractTableModel): def __init__(self, numCols): super().__init__() self.columnHeaders = [''] * numCols self._data = [] self.setHeaderData(0, Qt.Horizontal, "Id", Qt.EditRole) self.setHeaderData(1, Qt.Horizontal, "Name", Qt.EditRole) self.setHeaderData(2, Qt.Horizontal, "Value", Qt.EditRole) def rowCount(self, index): return len(self._data) def columnCount(self, index): # Return length of columnHeaders return len(self.columnHeaders) def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return None if not 0 <= index.row() < len(self._data): return None if role == Qt.DisplayRole: id = self._data[index.row()]["Id"] name = self._data[index.row()]["Name"] value = self._data[index.row()]["Value"] if index.column() == 0: return id elif index.column() == 1: return name elif index.column() == 2: return value return None def setData(self, index, value, role=Qt.EditRole): if role != Qt.EditRole: return False if index.isValid() and 0 <= index.row() < len(self._data): data = self._data[index.row()] if index.column() == 0: data["Id"] = value elif index.column() == 1: data["Name"] = value elif index.column() == 2: # Handle functionality here data["Value"] = value else: return False self.dataChanged.emit(index, index, 0) return True return False @Slot(int, Qt.Orientation, result="QVariant") def headerData(self, section, orientation, role=Qt.DisplayRole): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return self.columnHeaders[section] return None def setHeaderData(self, section, orientation, data, role=Qt.EditRole): if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole): try: self.columnHeaders[section] = data return True except: return False return super().setHeaderData(section, orientation, data, role) # Default insert 1 row per call with rows=1 def insertRows(self, position, rows = 1, index=QModelIndex): self.beginInsertRows(QModelIndex(), position, position + rows - 1) for row in range(rows): self._data.insert(position + row, {"Id": "", "Name": "", "Value": ""}) self.endInsertRows() return True def add_entry(self, id, name, value): row = {"Id": id, "Name": name, "Value": value} self.insertRows(id) # Use index of the newly created row dict entries = ["Id", "Name", "Value"] for idx, entry in enumerate(entries): index = self.index(id, idx, QModelIndex()) self.setData(index, row[entry], Qt.EditRole) def flags(self, index): if not index.isValid(): return Qt.ItemIsEnabled return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable) if __name__ == '__main__': app = QGuiApplication(sys.argv) view = QQuickView() view.setResizeMode(QQuickView.SizeRootObjectToView) tableModel = TableModel(3) tableModel.add_entry(0, "Apple", True) tableModel.add_entry(1, "Pear", True) tableModel.add_entry(2, "Banana", False) view.rootContext().setContextProperty("tableModel", tableModel) qml_file = Path(__file__).parent / "view.qml" view.setSource(QUrl.fromLocalFile(os.fspath(qml_file.resolve()))) if view.status() == QQuickView.Error: sys.exit(-1) view.show() sys.exit(app.exec()) del view
-
wrote on 7 Aug 2021, 19:28 last edited by SeDi 8 Jul 2021, 19:32
Hi,
docs state:In the delegate you have access to the following special properties: [...] styleData.column - the index of the column
You can make this a condition and work with a Loader as delegate.
Untested:
delegate: Loader { sourceComponent: { if (styleData.column === 4) { return value ===true ? buttonComponent : emptyComponent } else { return normalComponent } } Component { id: normalComponent Rectangle { // ... } } Component { id: buttonComponent Button{ // ... } } Component { id: emptyComponent Rectangle { // ...(transparent, empty) } } }
-
Hi,
docs state:In the delegate you have access to the following special properties: [...] styleData.column - the index of the column
You can make this a condition and work with a Loader as delegate.
Untested:
delegate: Loader { sourceComponent: { if (styleData.column === 4) { return value ===true ? buttonComponent : emptyComponent } else { return normalComponent } } Component { id: normalComponent Rectangle { // ... } } Component { id: buttonComponent Button{ // ... } } Component { id: emptyComponent Rectangle { // ...(transparent, empty) } } }
wrote on 9 Aug 2021, 14:28 last edited by@SeDi Thank you for the suggestion and sample code; the only issue that I have now when using it is getting an error that 'styleData' is undefined. I did some more research into that, and it seems to be something that is a part of TableView from Qt Quick Controls 1 which uses an itemDelegate instead of delegate, but I am using Quick Controls 2 in my case. Is using Quick Controls 1 the only way? I also saw this post:
https://stackoverflow.com/a/22924554/16051674
but it is not very clear to me how I could try to apply 'model' or modelData' in my code because I get an undefined error for both.Here is my new TableView component for anyone's reference:
TableView { id: tableView anchors.fill: parent topMargin: columnsHeader.implicitHeight rowSpacing: 5 columnWidthProvider: function (column) { return 50; } clip: true model: tableModel delegate: Loader { sourceComponent: { if (column === 2) { console.log("Model value: " + modelData[row]) return styleData.value ===true ? buttonComponent : emptyComponent } else { return normalComponent } } Component { id: normalComponent Rectangle { implicitHeight: 40 implicitWidth: 40 color: "green" Text { anchors.centerIn: parent text: display color: "#ffffff" } } } Component { id: buttonComponent Button{ implicitHeight: 10 implicitWidth: 10 background: Rectangle { color: "red" } } } Component { id: emptyComponent Rectangle { implicitHeight: 40 implicitWidth: 40 color: "#000000" } } } }
-
wrote on 9 Aug 2021, 14:50 last edited by
If your model isn't a number, nor an JS Array, then you need to use
model
keyword, wheremodel
represents one of the element of your backend model. In this case you shold implementroleNames
method, then you would be able to access data by given role, something likemodel.name
; -
If your model isn't a number, nor an JS Array, then you need to use
model
keyword, wheremodel
represents one of the element of your backend model. In this case you shold implementroleNames
method, then you would be able to access data by given role, something likemodel.name
;wrote on 9 Aug 2021, 15:27 last edited by SCP173 8 Sept 2021, 15:29@IntruderExcluder I see, thank you for the tip. It seems that I am able to just access the roles right now like the default 'display' without needing the 'model' keyword, but I guess once I modify the roles, I should probably add the 'model' keyword in.
Your comment should also technically be part of the solution, but I can only mark 1 post as the answer, so I just want to note that here.
1/5