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 --
PyQt/PySide 1:1 Coaching with Martin Fitzpatrick — Save yourself time and frustration. Get one on one help with your Python GUI projects. Working together with you I'll identify issues and suggest fixes, from bugs and usability to architecture and maintainability.
- a data store for the check state
- to return
Qt.ItemIsUserCheckablefrom the.flags()method - to implement the
.setDatamethod 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 --
Bring Your PyQt/PySide Application to Market — Stuck in development hell? I'll help you get your project focused, finished and released. Benefit from years of practical experience releasing software with Python.
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.