考虑以下设置:
主脚本,main.py
:
import sys
from PyQt5 import uic
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QMainWindow
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = uic.loadUi("mw.ui", self)
def on_btnFunc_clicked(self):
print('naked function call')
@pyqtSlot()
def on_btnSlot_clicked(self, bool):
print('slotted function call')
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
Qt Designer.ui表单,mw.ui
:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>153</width>
<height>83</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="btnFunc">
<property name="text">
<string>naked func</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSlot">
<property name="text">
<string>slotted func</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
此设置使用Qt的信号槽自动布线机制将按钮点击绑定到相应的回调。为什么裸回调被调用两次,而槽回调只按预期调用一次?
我发现了这个和这个,但这些设置与我的有点不同,因为我不手动绑定信号,也不安装事件过滤器。
我认为这种行为可能是由于具有不同签名的信号绑定到同一个插槽,但(如果我理解正确的话(QPushButton
只有一个clicked()
信号。
有人能解释一下吗?
首先,如果使用Qt的信号槽自动布线机制,则使用QMetaObject::connectSlotsByName()
方法,因此这种行为是由于该函数从C++转换为Python,在C++的情况下,QMetaObject::connectSlotsByName((函数仅连接槽,但在Python中,它扩展为调用非槽的函数。
问题是,当你点击时是一个过载信号,在C++的情况下,这允许你使用默认参数来实现:
void QAbstractButton::clicked(bool checked = false)
但是在python中必须使用2个签名:
clicked = QtCore.pyqtSignal([], [bool])
因此,在PyQt与插槽的连接中,它被用于使用QMetaMethod
获得签名的对象的QMetaObject
的QMetaObject::connectSlotsByName()
,但是,如果它不是插槽,则无法获得该信息,因此该连接等效于调用。
在@pyqtSlot()
的情况下,具有以下签名:
@pyqtSlot()
def on_btnSlot_clicked(self):
print('slotted function call')
PyQt建立的连接如下:
self.btnSlot.clicked.connect(self.on_btnSlot_clicked)
但是如果CCD_ 10的签名是:
@pyqtSlot(bool)
def on_btnSlot_clicked(self, checked):
print('slotted function call', checked)
PyQt建立的连接如下:
self.btnSlot.clicked[bool].connect(self.on_btnSlot_clicked)
但是,在它连接到一个不是槽的函数的情况下,它不考虑这些元素,因为它使用QMetaObject
,所以它将使用所有可能的签名进行连接。
self.btnSlot.clicked[bool].connect(self.on_btnFunc_clicked)
self.btnSlot.clicked.connect(self.on_btnFunc_clicked)
结论:
使用
QMetaObject::connectSlotsByName(...)
时,如果连接到@pyqtSlot(...)
,则验证签名。如果一个信号连接到一个不是@pyqtSlot(...)
的函数,它们将与所有可能的签名连接,因此,如果信号过载n个签名,它将被称为n次。您必须使用
@pyqtSlot()
来避免前面的问题,因为除此之外,它还具有快速和节省资源的优点。