信号和缺失的位置参数

问题描述 投票:0回答:2

我在QtDesigner中开发了两个窗口(SourceForm,DestinationForm)并使用pyuic5转换它们的.ui页面。我使用第三类

WController
作为使用堆叠小部件在两个窗口之间导航的方式。我在
SourceForm
中有一个按钮,用一些数据填充
treeWidget
,方法
handle_treewidget_itemchange
指示使用
treeWidget
选中或取消选中
self.treeWidget.itemChanged.connect(self.handle_treewidget_itemchange)
中的特定项目时会发生什么。据我了解,
itemChanged.connect
会自动将更改的行和列发送到插槽,但是当第一次调用
handle_treewidget_itemchange(self,row,col)
时,我的脚本崩溃并出现 TypeError:

TypeError: handle_treewidget_itemchange() missing 2 required positional arguments: 'row' and 'col'

如果我取出

row
col
参数,脚本运行正常。当我最初在
SourceForm
.py 文件本身中同时拥有该方法和调用时,我的代码按预期工作......也许这只是一个范围问题?我开始认为在对 Python 还没有经验的情况下尝试使用 PyQt 是一个坏主意:(

我尝试将代码精简为精华:

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import pyqtSlot
from imp_sourceform import Ui_SourceForm
from imp_destform import Ui_DestinationForm


class WController(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(WController, self).__init__(parent)
        self.central_widget = QtWidgets.QStackedWidget()
        self.setCentralWidget(self.central_widget)

        self.sourcewindow = SourceForm()
        self.destinationwindow = DestinationForm()

        self.central_widget.addWidget(self.sourcewindow)
        self.central_widget.addWidget(self.destinationwindow)

        self.central_widget.setCurrentWidget(self.sourcewindow)

        self.sourcewindow.selectdestinationsbutton.clicked.connect(lambda: self.navigation_control(1))
        self.destinationwindow.backbutton.clicked.connect(lambda: self.navigation_control(0))

    def navigation_control(self, topage):
        if topage == 1:
            self.central_widget.setCurrentWidget(self.destinationwindow)
        elif topage == 0:
            self.central_widget.setCurrentWidget(self.sourcewindow)

class SourceForm(QtWidgets.QWidget, Ui_SourceForm):
    def __init__(self):
        super(SourceForm, self).__init__()
        self.setupUi(self)

        self.treeWidget.itemChanged.connect(self.handle_treewidget_itemchange) 

    @pyqtSlot()
    def handle_treewidget_itemchange(self,row,col): 
        if row.parent() is None and row.checkState(col) == QtCore.Qt.Unchecked: 
            for x in range(0,row.childCount()):
                row.child(x).setCheckState(0, QtCore.Qt.Unchecked)
        elif row.parent() is None and row.checkState(col) == QtCore.Qt.Checked:
            for x in range(0,row.childCount()):
                row.child(x).setCheckState(0, QtCore.Qt.Checked)
        else:
            pass

class DestinationForm(QtWidgets.QWidget, Ui_DestinationForm):
    def __init__(self):
        super(DestinationForm, self).__init__()
        self.setupUi(self)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = WController()
    window.show()
    sys.exit(app.exec_())
python parameters pyqt signals-slots
2个回答
2
投票

使用

pyqtSlot
时需要小心,因为它很容易破坏它所装饰的插槽的签名。在您的情况下,它已将插槽重新定义为没有参数,这解释了您收到该错误消息的原因。简单的修复方法就是删除它,因为如果没有它,您的示例也将完美运行。

pyqtSlot
的主要目的是允许定义一个槽的多个不同的重载,每个重载都有不同的签名。有时在进行跨线程连接时也可能需要它。然而,这些用例相对较少,并且在大多数 PyQt/PySide 应用程序中根本没有必要使用
pyqtSlot
。信号可以连接到任何Python可调用对象,无论它是否被装饰为槽。


0
投票

这个问题已经有一个公认的答案,但无论如何我都会给出我的答案。

有问题的插槽连接到 itemChanged(QTreeWidgetItem *item, int column) 信号,因此

pyqtSlot
应该看起来像
@pyqtSlot(QTreeWidgetItem, int)

现在,正如

ekhumoro
所指出的,PyQt 接受将信号连接到任何 Python 可调用对象,无论是方法、lambda 还是具有
__call__
方法的函子。但这样做比使用
@pyqtSlot
更不安全。

例如,当源 QObject(将发出信号的)被销毁或目标 QObject(具有 Qt 插槽的)被销毁时,Qt 会自动断开连接。例如,如果您删除一个小部件,则无需向其发出其他地方发生了某些情况的信号。如果您使用

@pyqtSlot
,则会在您的类中创建一个真正的 Qt 插槽,因此可以应用此断开连接机制。另外,Qt 不持有对目标 QObject 的强引用,因此可以将其删除。

如果您使用任何可调用的方法,例如未修饰的绑定方法,Qt 将无法识别连接的目标 QObject。更糟糕的是,由于您传递了一个Python可调用对象,它将持有对它的强引用,而可调用对象(绑定方法)又将持有对最终QObject的引用,因此您的目标QObject将不会被垃圾收集,直到您手动断开连接,或删除源 QObject。

看到这段代码,您可以启用一个连接或另一个连接,并观察行为的差异,这表明窗口是否可以被垃圾收集:

from PyQt5.QtWidgets import QApplication, QMainWindow

app = QApplication([])

def create_win():
    win = QMainWindow()
    win.show()

    # case 1
    # app.aboutToQuit.connect(win.repaint)  # this is a qt slot, so win can be deleted

    # case 2
    # app.aboutToQuit.connect(win.size)  # this is not a qt slot, so win can't be deleted

    # win should get garbage-collected here

create_win()

app.exec_()
© www.soinside.com 2019 - 2024. All rights reserved.