【问题标题】:Adding checkBox as vertical header in QtableView在 QtableView 中添加复选框作为垂直标题
【发布时间】:2015-09-05 02:31:41
【问题描述】:

我正在尝试拥有一个 QTableView 复选框,因此我可以将它们用于行选择...我已经设法做到了,现在我希望标题本身是复选框,以便我可以选中/取消选中全部或任何行.我已经找了好几天了,但没能做到。

我尝试将 setHeaderData 用于模型,但做不到。 任何帮助将不胜感激。

【问题讨论】:

    标签: python pyqt qtableview


    【解决方案1】:

    我对 C++ version 和 @tmoreau ported 对 Python 并不特别满意,因为它没有:

    • 处理多个列
    • 处理自定义标题高度(例如多行标题文本)
    • 使用三态复选框
    • 使用排序

    所以我修复了所有这些问题,并创建了一个带有 QStandardItemModel 的示例,我通常会提倡尝试基于 QAbstractTableModel 创建自己的模型。

    可能还有一些不完善的地方,欢迎提出改进建议!

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

    【讨论】:

    • 不错的改进!我看过QStandardItemModel,但我觉得它不太符合我的目的。我的模型实际上是一个“行对象”列表(用户只能选择行)。有一个“检查列表”对我来说也很方便,可以快速对用户选择的内容进行一些处理。我可以用QStandardItemModel 做这样的事情吗?
    • @tmoreau 你当然可以很容易地建立一个等效的列表,但是没有对数据的pythonic访问(只是我在示例中使用的item()方法)。我喜欢QStandardItem/QStandardItemModel 系统,因为它可以轻松完成具有多个角色的复杂事情(例如文本/背景颜色、复选框、图标和不需要显示的任意自定义数据的角色) .您当然可以使用QStandardItem.setData() 方法将python 对象与item 一起存储,并发挥创意并让python 对象从item 本身中提取数据。
    【解决方案2】:

    我遇到了同样的问题,并在 C++ 中找到了解决方案 here。没有简单的解决方案,您必须创建自己的标题。

    这是我使用 PyQt4 的完整代码。它似乎适用于 Python2 和 Python3。

    我还实现了全选/全选功能。

    import sys
    import signal
    
    #import QT
    from PyQt4 import QtCore,QtGui
    
    #---------------------------------------------------------------------------------------------------------
    # Custom checkbox header
    #---------------------------------------------------------------------------------------------------------
    #Draw a CheckBox to the left of the first column
    #Emit clicked when checked/unchecked
    class CheckBoxHeader(QtGui.QHeaderView):
        clicked=QtCore.pyqtSignal(bool)
    
        def __init__(self,orientation=QtCore.Qt.Horizontal,parent=None):
            super(CheckBoxHeader,self).__init__(orientation,parent)
            self.setResizeMode(QtGui.QHeaderView.Stretch)
            self.isChecked=False
    
        def paintSection(self,painter,rect,logicalIndex):
            painter.save()
            super(CheckBoxHeader,self).paintSection(painter,rect,logicalIndex)
            painter.restore()
            if logicalIndex==0:
                option=QtGui.QStyleOptionButton()
                option.rect= QtCore.QRect(3,1,20,20)  #may have to be adapt
                option.state=QtGui.QStyle.State_Enabled | QtGui.QStyle.State_Active
                if self.isChecked:
                    option.state|=QtGui.QStyle.State_On
                else:
                    option.state|=QtGui.QStyle.State_Off
                self.style().drawControl(QtGui.QStyle.CE_CheckBox,option,painter)
    
        def mousePressEvent(self,event):
            if self.isChecked:
                self.isChecked=False
            else:
                self.isChecked=True
            self.clicked.emit(self.isChecked)
            self.viewport().update()
    
    #---------------------------------------------------------------------------------------------------------
    # Table Model, with checkBoxed on the left
    #---------------------------------------------------------------------------------------------------------
    #On row in the table
    class RowObject(object):
        def __init__(self):
            self.col0="column 0"
            self.col1="column 1"
    
    class Model(QtCore.QAbstractTableModel):
        def __init__(self,parent=None):
            super(Model,self).__init__(parent)
            #Model= list of object
            self.myList=[RowObject(),RowObject()]
            #Keep track of which object are checked
            self.checkList=[]
    
        def rowCount(self,QModelIndex):
            return len(self.myList)
    
        def columnCount(self,QModelIndex):
            return 2
    
        def addOneRow(self,rowObject):
            frow=len(self.myList)
            self.beginInsertRows(QtCore.QModelIndex(),row,row)
            self.myList.append(rowObject)
            self.endInsertRows()
    
        def data(self,index,role):
            row=index.row()
            col=index.column()
            if role==QtCore.Qt.DisplayRole:
                if col==0:
                    return self.myList[row].col0
                if col==1:
                    return self.myList[row].col1
            elif role==QtCore.Qt.CheckStateRole:
                if col==0:
                    if self.myList[row] in self.checkList:
                        return QtCore.Qt.Checked
                    else:
                        return QtCore.Qt.Unchecked
    
        def setData(self,index,value,role):
            row=index.row()
            col=index.column()
            if role==QtCore.Qt.CheckStateRole and col==0:
                rowObject=self.myList[row]
                if rowObject in self.checkList:
                    self.checkList.remove(rowObject)
                else:
                    self.checkList.append(rowObject)
                index=self.index(row,col+1)
                self.dataChanged.emit(index,index)  
            return True
    
        def flags(self,index):
            if index.column()==0:
                return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable
            return QtCore.Qt.ItemIsEnabled
    
        def headerData(self,section,orientation,role):
            if role==QtCore.Qt.DisplayRole:
                if orientation==QtCore.Qt.Horizontal:
                    if section==0:
                        return "Title 1"
                    elif section==1:
                        return "Title 2"
    
        def headerClick(self,isCheck):
            self.beginResetModel()
            if isCheck:
                self.checkList=self.myList[:]
            else:
                self.checkList=[]
            self.endResetModel()
    
    if __name__=='__main__':
        app=QtGui.QApplication(sys.argv)
    
        #to be able to close with ctrl+c
        signal.signal(signal.SIGINT, signal.SIG_DFL)
    
        tableView=QtGui.QTableView()
        model=Model(parent=tableView)
        header=CheckBoxHeader(parent=tableView)
        header.clicked.connect(model.headerClick)
    
        tableView.setModel(model)
        tableView.setHorizontalHeader(header)
        tableView.show()
    
        sys.exit(app.exec_())
    

    注意:您可以将行存储在 self.checkList 中。就我而言,我经常不得不删除随机位置的行,所以这还不够。

    【讨论】:

      猜你喜欢
      • 2017-05-08
      • 1970-01-01
      • 1970-01-01
      • 2016-12-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-14
      • 1970-01-01
      相关资源
      最近更新 更多