1

I wish to add a checkbox to a cell with text in my QTableView:

  • when a cell is not activated we should see a text and a greyed out checkbox, like this

enter image description here

  • when we open a delegate editor, we should be able to edit text and to change state of a checkbox

enter image description here

Just for info - further I was planning to do next: when a checkbox is activated - a cell will reject signal for updating text in it, and if checkbox is unchecked - text will be updated when special signal is emitted.

I understand that I should subclass QStyledItemDelegate and re-implement paint method, but as far as I am a beginner I found it difficult to figure out this task.

You can use this example to write and test a code:

from PyQt5 import QtCore, QtGui, QtWidgets
import sys
import pickle

class Mainwindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QtWidgets.QTableView()
        self.setCentralWidget(self.table)

        self.list = ['item_1', 'item_2','item_3']
        self.data = [
            [1, 'Blocks γ=500 GOST 31359-2007', self.list[0], 0.18, 0.22],
            [2, 'Blocks γ=600 GOST 31359-2008', self.list[0], 0.25, 0.27],
            [3, 'Insulation', self.list[0], 0.041, 0.042],
            [3, 'Insulation', self.list[0], 0.041, 0.042]
            ]

        self.model = Materials(self.data)
        self.table.setModel(self.model)

        self.table.setSelectionBehavior(self.table.SelectRows)
        self.table.setSelectionMode(self.table.SingleSelection)


        for row in range(len(self.model.materials)):
            index = self.table.model().index(row,2)
            self.table.setIndexWidget(index, self.setting_combobox(index))



    def setting_combobox(self, index):
        widget = QtWidgets.QComboBox()
        list = self.list
        widget.addItems(list)
        widget.setCurrentIndex(0)
        widget.currentTextChanged.connect(lambda value: self.model.setData(index, value))
        return widget


class Materials(QtCore.QAbstractTableModel):

    def __init__(self, materials = [[]], parent = None):
        super(Materials, self).__init__()
        self.materials = materials


    def rowCount(self, parent):
        return len(self.materials)

    def columnCount(self, parent):
        return len(self.materials[0])

    def data(self, index, role):

        if role == QtCore.Qt.DisplayRole:
            row = index.row()
            column = index.column()
            value = self.materials[row][column]
            return value

        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            value = self.materials[row][column]
            return value

        if role == QtCore.Qt.FontRole:
            if index.column() == 0:
                boldfont = QtGui.QFont()
                boldfont.setBold(True)
                return boldfont

    def setData(self, index, value, role = QtCore.Qt.EditRole):

        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            self.materials[row][column] = value
            self.dataChanged.emit(index, index)
            return True
        return False


    def flags(self, index):
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    application = Mainwindow()
    application.show()


    sys.exit(app.exec())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Nasty_gri
  • 83
  • 7

1 Answers1

2

A possible solution is to enable the role Qt.CheckStateRole and modify the position of the checkbox using the delegate. For this, the model must also be modified to store the state of the checkbox.

import sys

from PyQt5 import QtCore, QtGui, QtWidgets


class CustomDelegate(QtWidgets.QStyledItemDelegate):
    def initStyleOption(self, option, index):
        value = index.data(QtCore.Qt.CheckStateRole)
        if value is None:
            model = index.model()
            model.setData(index, QtCore.Qt.Unchecked, QtCore.Qt.CheckStateRole)
        super().initStyleOption(option, index)
        option.direction = QtCore.Qt.RightToLeft
        option.displayAlignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter


class Mainwindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QtWidgets.QTableView()
        self.setCentralWidget(self.table)

        self.list = ["item_1", "item_2", "item_3"]
        self.data = [
            [1, "Blocks γ=500 GOST 31359-2007", self.list[0], 0.18, 0.22],
            [2, "Blocks γ=600 GOST 31359-2008", self.list[0], 0.25, 0.27],
            [3, "Insulation", self.list[0], 0.041, 0.042],
            [3, "Insulation", self.list[0], 0.041, 0.042],
        ]

        self.model = Materials(self.data)
        self.table.setModel(self.model)

        self.table.setSelectionBehavior(self.table.SelectRows)
        self.table.setSelectionMode(self.table.SingleSelection)

        for row in range(len(self.model.materials)):
            index = self.table.model().index(row, 2)
            self.table.setIndexWidget(index, self.setting_combobox(index))

        delegate = CustomDelegate(self.table)
        self.table.setItemDelegateForColumn(4, delegate)

        self.resize(640, 480)

    def setting_combobox(self, index):
        widget = QtWidgets.QComboBox()
        list = self.list
        widget.addItems(list)
        widget.setCurrentIndex(0)
        widget.currentTextChanged.connect(
            lambda value: self.model.setData(index, value)
        )
        return widget


class Materials(QtCore.QAbstractTableModel):
    def __init__(self, materials=[[]], parent=None):
        super(Materials, self).__init__()
        self.materials = materials

        self.check_states = dict()

    def rowCount(self, parent):
        return len(self.materials)

    def columnCount(self, parent):
        return len(self.materials[0])

    def data(self, index, role):

        if role == QtCore.Qt.DisplayRole:
            row = index.row()
            column = index.column()
            value = self.materials[row][column]
            return value

        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            value = self.materials[row][column]
            return value

        if role == QtCore.Qt.FontRole:
            if index.column() == 0:
                boldfont = QtGui.QFont()
                boldfont.setBold(True)
                return boldfont

        if role == QtCore.Qt.CheckStateRole:
            value = self.check_states.get(QtCore.QPersistentModelIndex(index))
            if value is not None:
                return value

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if role == QtCore.Qt.EditRole:
            row = index.row()
            column = index.column()
            self.materials[row][column] = value
            self.dataChanged.emit(index, index, (role,))
            return True
        if role == QtCore.Qt.CheckStateRole:
            self.check_states[QtCore.QPersistentModelIndex(index)] = value
            self.dataChanged.emit(index, index, (role,))
            return True
        return False

    def flags(self, index):
        return (
            QtCore.Qt.ItemIsEditable
            | QtCore.Qt.ItemIsEnabled
            | QtCore.Qt.ItemIsSelectable
            | QtCore.Qt.ItemIsUserCheckable
        )


if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    application = Mainwindow()
    application.show()

    sys.exit(app.exec())

enter image description here

eyllanesc
  • 235,170
  • 19
  • 170
  • 241