There is no API to insert a checkbox into the header of a QTableView. The recommended way of achieving this is to subclass the QHeaderView and draw the checkbox in the paintSection() method which is in my opinion overkill for such a simple feature.
In this post, I show how to achieve a similar effect by simply adding icons in the header. The desired effect is shown below:
Code description
First, a table model is created by subclassing the QAbstractTableModel. The data for the table is initialised in self._array (which is a numpy array in this example) and self.test holds the check states for each row in the data. if it was not obvious self.header_icon will hold the current icon in the header. The current icon is determined by the setHeaderIcon() method.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class NumpyModel(QtCore.QAbstractTableModel):
def __init__(self, narray, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self._array = narray.point
self.test = narray.enabled
self.header_icon = None
self.setHeaderIcon()
The QAbstractTableModel.headerData() method is overloaded and the checkbox icon is drawn into the horizontal header of the table.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def headerData(self, index, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DecorationRole:
if index == 3:
return QtCore.QVariant(QtGui.QPixmap(self.header_icon).scaled(100, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation))
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
if index != 3:
return QtCore.QVariant(index+1)
return QtCore.QVariant()
In the setHeaderIcon() method, the header icon is set depending on the states of the checkboxes in the columns. After changing the icon, a headerDataChanged signal is emitted to notify the table view that the header has been changed without this line the header icon will not be updated.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def setHeaderIcon(self):
if numpy.all(self.test == True):
self.header_icon = 'checked.png'
elif numpy.all(self.test == False):
self.header_icon = 'unchecked.png'
else:
self.header_icon = 'intermediate.png'
self.headerDataChanged.emit(Qt.Horizontal, 3, 3)
The icons used are checked.png (left), intermediate.png (middle) and unchecked.png (right).
The toggleCheckState() method toggles the check state of the data. The dataChanged signal is emitted to notify the table that the data has changed and the header is updated by calling setHeaderIcon() method.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def toggleCheckState(self, index):
if index == 3:
if numpy.all(self.test == False):
self.test.fill(True)
else:
self.test.fill(False)
topLeft =self.index(0, 3)
bottomRight = self.index(self.rowCount(), 3)
self.dataChanged.emit(topLeft, bottomRight)
self.setHeaderIcon()
In the main script, a table view is created with a NumpyModel object set as its model. Finally, the sectionPressed signal of the table view header is connected to the toggleState() method of the model, this ensures that clicking on the header changed the data's check state.
The complete python code is available below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy
from PyQt5 import QtCore, QtWidgets, QtGui
Qt = QtCore.Qt
class NumpyModel(QtCore.QAbstractTableModel):
def __init__(self, narray, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self._array = narray.point
self.test = narray.enabled
self.header_icon = None
self.setHeaderIcon()
def rowCount(self, _parent=None):
return self._array.shape[0]
def columnCount(self, _parent=None):
return self._array.shape[1] + 1
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
if (index.column() == 3):
value = ''
else:
value = QtCore.QVariant(
"%.5f" % self._array[index.row(), index.column()])
if role == QtCore.Qt.EditRole:
return value
elif role == QtCore.Qt.DisplayRole:
return value
elif role == QtCore.Qt.CheckStateRole:
if index.column() == 3:
if self.test[index.row()]:
return QtCore.Qt.Checked
else:
return QtCore.Qt.Unchecked
elif role == Qt.TextAlignmentRole:
return Qt.AlignCenter
return QtCore.QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return False
if role == Qt.CheckStateRole and index.column() == 3:
if value == Qt.Checked:
self.test[index.row()] = True
else:
self.test[index.row()] = False
self.setHeaderIcon()
elif role == Qt.EditRole and index.column() != 3:
row = index.row()
col = index.column()
if value.isdigit():
self._array[row, col] = value
return True
def flags(self, index):
if not index.isValid():
return None
if index.column() == 3:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
else:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
def headerData(self, index, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DecorationRole:
if index == 3:
return QtCore.QVariant(QtGui.QPixmap(self.header_icon).scaled(100, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation))
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
if index != 3:
return QtCore.QVariant(index+1)
return QtCore.QVariant()
def toggleCheckState(self, index):
if index == 3:
if numpy.all(self.test == False):
self.test.fill(True)
else:
self.test.fill(False)
topLeft =self.index(0, 3)
bottomRight = self.index(self.rowCount(), 3)
self.dataChanged.emit(topLeft, bottomRight)
self.setHeaderIcon()
def setHeaderIcon(self):
if numpy.all(self.test == True):
self.header_icon = 'checked.png'
elif numpy.all(self.test == False):
self.header_icon = 'unchecked.png'
else:
self.header_icon = 'intermediate.png'
self.headerDataChanged.emit(Qt.Horizontal, 3, 3)
if __name__ == "__main__":
a = QtWidgets.QApplication([])
w = QtWidgets.QTableView()
d = numpy.rec.array([([1., 2., 3.], True), ([4., 5., 6.], False), ([7., 8., 9.], True)],
dtype=[('point', 'f4', 3), ('enabled', '?')])
m = NumpyModel(d)
w.setModel(m)
w.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
w.setAlternatingRowColors(True)
w.verticalHeader().setVisible(False)
w.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
w.horizontalHeader().setSectionResizeMode(3, QtWidgets.QHeaderView.Fixed)
w.horizontalHeader().setMinimumSectionSize(40)
w.horizontalHeader().setDefaultSectionSize(40)
header = w.horizontalHeader()
header.sectionPressed.connect(m.toggleCheckState)
w.show()
a.exec_()
This comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteGood night my friend. I managed to convert your code to PyQt6, but the checkbox in rows didn't accept being selected, just deselected.
ReplyDeleteThis worked for me
Deletehttps://gist.github.com/StephenNneji/dd94418ecef749525e6251e37c38f25a