我在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_())
使用
pyqtSlot
时需要小心,因为它很容易破坏它所装饰的插槽的签名。在您的情况下,它已将插槽重新定义为没有参数,这解释了您收到该错误消息的原因。简单的修复方法就是删除它,因为如果没有它,您的示例也将完美运行。
pyqtSlot
的主要目的是允许定义一个槽的多个不同的重载,每个重载都有不同的签名。有时在进行跨线程连接时也可能需要它。然而,这些用例相对较少,并且在大多数 PyQt/PySide 应用程序中根本没有必要使用 pyqtSlot
。信号可以连接到任何Python可调用对象,无论它是否被装饰为槽。
这个问题已经有一个公认的答案,但无论如何我都会给出我的答案。
有问题的插槽连接到 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_()