Adding a checkBox as a vertical header to a QtableView

I'm trying to use QTableView flags, so I can use them to select rows ... I succeeded, now I want the header to be a flag so that I can check / uncheck all or any row, I searched for days, but not could do that.

I tried to use setHeaderData for the model, but could not do it. Any help would be appreciated.

+1
source share
2 answers

I was not particularly pleased with the C ++ version that @tmoreau ported to Python since it "t:

  • handle more than one column
  • handle custom header heights (e.g. multi-line header text)
  • check the tri-state box
  • sorting work

So, I fixed all these problems and created an example with QStandardItemModel , which I would usually protect by trying to create my own model based on QAbstractTableModel .

There are probably some flaws, so I welcome suggestions for improving 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_()) 
+2
source

I had the same problem and find a solution here in C ++. There is no easy solution, you need to create your own header.

Here is my complete code with PyQt4. It works with Python2 and Python3.

I also implemented the select all / select none function.

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

Note. You can save the lines in self.checkList . In my case, I often have to delete rows in a random position so that is not enough.

0
source

All Articles