如何使用按钮启用和禁用 QHeaderView 过滤器
How to enable and disable QHeaderView filters with button
关于@Oak_3260548 和@eyllanesc 的精彩 by @ekhumoro and ,我想首先在 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_())
编辑:已接受的解决方案在许多情况下都可以正常工作,但在某些情况下,当 window 关闭时它会冻结 GUI,所以我找到了一个解决方法:
def closeEvent(self, event):
self.view.horizontalHeader().visible_editors = False
您必须创建一个 属性 来处理编辑器的可见性:
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()
关于@Oak_3260548 和@eyllanesc 的精彩
在我点击过滤按钮后(有 属性 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_())
编辑:已接受的解决方案在许多情况下都可以正常工作,但在某些情况下,当 window 关闭时它会冻结 GUI,所以我找到了一个解决方法:
def closeEvent(self, event):
self.view.horizontalHeader().visible_editors = False
您必须创建一个 属性 来处理编辑器的可见性:
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()