T Carson wrote
I found some code with an example of an abstract table model. I got it running with Pyside2 and now I want to add a column of checkboxes. I see where the combo box is made in the AssetDelegate class. Should I make a similar class for a checkbox or somehow add to this class?
import sys
from PySide2 import QtGui, QtCore, QtWidgets
class AssetDelegate(QtWidgets.QStyledItemDelegate):
def paint(self, painter, option, index):
if isinstance(self.parent(), QtWidgets.QAbstractItemView):
self.parent().openPersistentEditor(index)
QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)
def createEditor(self, parent, option, index):
combobox = QtWidgets.QComboBox(parent)
combobox.addItems(index.data(AssetModel.ItemsRole))
combobox.currentIndexChanged.connect(self.onCurrentIndexChanged)
return combobox
def onCurrentIndexChanged(self, ix):
editor = self.sender()
self.commitData.emit(editor)
self.closeEditor.emit(editor, QtWidgets.QAbstractItemDelegate.NoHint)
def setEditorData(self, editor, index):
ix = index.data(AssetModel.ActiveRole)
editor.setCurrentIndex(ix)
def setModelData(self, editor, model, index):
ix = editor.currentIndex()
model.setData(index, ix, AssetModel.ActiveRole)
class Asset(object):
def __init__(self, name, items=[], active=0):
self.active = active
self.name = name
self.items = items
@property
def status(self):
return self.active == len(self.items) - 1
class AssetModel(QtCore.QAbstractTableModel):
attr = ["Name", "Options", "Extra"]
ItemsRole = QtCore.Qt.UserRole + 1
ActiveRole = QtCore.Qt.UserRole + 2
def __init__(self, *args, **kwargs):
QtCore.QAbstractTableModel.__init__(self, *args, **kwargs)
self._items = []
def flags(self, index):
fl = QtCore.QAbstractTableModel.flags(self, index)
if index.column() == 1:
fl |= QtCore.Qt.ItemIsEditable
return fl
def clear(self):
self.beginResetModel()
self._items = []
self.endResetModel()
def rowCount(self, index=QtCore.QModelIndex()):
return len(self._items)
def columnCount(self, index=QtCore.QModelIndex()):
return len(self.attr)
def addItem(self, sbsFileObject):
self.beginInsertRows(QtCore.QModelIndex(),
self.rowCount(), self.rowCount())
self._items.append(sbsFileObject)
self.endInsertRows()
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return AssetModel.attr[section]
return QtCore.QAbstractTableModel.headerData(self, section, orientation, role)
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
if 0 <= index.row() < self.rowCount():
item = self._items[index.row()]
col = index.column()
if role == AssetModel.ItemsRole:
return getattr(item, 'items')
if role == AssetModel.ActiveRole:
return getattr(item, 'active')
if 0 <= col < self.columnCount():
if role == QtCore.Qt.DisplayRole:
if col == 0:
return getattr(item, 'name', '')
if col == 1:
return getattr(item, 'items')[getattr(item, 'active')]
elif role == QtCore.Qt.DecorationRole:
if col == 0:
status = getattr(item, 'status')
col = QtGui.QColor(QtCore.Qt.red) if status else QtGui.QColor(
QtCore.Qt.green)
px = QtGui.QPixmap(120, 120)
px.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(px)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
px_size = px.rect().adjusted(12, 12, -12, -12)
painter.setBrush(col)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 4,
QtCore.Qt.SolidLine,
QtCore.Qt.RoundCap,
QtCore.Qt.RoundJoin))
painter.drawEllipse(px_size)
painter.end()
return QtGui.QIcon(px)
def setData(self, index, value, role=QtCore.Qt.EditRole):
if 0 <= index.row() < self.rowCount():
item = self._items[index.row()]
if role == AssetModel.ActiveRole:
setattr(item, 'active', value)
return True
return QtCore.QAbstractTableModel.setData(self, index, value, role)
class Example(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.resize(400, 300)
# controls
asset_model = QtCore.QSortFilterProxyModel()
asset_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
asset_model.setSourceModel(AssetModel())
self.ui_assets = QtWidgets.QTableView()
self.ui_assets.setEditTriggers(
QtWidgets.QAbstractItemView.NoEditTriggers)
self.ui_assets.setModel(asset_model)
self.ui_assets.verticalHeader().hide()
self.ui_assets.setItemDelegateForColumn(1, AssetDelegate(self.ui_assets))
main_layout = QtWidgets.QVBoxLayout()
main_layout.addWidget(self.ui_assets)
self.setLayout(main_layout)
self.unit_test()
def unit_test(self):
assets = [
Asset('Dev1', ['v01', 'v02', 'v03'], 0),
Asset('Dev2', ['v10', 'v11', 'v13'], 1),
Asset('Dev3', ['v11', 'v22', 'v53'], 2),
Asset('Dev4', ['v13', 'v21', 'v23'], 0)
]
self.ui_assets.model().sourceModel().clear()
for i, obj in enumerate(assets):
self.ui_assets.model().sourceModel().addItem(obj)
def main():
app = QtWidgets.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Martin Fitzpatrick
You can use a custom delegate to draw the widget if you like, but you don't have to. There is Qt.CheckStateRole
which you can use to display a checkbox depending on whether you return Qt.Checked
or Qt.Unchecked
in your .data
method.
def data(self, index, role):
if role == Qt.DisplayRole:
value = self._data[index.row()][index.column()]
return str(value)
if role == Qt.CheckStateRole:
return Qt.Checked
This shows up like this
If you want to allow the user to toggle the checkboxes you need a few things --
Packaging Python Applications with PyInstaller by Martin Fitzpatrick — This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.
- a data store for the check state
- to return
Qt.ItemIsUserCheckable
from the.flags()
method - to implement the
.setData
method to accept and store the updated data.
The following example implements that. The check state data is stored in a separate table, just for clarity --
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data, checked):
super().__init__()
self._data = data
self._checked = checked
def data(self, index, role):
if role == Qt.DisplayRole:
value = self._data[index.row()][index.column()]
return str(value)
if role == Qt.CheckStateRole:
checked = self._checked[index.row()][index.column()]
return Qt.Checked if checked else Qt.Unchecked
def setData(self, index, value, role):
if role == Qt.CheckStateRole:
checked = value == Qt.Checked
self._checked[index.row()][index.column()] = checked
return True
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
return len(self._data[0])
def flags(self, index):
return Qt.ItemIsSelectable|Qt.ItemIsEnabled|Qt.ItemIsUserCheckable
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
data = [
[1, 9, 2],
[1, 0, -1],
[3, 5, 2],
[3, 3, 2],
[5, 8, 9],
]
checked = [
[True, True, True],
[False, False, False],
[True, False, False],
[True, False, True],
[False, True, True],
]
self.model = TableModel(data, checked)
self.table.setModel(self.model)
self.setCentralWidget(self.table)
app=QtWidgets.QApplication(sys.argv)
window=MainWindow()
window.show()
app.exec_()
If you run this, you'll see you are able to check and toggle the checkboxes next to any value.
Create GUI Applications with Python & Qt6 by Martin Fitzpatrick — (PyQt6 Edition) The hands-on guide to making apps with Python — Over 10,000 copies sold!