Just for a sport of it I am playing around with a demo code from @ekhumoro (all credits for original Qt4 code goes to him), where he inserted a new line of QLineEdit
widgets into QHeaderview
of QTableView
. I ported the code to Qt5 and started to add different widget to the header. No problems with a QComboBox
, QCheckBox
, an empty space (QWidget
) and a QPushButton
.
However, when I created a composed QWidget
containting a QHBoxLayout
with a QPushButton
(it's the one with "=" sign, in column "Three") and a QLineEdit
. All the controls are linked to relevant slots and it runs fine, including the QLineEdit
from the composed field in column Three, but except the QPushButton
from that composed widget. The ChangeIntButtonSymbol(self)
slot def should cycle the button's Text
between <|=|> values. I always get an error:
AttributeError: 'FilterHeader' object has no attribute 'text'
which indicates, that unlike in other cases, here the context of the parent (retrieved by self.sender()
) widget is different, the def received FilterHeader
class as a parent instead the btn
. I tried also passing an argument using lambda:
self.btn.clicked.connect(lambda: self.changebuttonsymbol.emit(self.btn))
...but the result was exactly the same (with different wording in the error).
Clearly, I'm not getting fully the architecture of this QHeaderView
extension and making some basic mistake. Full demo bellow, the problem occures when "=" button is clicked, any solutions or hints appreciated.
import sys
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QHeaderView, QWidget, QLineEdit, QApplication, QTableView, QVBoxLayout,QHBoxLayout, QLineEdit, QComboBox, QPushButton, QCheckBox
from PyQt5.QtCore import pyqtSignal
class FilterHeader(QHeaderView):
filterActivated = QtCore.pyqtSignal()
changebuttonsymbol = QtCore.pyqtSignal()
def __init__(self, parent):
super().__init__(QtCore.Qt.Horizontal, parent)
self._editors = []
self._padding = 4
self.setStretchLastSection(True)
#self.setResizeMode(QHeaderView.Stretch)
self.setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.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):
if index == 1: # Empty
editor = QWidget()
elif index == 2: # Number filter (>|=|<)
editor = QWidget(self.parent())
edlay = QHBoxLayout()
edlay.setContentsMargins(0, 0, 0, 0)
edlay.setSpacing(0)
self.btn = QPushButton()
self.btn.setText("=")
self.btn.setFixedWidth(20)
#self.btn.clicked.connect(lambda: self.changebuttonsymbol.emit(self.btn))
self.btn.clicked.connect(self.changebuttonsymbol.emit)
#btn.setViewportMargins(0, 0, 0, 0)
linee = QLineEdit(self.parent())
linee.setPlaceholderText('Filter')
linee.returnPressed.connect(self.filterActivated.emit)
#linee.setViewportMargins(0, 0, 0, 0)
edlay.addWidget(self.btn)
edlay.addWidget(linee)
editor.setLayout(edlay)
elif index == 3:
editor = QComboBox(self.parent())
editor.addItems(["", "Combo", "One", "Two", "Three"])
editor.currentIndexChanged.connect(self.filterActivated.emit)
elif index == 4:
editor = QPushButton(self.parent())
editor.clicked.connect(self.filterActivated.emit)
editor.setText("Button")
elif index == 5:
editor = QCheckBox(self.parent())
editor.clicked.connect(self.filterActivated.emit)
editor.setTristate(True)
editor.setCheckState(1)
editor.setText("CheckBox")
else: # string filter
editor = QLineEdit(self.parent())
editor.setPlaceholderText('Filter')
editor.returnPressed.connect(self.filterActivated.emit)
self._editors.append(editor)
self.adjustPositions()
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):
height = editor.sizeHint().height()
CompensateY = 0
CompensateX = 0
if self._editors[index].__class__.__name__ == "QComboBox":
CompensateY = +2
elif self._editors[index].__class__.__name__ == "QWidget":
CompensateY = -1
elif self._editors[index].__class__.__name__ == "QPushButton":
CompensateY = -1
elif self._editors[index].__class__.__name__ == "QCheckBox":
CompensateY = 4
CompensateX = 4
editor.move( self.sectionPosition(index) - self.offset() + 1 + CompensateX, height + (self._padding // 2) + 2 + CompensateY)
editor.resize(self.sectionSize(index), height)
def filterText(self, index):
if 0 <= index < len(self._editors):
if self._editors[index].__class__.__name__ == "QLineEdit":
return self._editors[index].text()
return ''
def setFilterText(self, index, text):
if 0 <= index < len(self._editors):
self._editors[index].setText(text)
def clearFilters(self):
for editor in self._editors:
editor.clear()
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.view = QTableView()
layout = QVBoxLayout(self)
layout.addWidget(self.view)
header = FilterHeader(self.view)
self.view.setHorizontalHeader(header)
model = QtGui.QStandardItemModel(self.view)
model.setHorizontalHeaderLabels('One Two Three Four Five Six Seven'.split())
self.view.setModel(model)
header.setFilterBoxes(model.columnCount())
header.filterActivated.connect(self.handleFilterActivated)
header.changebuttonsymbol.connect(self.ChangeIntButtonSymbol)
def handleFilterActivated(self):
header = self.view.horizontalHeader()
for index in range(header.count()):
if index != 4:
print((index, header.filterText(index)))
else:
print("Button")
def ChangeIntButtonSymbol(self):
print("Int button triggered")
nbtn = self.sender()
print(str(nbtn))
if nbtn.text() == "=":
nbtn.setText(">")
elif nbtn.text() == ">":
nbtn.setText("<")
else:
nbtn.setText("=")
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.setGeometry(800, 100, 600, 300)
window.show()
sys.exit(app.exec_())