I would like to display widgets between the QHeaderView
and the rest of the QTableView
, like in the example picture below (created with Photoshop), as this seems like a natural way to enable input for filtering columns.
Asked
Active
Viewed 2,915 times
5

ekhumoro
- 115,249
- 20
- 229
- 336

timmwagener
- 2,368
- 2
- 19
- 27
2 Answers
9
Below is a demo of a FilterHeader
class that I wrote for one of my own projects. You will probably need to adapt it to suit your own needs, but it should already do most what you want. The padding around the filter boxes is unlikely to work the same on all platforms, so you may need to tweak the code in the adjustPositions
method.
import sys
from PyQt4 import QtCore, QtGui
class FilterHeader(QtGui.QHeaderView):
filterActivated = QtCore.pyqtSignal()
def __init__(self, parent):
super().__init__(QtCore.Qt.Horizontal, parent)
self._editors = []
self._padding = 4
self.setStretchLastSection(True)
self.setResizeMode(QtGui.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):
editor = QtGui.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()
editor.move(
self.sectionPosition(index) - self.offset() + 2,
height + (self._padding // 2))
editor.resize(self.sectionSize(index), height)
def filterText(self, index):
if 0 <= index < len(self._editors):
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(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.view = QtGui.QTableView()
layout = QtGui.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'.split())
self.view.setModel(model)
header.setFilterBoxes(model.columnCount())
header.filterActivated.connect(self.handleFilterActivated)
def handleFilterActivated(self):
header = self.view.horizontalHeader()
for index in range(header.count()):
print((index, header.filterText(index)))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 600, 300)
window.show()
sys.exit(app.exec_())

ekhumoro
- 115,249
- 20
- 229
- 336
-
I tested it (with PySide) and it works and is a great start. However, I noticed some _minor_ display annoyances with hoverEvents _(basically simply that the focus vanishes/slightly flickers without an apparent reason when the mouse is over the QLineEdit)_. So I logged the event delivery to the QLineEdits and it seems all events are caught by the FilterView. (Which probably silently distributes it down to the QLineEdits as typing etc. still works). However, it makes me wonder if this is related to the sizeHint() of FilterView, spanning the area of the QLineEdits and if this could cause issues? – timmwagener Jun 07 '17 at 14:32
-
@timmwagener. It all works perfectly fine for me with both PyQt4 and PySide (on Linux). I think the problem must be at your end. There is nothing in the code I posted that would cause the issue you describe. – ekhumoro Jun 07 '17 at 18:11
-
Ok, the issue is happening on Windows 7 with Aero desktop turned on (default). The line edits in the header view are displaying a different highlighting behaviour than line edits added to a normal layout. [Here is a slight mod. of your example to mock it up](https://gist.github.com/timmwagener/58449d508b028f714279c2b4647b60e7). When no Aero desktop is on, there is no highlighting of line edits and so there is no difference visible. My concern is *less* about just visual noise _(although I couldn't use it like that)_, and more about that possibly hinting at some update/paintEvent order issue!? – timmwagener Jun 07 '17 at 19:36
-
@timmwagener. I'm afraid I don't have much to add, since I cannot test on Windows. Maybe try checking how often `updateGeometries` is being called. You seem to have identified a possible Qt bug in the Aero style-plugin. But you need to pare down the example a lot more so that you can isolate the real cause. For a start, I would want to see if it's possible to reproduce the problem outside of a header-view. – ekhumoro Jun 07 '17 at 20:07
-
I could fix that highlighting issue by parenting the LineEdits under the TableView not the HeaderView. They now get all the updating from the signals and are placed correctly but display the same highlighting behaviour as usual. I suspect it had something to do with `updateGeometries()` triggering events/updates on all children of the `HeaderView` causing the line edits loose focus or so. But just guessing here. – timmwagener Jun 07 '17 at 21:28
-
1@timmwagener. I've adjusted my demo code according to your suggestion as it does not affect the way it works on linux, and should therefore provide a more generic answer. The problem is certainly caused by a bug in the Aero style-plugin, since focus behaviour should not arbitrarily depend on which parent widget is used. – ekhumoro Jun 08 '17 at 15:38
-
Did you ever try to set `view.setSortingEnabled(True)` on the table view that holds the new header view instance? I'm experiencing an issue, that the view doesn't want to sort anymore. As if the signal from the new header view is not connected to the sorting changed slot of the view after calling `view.setHorizontalHeader(header)`. This also seems not to be specific to `FilterHeader` but happens on just setting new vanilla instances of `QtGui.QHeaderView` too. The sort indicator is shown, but no sorting happens whatsoever. If no new header view is set, sorting works as expected. – timmwagener Jun 10 '17 at 12:31
-
I posted the sorting issue, with specific demo code, as a new question [here](https://stackoverflow.com/questions/44474135/qtableview-sorting-fails-after-sethorizontalheader) – timmwagener Jun 10 '17 at 13:59
1
Here is @ekhumoro's answer ported to PyQt5
and with a little change - added a combobox on 4th column.
import sys
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QHeaderView, QWidget, QLineEdit, QApplication, QTableView, QVBoxLayout, QLineEdit, QComboBox
from PyQt5.QtCore import pyqtSignal
class FilterHeader(QHeaderView):
filterActivated = 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 == 3:
editor = QComboBox(self.parent())
#editor.returnPressed.connect(self.filterActivated.emit)
editor.addItems(["One","Two"])
else:
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()
editor.move( self.sectionPosition(index) - self.offset() + 2, height + (self._padding // 2))
editor.resize(self.sectionSize(index), height)
def filterText(self, index):
if 0 <= index < len(self._editors):
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'.split())
self.view.setModel(model)
header.setFilterBoxes(model.columnCount())
header.filterActivated.connect(self.handleFilterActivated)
def handleFilterActivated(self):
header = self.view.horizontalHeader()
for index in range(header.count()):
print((index, header.filterText(index)))
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 600, 300)
window.show()
sys.exit(app.exec_())

Oak_3260548
- 1,882
- 3
- 23
- 41