Checkboxes in Table Views with custom model

Show check boxes for boolean values in PyQt/PySide table views
Heads up! You've already completed this tutorial.

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?

python
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 checkbox 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.

Displaying checkboxes in a QTableView using Qt.CheckStateRole

The simplest way to add checkboxes to a QTableView is to return Qt.Checked or Qt.Unchecked from your model's data() method when the role is Qt.CheckStateRole. No custom delegate is needed for basic checkbox display.

python
    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:

Checkboxes displayed as checked in a QTableView using Qt.CheckStateRole in PyQt5

Making checkboxes toggleable with setData and ItemIsUserCheckable

If you want to allow the user to toggle the checkboxes you need a few things:

  1. A data store for the check state
  2. Return Qt.ItemIsUserCheckable from the .flags() method
  3. Implement the .setData() method to accept and store the updated data

The following complete example implements toggleable checkboxes in a QTableView using a custom QAbstractTableModel. The check state data is stored in a separate table, just for clarity:

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. This approach works for both PyQt5 and PySide2 — just adjust your imports accordingly.

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

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.

Find out More

Martin Fitzpatrick

Checkboxes in Table Views with custom model was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.