1

关于@ekhumoro 的出色回答以及@Oak_3260548 和@eyllanesc 的修改,我想首先在 QHeaderView 下方有一个没有过滤器的普通视图和一个用于激活(显示)过滤器的按钮:

在此处输入图像描述

单击过滤器按钮(具有属性 button.setCheckable(True))后,我想得到:

在此处输入图像描述

注意:过滤器按钮被按下。现在,当我再次单击“过滤器”按钮(取消按下它)时,我希望 QHeaderView 下方带有过滤器的行消失:

在此处输入图像描述

我该怎么做,换句话说,如何让 QTableView 的 QHeaderView 成为旧的普通的?在应用程序中拥有这种功能非常方便,用户可以随时使用按钮(或任何其他方式)切换 QHeaderView 下方的过滤器行。应用程序的行为将是在未按下按钮并且过滤器行消失时取消视图中的所有过滤器,但这对于这个问题并不重要。

编辑:这是MRE:

import sys

from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import (
    QHeaderView,
    QWidget,
    QLineEdit,
    QApplication,
    QTableView,
    QVBoxLayout,
    QHBoxLayout,
    QComboBox,
    QPushButton,
    QCheckBox,
    QMessageBox,
)

class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.btn = QPushButton()
        self.btn.setText("=")
        self.btn.setFixedWidth(20)

        self.linee = QLineEdit()
        self.linee.setPlaceholderText("Filter")

        lay = QHBoxLayout(self)
        lay.setContentsMargins(0, 0, 0, 0)
        lay.setSpacing(0)
        lay.addWidget(self.btn)
        lay.addWidget(self.linee)

class FilterHeader(QHeaderView):
    filterActivated = pyqtSignal()

    def __init__(self, parent):
        super().__init__(Qt.Horizontal, parent)
        self._editors = []
        self._padding = 4
        self.setStretchLastSection(True)
        # self.setResizeMode(QHeaderView.Stretch)
        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()
        elif index == 2:  # Number filter (>|=|<)
            editor = Widget(parent)
            editor.linee.returnPressed.connect(self.filterActivated)
            editor.btn.clicked.connect(self.changebuttonsymbol)
        elif index == 3:
            editor = QComboBox(parent)
            editor.addItems(["", "Combo", "One", "Two", "Three"])
            editor.currentIndexChanged.connect(self.filterActivated)
        elif index == 4:
            editor = QPushButton(parent)
            editor.clicked.connect(self.filterActivated)
            editor.setText("Button")
        elif index == 5:
            editor = QCheckBox(parent)
            editor.clicked.connect(self.filterActivated)
            editor.setTristate(True)
            editor.setCheckState(Qt.Checked)
            editor.setText("CheckBox")
        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
            if type(editor) is QComboBox:
                compensate_y = +2
            elif type(editor) in (QWidget, Widget):
                compensate_y = -1
            elif type(editor) is QPushButton:
                compensate_y = -1
            elif type(editor) is QCheckBox:
                compensate_y = 4
                compensate_x = 4
            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.text()
        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()

    def changebuttonsymbol(self):
        nbtn = self.sender()
        if nbtn.text() == "=":
            nbtn.setText(">")
        elif nbtn.text() == ">":
            nbtn.setText("<")
        else:
            nbtn.setText("=")

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.filter_button = QPushButton("Filter")
        self.filter_button.setCheckable(True)
        self.filter_button.setChecked(True)
        self.filter_button.clicked.connect(self.on_button_clicked)
        self.view = QTableView()
        self.view.horizontalHeader().setStretchLastSection(True)

        button_layout = QHBoxLayout()
        button_layout.addStretch()
        button_layout.addWidget(self.filter_button)

        layout = QVBoxLayout(self)
        layout.addLayout(button_layout)
        layout.addWidget(self.view)

        header = FilterHeader(self.view)
        self.view.setHorizontalHeader(header)
        self.view.verticalHeader().setVisible(False)
        #model = QStandardItemModel(self.view)
        model = QStandardItemModel(5, 7, self.view)
        for i in range(5):
            for j in range(7):
                item = QStandardItem(str(i+j))
                model.setItem(i, j, item)
        model.setHorizontalHeaderLabels("One Two Three Four Five Six Seven".split())
        self.view.setModel(model)
        header.setFilterBoxes(model.columnCount())
        header.filterActivated.connect(self.handleFilterActivated)

    def handleFilterActivated(self):
        header = self.view.horizontalHeader()
        print()
        for index in range(header.count()):
            if index != 4:
                print(index, header.filterText(index))
            else:
                print("Button")

    def on_button_clicked(self):
        if self.filter_button.isChecked():
            QMessageBox.information(None, "", "Now I want the row with filters below the QHeaderView to appear again.")
        else:
            QMessageBox.information(None, "", "Now I want the row with filters below the QHeaderView to disappear.")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    #window.setGeometry(800, 100, 600, 300)
    window.resize(950, 220)
    window.show()
    sys.exit(app.exec_())

