【问题标题】:Get correct parent in a slot triggered by Button inside a QHeaderView class在 QHeaderView 类中由 Button 触发的插槽中获取正确的父级
【发布时间】:2020-12-03 03:33:58
【问题描述】:

只是为了好玩,我在玩 @ekhumoroa demo code(原始 Qt4 代码的所有功劳归他所有),他在其中插入了一行新的 QLineEdit 小部件进入QTableViewQHeaderview。我将代码移植到 Qt5 并开始向标题添加不同的小部件。 QComboBoxQCheckBox、空白空间 (QWidget) 和 QPushButton 没有问题。

但是,当我创建一个组合 QWidget 时,它包含一个 QHBoxLayout 和一个 QPushButton(它是带有“=”符号的那个,在“三”列中)和一个 QLineEdit。所有控件都链接到相关插槽并且运行良好,包括来自第三列中组合字段的QLineEdit,但来自该组合小部件的QPushButton 除外。 ChangeIntButtonSymbol(self) 插槽定义应该在 值之间循环按钮的 Text。我总是得到一个错误:

AttributeError: 'FilterHeader' object has no attribute 'text'

这表明,与其他情况不同,这里父小部件的上下文(由self.sender() 检索)是不同的,def 接收FilterHeader 类作为父类而不是btn。我也尝试使用 lambda 传递参数:

self.btn.clicked.connect(lambda: self.changebuttonsymbol.emit(self.btn))

...但结果完全相同(错误中的措辞不同)。

很明显,我没有完全理解这个QHeaderView 扩展的架构并犯了一些基本错误。完整的演示如下,单击“=”按钮时会出现问题,感谢任何解决方案或提示。

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

【问题讨论】:

    标签: python pyqt pyqt5 qtableview qheaderview


    【解决方案1】:

    sender() 是表示信号属于哪个对象的方法,很明显 changebuttonsymbol 属于显然没有 text() 方法的 header。另一方面,最好是每个类管理自己的对象,所以按钮文本的改变必须在表头中实现。

    最后,如果使用复杂的小部件,最好将它放在一个类中。

    import sys
    
    from PyQt5.QtCore import pyqtSignal, Qt
    from PyQt5.QtGui import QStandardItemModel
    from PyQt5.QtWidgets import (
        QHeaderView,
        QWidget,
        QLineEdit,
        QApplication,
        QTableView,
        QVBoxLayout,
        QHBoxLayout,
        QComboBox,
        QPushButton,
        QCheckBox,
    )
    
    
    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.view = QTableView()
            layout = QVBoxLayout(self)
            layout.addWidget(self.view)
            header = FilterHeader(self.view)
            self.view.setHorizontalHeader(header)
            model = 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)
    
        def handleFilterActivated(self):
            header = self.view.horizontalHeader()
            for index in range(header.count()):
                if index != 4:
                    print(index, header.filterText(index))
                else:
                    print("Button")
    
    
    if __name__ == "__main__":
    
        app = QApplication(sys.argv)
        window = Window()
        window.setGeometry(800, 100, 600, 300)
        window.show()
        sys.exit(app.exec_())
    

    【讨论】:

    • 非常感谢!它很有魅力,教会了我更多关于如何用 Python 构建代码的知识,我非常感激!
    猜你喜欢
    • 1970-01-01
    • 2022-07-06
    • 1970-01-01
    • 1970-01-01
    • 2016-05-18
    • 1970-01-01
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    相关资源
    最近更新 更多