Adding custom QHeaderView upon request
-
wrote on 25 Jan 2021, 20:28 last edited by Oak77
Hello Qt community,
I used this example (as many others) to learn and elaborate on
QHeaderVivew
fitlers, subclassing and chopping the code.
I've been trying to resolve an issue with inability to update theQHeaderView
for over two weeks, so I tried (my best) to produce a Minimal Usable Code:import sys from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QStandardItemModel from PyQt5.QtWidgets import QHeaderView, QWidget, QLineEdit, QApplication, QTableView, QVBoxLayout, QHBoxLayout, QPushButton class FilterHeader(QHeaderView): filterActivated = pyqtSignal() def __init__(self, parent): super().__init__(Qt.Horizontal, parent) self._editors = [] self._padding = 4 self.setStretchLastSection(True) self.setDefaultAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.setSortIndicatorShown(False) self.sectionResized.connect(self.adjustPositions) parent.horizontalScrollBar().valueChanged.connect(self.adjustPositions) def setFilterBoxes(self, count): while self._editors: editor = self._editors.pop() editor.deleteLater() for index in range(count): editor = self.create_editor(self.parent(), index) self._editors.append(editor) self.adjustPositions() def create_editor(self, parent, index): if index == 1: # Empty editor = QWidget() else: editor = QLineEdit(parent) editor.setPlaceholderText("Filter") editor.returnPressed.connect(self.filterActivated) return editor def sizeHint(self): size = super().sizeHint() if self._editors: height = self._editors[0].sizeHint().height() size.setHeight(size.height() + height + self._padding) return size def updateGeometries(self): if self._editors: height = self._editors[0].sizeHint().height() self.setViewportMargins(0, 0, 0, height + self._padding) else: self.setViewportMargins(0, 0, 0, 0) super().updateGeometries() self.adjustPositions() def adjustPositions(self): for index, editor in enumerate(self._editors): if not isinstance(editor, QWidget): continue height = editor.sizeHint().height() compensate_y = 0 compensate_x = 0 editor.move( self.sectionPosition(index) - self.offset() + 1 + compensate_x, height + (self._padding // 2) + 2 + compensate_y, ) editor.resize(self.sectionSize(index), height) def filterText(self, index): for editor in self._editors: if hasattr(editor, "text") and callable(editor.text): return editor.texts() return "" def setFilterText(self, index, text): for editor in self._editors: if hasattr(editor, "setText") and callable(editor.setText): editor.setText(text) def clearFilters(self): for editor in self._editors: editor.clear() class Window(QWidget): def __init__(self): super(Window, self).__init__() self.panel = QWidget() self.panel.setFixedHeight(42) btn = QPushButton() btn.setText("Set table headers") btn.clicked.connect(self.SetThemUp) lay = QHBoxLayout() lay.addWidget(btn) self.panel.setLayout(lay) self.view = QTableView() layout = QVBoxLayout(self) layout.addWidget(self.panel) layout.addWidget(self.view) header = FilterHeader(self.view) print("header instance (init) = " + str(header)) self.view.setHorizontalHeader(header) model = QStandardItemModel(self.view) model.setHorizontalHeaderLabels("One Two Three Four Five Six Seven".split()) self.view.setModel(model) self.SetThemUp() header.filterActivated.connect(self.handleFilterActivated) def handleFilterActivated(self): print("handleFilterActivated!") def SetThemUp(self): print("setThemUp........") header = self.view.horizontalHeader() print("header instance (SetThemUp) = " + str(header)) header.setFilterBoxes(6) if __name__ == "__main__": app = QApplication(sys.argv) window = Window() window.setGeometry(800, 100, 600, 300) window.show() sys.exit(app.exec_())
If you fire the code as-is, it will show a
QTableView
with filter headers on all columns but 2nd. The desired behaviour is to comment out line No. 109 (self.SetThemUp()
) and invoke the headers by clicking on thebtn1
, that in turns runsself.SetThemUp()
.The instances seem to be same - in the console I printed and can see identical memory addresses from the initiation, calling by default
__init__
method and fromself.SetThemUp()
method. However, in my application it would only show empty header. In this MUC it doesn't work at all. In either case I don't get any errors or warnings.I suspect it's the inheritance and instances of the classes that needs to be tweaked, but I'm not experienced enough to see through this... I hope someone could point me into right direction? Or could someone advice how can I further debug it?
EDIT:
I still have no solution to this problem, the filter widgets don't get drawn when called from the button click, while they're drawn fine when the same function is called from__init__
. I broke down every piece of code and made sure that:- I get everywhere same instances of objects, nothing is messes between the two ways of running
setThemUp()
- filter widgets have proper sizes and positions
- filter objects are not hidden behind the
QTableView
(checked by hidingQTableView
) - I added numerous logging prints in the functions (
create_editor()
,adjustPosition()
,updateGeometries()
, atd.), they behave absolutely identically.
- I get everywhere same instances of objects, nothing is messes between the two ways of running
-
wrote on 30 Jan 2021, 02:16 last edited by
Hundreds of mods and testing cycles later, I found a solution:
def setFilterBoxes(self, count): while self._editors: editor = self._editors.pop() editor.deleteLater() for index in range(count): editor = self.create_editor(self.parent(), index) self._editors.append(editor) editor.show() # <<<<<<<<<<<<<<<<<<<<<<<<< self.adjustPositions()
To my best undestanding, Ekhumoro is drawing the filter widgets directly to the
window
, since there are no layouts to add widgets (or widgets to add layouts to add widgets) withinQHeaderView
.When they are created from the
__init__
function, they got displayed by the finalwindow.show()
. But when they are added later on demand by button click, they stay hidden. Addingeditor.show()
into the function displaying the filter widgets resolves the issue.
1/2