如何在QHeaderView和QTableView之间插入小部件?

5
我想在QHeaderView和QTableView的其余部分之间显示小部件,就像下面的示例图片(使用Photoshop创建)一样,因为这似乎是启用列过滤输入的自然方式。
有人有什么想法可以在它们之间插入小部件吗?
enter image description here
2个回答

9
以下是我为自己的一个项目编写的FilterHeader类的演示。您可能需要根据自己的需要进行调整,但它应该已经可以满足大多数要求了。过滤框周围的填充在所有平台上可能不会起作用,因此您可能需要在adjustPositions方法中调整代码。 屏幕截图
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_())

我用PySide测试了一下,它能够正常工作并且是一个很好的起点。然而,我注意到在hoverEvents中有一些微小的显示问题(基本上就是当鼠标悬停在QLineEdit上时,焦点会消失/稍微闪烁而没有明显的原因)。所以我记录了QLineEdits的事件传递情况,看起来所有的事件都被FilterView捕获了。(这可能会静默地将其分配给QLineEdits,因为打字等仍然可以工作)。然而,这让我想知道这是否与FilterView的sizeHint()有关,它跨越了QLineEdits的区域,是否会引起问题? - timmwagener
我可以通过将LineEdits放在TableView而不是HeaderView下来解决这个高亮问题。现在它们从信号中获取所有更新并正确地放置,但显示与通常相同的高亮行为。我怀疑这与updateGeometries()触发HeaderView的所有子元素的事件/更新有关,导致行编辑失去焦点或类似情况。但只是猜测。 - timmwagener
1
@timmwagener。根据您的建议,我已经调整了我的演示代码,因为它不会影响在Linux上的工作方式,因此应该提供更通用的答案。问题肯定是由Aero样式插件中的错误引起的,因为焦点行为不应该任意地取决于使用哪个父小部件。 - ekhumoro
你有没有尝试在持有新标题视图实例的表视图上设置view.setSortingEnabled(True)?我遇到了一个问题,即视图不再想要排序。就好像在调用view.setHorizontalHeader(header)之后,新标题视图的信号未连接到视图的排序更改插槽一样。这似乎也不是特定于FilterHeader,而是发生在仅设置新的QtGui.QHeaderView实例时。排序指示器被显示出来,但根本没有进行排序。如果没有设置新的标题视图,则排序按预期工作。 - timmwagener
我在这里发布了一个排序问题,附带具体的演示代码,作为一个新问题 here - timmwagener
显示剩余3条评论

1
这里是@ekhumoro的答案,移植到了PyQt5并进行了少许更改——在第四列加入了一个组合框。

enter image description here

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接