pythonpyqtpyqt5qitemdelegate

Add a checkbox to text in a QTableView cell using delegate


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

enter image description here

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())

Solution

  • 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