Checkboxes in Table Views with a Custom Model

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

I have a QTableView with a custom QAbstractTableModel, and I want to add a column of checkboxes. Should I create a custom delegate class for the checkbox, or is there a simpler way to do this?

You can use a custom delegate to draw a checkbox widget, but you don't have to. Qt provides a built-in mechanism for this: Qt.CheckStateRole. By returning Qt.Checked or Qt.Unchecked from your model's data() method, Qt will render a checkbox automatically — no delegate required.

Let's walk through how this works, starting with a simple display and then adding interactivity.

Displaying checkboxes using Qt.CheckStateRole

The simplest way to add checkboxes to a QTableView is to handle Qt.CheckStateRole in your model's data() method. When Qt asks your model for data with this role, returning Qt.Checked or Qt.Unchecked tells Qt to draw a checkbox in that cell.

Here's a minimal example that shows a checked checkbox in every cell:

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 produces a table where every cell has both text and a checked checkbox:

Checkboxes displayed as checked in a QTableView using Qt.CheckStateRole

In a real application, you would return Qt.Checked or Qt.Unchecked based on actual boolean values in your data. You might also restrict checkboxes to a specific column — for example, one that holds True/False values — rather than showing them everywhere.

Making checkboxes toggleable

Displaying checkboxes is a good start, but users will expect to be able to click them. To make checkboxes interactive, you need three things:

  1. A data store for the check state — a list (or column) that tracks which items are checked.
  2. Qt.ItemIsUserCheckable returned from flags() — this tells Qt that the cell supports toggling.
  3. A setData() implementation for Qt.CheckStateRole — this stores the updated state when the user clicks a checkbox.

Let's put all of this together in a complete example.

python
import sys

from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.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.ItemDataRole.DisplayRole:
            value = self._data[index.row()][index.column()]
            return str(value)

        if role == Qt.ItemDataRole.CheckStateRole:
            checked = self._checked[index.row()][index.column()]
            if checked:
                return Qt.CheckState.Checked
            return Qt.CheckState.Unchecked

    def setData(self, index, value, role):
        if role == Qt.ItemDataRole.CheckStateRole:
            checked = value == Qt.CheckState.Checked.value
            self._checked[index.row()][index.column()] = checked
            self.dataChanged.emit(index, index, [role])
            return True
        return False

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return len(self._data[0])

    def flags(self, index):
        return (
            Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.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()

Run this and you'll see a table with checkboxes next to every value. Clicking any checkbox toggles it on and off, and the underlying checked list is updated accordingly.

Let's look at what each piece is doing.

Storing check state separately

The checked list mirrors the structure of the data list — each cell has a corresponding True or False value. This keeps the boolean state cleanly separated from your display data, though you could just as easily store it in the same data structure.

Returning the check state in data()

When Qt asks for Qt.ItemDataRole.CheckStateRole, we look up the boolean value for that cell and return either Qt.CheckState.Checked or Qt.CheckState.Unchecked:

python
if role == Qt.ItemDataRole.CheckStateRole:
    checked = self._checked[index.row()][index.column()]
    if checked:
        return Qt.CheckState.Checked
    return Qt.CheckState.Unchecked

Handling user clicks in setData()

When the user clicks a checkbox, Qt calls setData() with the new value and the Qt.ItemDataRole.CheckStateRole role. We compare the incoming value to Qt.CheckState.Checked.value to determine whether the box was checked or unchecked, then store the result:

python
def setData(self, index, value, role):
    if role == Qt.ItemDataRole.CheckStateRole:
        checked = value == Qt.CheckState.Checked.value
        self._checked[index.row()][index.column()] = checked
        self.dataChanged.emit(index, index, [role])
        return True
    return False

Notice the self.dataChanged.emit(...) call — this notifies the view that the data has changed so it can redraw the cell. Always emit this signal when you modify data in setData().

Enabling user interaction with flags()

The flags() method tells Qt what the user can do with each cell. Including Qt.ItemFlag.ItemIsUserCheckable is what makes the checkbox clickable:

python
def flags(self, index):
    return (
        Qt.ItemFlag.ItemIsSelectable
        | Qt.ItemFlag.ItemIsEnabled
        | Qt.ItemFlag.ItemIsUserCheckable
    )

Without this flag, the checkbox will still appear (because you're returning data for CheckStateRole), but the user won't be able to toggle it.

Showing checkboxes in only one column

In many applications, you only want checkboxes in a specific column. You can achieve this by checking index.column() in your data() and flags() methods. For example, to show checkboxes only in column 2:

python
def data(self, index, role):
    if role == Qt.ItemDataRole.DisplayRole:
        value = self._data[index.row()][index.column()]
        return str(value)

    if role == Qt.ItemDataRole.CheckStateRole:
        if index.column() == 2:
            checked = self._checked[index.row()]
            if checked:
                return Qt.CheckState.Checked
            return Qt.CheckState.Unchecked

def flags(self, index):
    flags = Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled
    if index.column() == 2:
        flags |= Qt.ItemFlag.ItemIsUserCheckable
    return flags

In this case, self._checked would be a simple one-dimensional list (one boolean per row) rather than a 2D list.

Summary

To add checkboxes to a QTableView with a custom QAbstractTableModel:

  • Handle Qt.ItemDataRole.CheckStateRole in data() to display checkboxes based on boolean values.
  • Return Qt.ItemFlag.ItemIsUserCheckable from flags() to make checkboxes interactive.
  • Implement setData() for Qt.ItemDataRole.CheckStateRole to store the updated state when the user clicks, and emit dataChanged to keep the view in sync.

This approach works natively with Qt's model/view architecture and avoids the complexity of writing a custom delegate. For a more complete guide to displaying data in table views — including using numpy and pandas data sources — see our QTableView with ModelViews tutorial. If you want to show only an icon without text in specific cells, see how to show only an icon in a QTableView cell. You can also learn how to create your own custom widgets for more advanced UI needs.

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

PyQt/PySide Office Hours 1:1 with Martin Fitzpatrick

Save yourself time and frustration. Get one on one help with your projects. Bring issues, bugs and questions about usability to architecture and maintainability, and leave with solutions.

Book Now 60 mins ($195)

Martin Fitzpatrick

Checkboxes in Table Views with a 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.