编辑:接受的解决方案在许多情况下都可以正常工作,但在某些情况下,它会在窗口关闭时冻结 GUI,所以我找到了一种解决方法:

def closeEvent(self, event):
    self.view.horizontalHeader().visible_editors = False
4

1 回答 1

2

您必须创建一个属性来处理编辑器的可见性:

class FilterHeader(QHeaderView):
    filterActivated = pyqtSignal()

    def __init__(self, parent):
        super().__init__(Qt.Horizontal, parent)
        self._visible_editors = False
        self._editors = []
        self._padding = 4
        self.setStretchLastSection(True)
        # self.setResizeMode(QHeaderView.Stretch)
        self.setDefaultAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        self.setSortIndicatorShown(False)
        self.sectionResized.connect(self.adjustPositions)
        parent.horizontalScrollBar().valueChanged.connect(self.adjustPositions)

    @property
    def visible_editors(self):
        return self._visible_editors

    @visible_editors.setter
    def visible_editors(self, is_visible):
        self._visible_editors = is_visible
        for editor in self._editors:
            editor.setVisible(self.visible_editors)
        self.updateGeometries()

    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)
            editor.setVisible(self.visible_editors)
            self._editors.append(editor)
        self.adjustPositions()

    def create_editor(self, parent, index):
        if index == 1:  # Empty
            editor = QWidget()
        elif index == 2:  # Number filter (>|=|<)
            editor = Widget(parent)
            editor.linee.returnPressed.connect(self.filterActivated)
            editor.btn.clicked.connect(self.changebuttonsymbol)
        elif index == 3:
            editor = QComboBox(parent)
            editor.addItems(["", "Combo", "One", "Two", "Three"])
            editor.currentIndexChanged.connect(self.filterActivated)
        elif index == 4:
            editor = QPushButton(parent)
            editor.clicked.connect(self.filterActivated)
            editor.setText("Button")
        elif index == 5:
            editor = QCheckBox(parent)
            editor.clicked.connect(self.filterActivated)
            editor.setTristate(True)
            editor.setCheckState(Qt.Checked)
            editor.setText("CheckBox")
        else:
            editor = QLineEdit(parent)
            editor.setPlaceholderText("Filter")
            editor.returnPressed.connect(self.filterActivated)
        return editor

    def sizeHint(self):
        size = super().sizeHint()
        if self._editors and self.visible_editors:
            height = self._editors[0].sizeHint().height()
            size.setHeight(size.height() + height + self._padding)
        return size

    def updateGeometries(self):
        if self._editors and self.visible_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
            if type(editor) is QComboBox:
                compensate_y = +2
            elif type(editor) in (QWidget, Widget):
                compensate_y = -1
            elif type(editor) is QPushButton:
                compensate_y = -1
            elif type(editor) is QCheckBox:
                compensate_y = 4
                compensate_x = 4
            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.text()
        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()

    def changebuttonsymbol(self):
        nbtn = self.sender()
        if nbtn.text() == "=":
            nbtn.setText(">")
        elif nbtn.text() == ">":
            nbtn.setText("<")
        else:
            nbtn.setText("=")


class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.filter_button = QPushButton("Filter")
        self.filter_button.setCheckable(True)
        self.filter_button.setChecked(True)
        self.filter_button.clicked.connect(self.on_button_clicked)
        self.view = QTableView()
        self.view.horizontalHeader().setStretchLastSection(True)

        button_layout = QHBoxLayout()
        button_layout.addStretch()
        button_layout.addWidget(self.filter_button)

        layout = QVBoxLayout(self)
        layout.addLayout(button_layout)
        layout.addWidget(self.view)

        header = FilterHeader(self.view)
        self.view.setHorizontalHeader(header)
        self.view.verticalHeader().setVisible(False)
        # model = QStandardItemModel(self.view)
        model = QStandardItemModel(5, 7, self.view)
        for i in range(5):
            for j in range(7):
                item = QStandardItem(str(i + j))
                model.setItem(i, j, item)
        model.setHorizontalHeaderLabels("One Two Three Four Five Six Seven".split())
        self.view.setModel(model)
        header.setFilterBoxes(model.columnCount())
        header.filterActivated.connect(self.handleFilterActivated)

        self.on_button_clicked()

    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 on_button_clicked(self):
        self.view.horizontalHeader().visible_editors = self.filter_button.isChecked()
于 2020-09-03T00:01:09.373 回答