Adding checkBox as vertical header in QtableView

前端 未结 2 1126
误落风尘
误落风尘 2021-01-21 16:12

I am trying to have a QTableView of checkboxes, so I can use them for row selections... I have managed to do that, now I want the header Itself to be checkbox so I can check/Unc

2条回答
  •  没有蜡笔的小新
    2021-01-21 16:24

    I wasn't particularly happy with the C++ version that @tmoreau ported to Python as it didn't:

    • handle more than one column
    • handle custom header heights (for example multi-line header text)
    • use a tri-state checkbox
    • work with sorting

    So I fixed all of those issues, and created an example with a QStandardItemModel which I generally would advocate over trying to create your own model based on QAbstractTableModel.

    There are probably still some imperfections, so I welcome suggestions for how to improve it!

    import sys
    from PyQt4 import QtCore, QtGui
    
    
    # A Header supporting checkboxes to the left of the text of a subset of columns
    # The subset of columns is specified by a list of column_indices at 
    # instantiation time
    class CheckBoxHeader(QtGui.QHeaderView):
        clicked=QtCore.pyqtSignal(int, bool)
    
        _x_offset = 3
        _y_offset = 0 # This value is calculated later, based on the height of the paint rect
        _width = 20
        _height = 20
    
        def __init__(self, column_indices, orientation = QtCore.Qt.Horizontal, parent = None):
            super(CheckBoxHeader, self).__init__(orientation, parent)
            self.setResizeMode(QtGui.QHeaderView.Stretch)
            self.setClickable(True)
    
            if isinstance(column_indices, list) or isinstance(column_indices, tuple):
                self.column_indices = column_indices
            elif isinstance(column_indices, (int, long)):
                self.column_indices = [column_indices]
            else:
                raise RuntimeError('column_indices must be a list, tuple or integer')
    
            self.isChecked = {}
            for column in self.column_indices:
                self.isChecked[column] = 0
    
        def paintSection(self, painter, rect, logicalIndex):
            painter.save()
            super(CheckBoxHeader, self).paintSection(painter, rect, logicalIndex)
            painter.restore()
    
            #
            self._y_offset = int((rect.height()-self._width)/2.)
    
            if logicalIndex in self.column_indices:
                option = QtGui.QStyleOptionButton()
                option.rect = QtCore.QRect(rect.x() + self._x_offset, rect.y() + self._y_offset, self._width, self._height)
                option.state = QtGui.QStyle.State_Enabled | QtGui.QStyle.State_Active
                if self.isChecked[logicalIndex] == 2:
                    option.state |= QtGui.QStyle.State_NoChange
                elif self.isChecked[logicalIndex]:
                    option.state |= QtGui.QStyle.State_On
                else:
                    option.state |= QtGui.QStyle.State_Off
    
                self.style().drawControl(QtGui.QStyle.CE_CheckBox,option,painter)
    
        def updateCheckState(self, index, state):
            self.isChecked[index] = state
            self.viewport().update()
    
        def mousePressEvent(self, event):
            index = self.logicalIndexAt(event.pos())
            if 0 <= index < self.count():
                x = self.sectionPosition(index)
                if x + self._x_offset < event.pos().x() < x + self._x_offset + self._width and self._y_offset < event.pos().y() < self._y_offset + self._height:
                    if self.isChecked[index] == 1:
                        self.isChecked[index] = 0
                    else:
                        self.isChecked[index] = 1
    
                    self.clicked.emit(index, self.isChecked[index])
                    self.viewport().update()
                else:
                    super(CheckBoxHeader, self).mousePressEvent(event)
            else:
                super(CheckBoxHeader, self).mousePressEvent(event)
    
    if __name__=='__main__':
    
        def updateModel(index, state):
            for i in range(model.rowCount()):
                item = model.item(i, index)
                item.setCheckState(QtCore.Qt.Checked if state else QtCore.Qt.Unchecked)
    
        def modelChanged():
            for i in range(model.columnCount()):
                checked = 0
                unchecked = 0
                for j in range(model.rowCount()):
                    if model.item(j,i).checkState() == QtCore.Qt.Checked:
                        checked += 1
                    elif model.item(j,i).checkState() == QtCore.Qt.Unchecked:
                        unchecked += 1
    
                if checked and unchecked:
                    header.updateCheckState(i, 2)
                elif checked:
                    header.updateCheckState(i, 1)
                else:
                    header.updateCheckState(i, 0)
    
        app = QtGui.QApplication(sys.argv)
    
        tableView = QtGui.QTableView()
        model = QtGui.QStandardItemModel()
        model.itemChanged.connect(modelChanged)
        model.setHorizontalHeaderLabels(['Title 1\nA Second Line','Title 2'])
        header = CheckBoxHeader([0,1], parent = tableView)
        header.clicked.connect(updateModel)
    
        # populate the models with some items
        for i in range(3):
            item1 = QtGui.QStandardItem('Item %d'%i)
            item1.setCheckable(True)
    
            item2 = QtGui.QStandardItem('Another Checkbox %d'%i)
            item2.setCheckable(True)
    
            model.appendRow([item1, item2])
    
    
        tableView.setModel(model)
        tableView.setHorizontalHeader(header)
        tableView.setSortingEnabled(True)
        tableView.show()
    
        sys.exit(app.exec_())
    

提交回复
热议问题