当使用.ui表单自动连线时,QPushButton.clicked()会激发两次



考虑以下设置:

主脚本,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获得签名的对象的QMetaObjectQMetaObject::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()来避免前面的问题,因为除此之外,它还具有快速和节省资源的优点。

最新更新