在QtableView中将checkBox添加为垂直标题

时间:2015-06-19 07:36:22

标签: python pyqt qtableview

我正在尝试使用QTableView的复选框,因此我可以将它们用于行选择...我已设法做到这一点,现在我想要标题本身是复选框所以我可以检查/取消选中全部或任何行。我一直在寻找好几天,但却无法做到。

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

2 个答案:

答案 0 :(得分:2)

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

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

所以我修复了所有这些问题,并创建了一个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_())

答案 1 :(得分:0)

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

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

我还实现了select all / select none功能。

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中。在我的情况下,我经常要删除随机位置的行,所以这还不够。