Agregar checkBox como encabezado vertical en QtableView

Estoy tratando de tener una QTableView de casillas de verificación, así que puedo usarlas para selecciones de filas … Lo he logrado, ahora quiero que el encabezado en sí sea una checkbox para que pueda marcar / Desmarcar todas o cualquier fila. He estado buscando durante días, pero no pude hacerlo.

Intenté usar setHeaderData para el modelo, pero no pude hacerlo. Cualquier ayuda sería apreciada.

No estaba particularmente contento con la versión C ++ que @tmoreau portó a Python, ya que no:

  • manejar más de una columna
  • manejar alturas de encabezado personalizadas (por ejemplo, texto de encabezado multilínea)
  • usar una checkbox de tres estados
  • trabajar con la clasificación

Así que solucioné todos esos problemas y creé un ejemplo con un QStandardItemModel que generalmente recomendaría tratar de crear su propio modelo basado en QAbstractTableModel .

Probablemente todavía haya algunas imperfecciones, así que doy la bienvenida a las sugerencias sobre cómo mejorarlo.

 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.restre() # 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_()) 

Tuve el mismo problema, y ​​encuentro una solución aquí , en C ++. No hay una solución fácil, tienes que crear tu propio encabezado.

Aquí está mi código completo con PyQt4. Parece funcionar con Python2 y Python3.

También implementé la funcionalidad seleccionar todo / seleccionar ninguno.

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

NB: Podrías almacenar las filas en self.checkList . En mi caso, a menudo tengo que eliminar filas en posición aleatoria para que no sea suficiente.