QPushButton.clicked() 在使用 .ui 表单自动装配时触发两次 [英] QPushButton.clicked() fires twice when autowired using .ui form
问题描述
考虑这个设置:
主脚本,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
:
Qt Designer .ui form, 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 的信号槽自动装配机制将按钮点击绑定到相应的回调.为什么裸回调会被调用两次,而插槽只按预期调用一次?
This setup uses Qt's signal-slot autowire mechanic to bind button clicks to respective callbacks. Why does the naked callback get called twice while the slotted one only once as intended?
我发现了这个和this 但这些设置与我的有点不同,因为我不手动绑定信号我也不安装事件过滤器.
I found this and this but those setups are a bit different from mine, since I don't bind signals manually nor do I install an event filter.
我认为这种行为可能是由于具有不同签名的信号绑定到同一个插槽,但是(如果我理解正确的话)QPushButton
只有一个 clicked()
信号.
I thought this behavior might occur due to signals with different signatures get bound to the same slot, but (if I understand correctly) QPushButton
has only one clicked()
signal.
有人可以解释一下吗?
推荐答案
首先,如果使用Qt的signal-slot autowire机制,则使用方法QMetaObject::connectSlotsByName()
,所以这种行为是由于该函数从 C++ 到 Python 的转换,在 C++ 的情况下 QMetaObject::connectSlotsByName() 函数只连接到插槽,但在 Python 中它扩展到调用不是插槽的函数.
First of all, if the Qt's signal-slot autowire mechanic is used, the method is used QMetaObject::connectSlotsByName()
, so this behavior is due to the translation of that function from C++ to Python, in the case of C++ the QMetaObject::connectSlotsByName() function only connect to slots, but in Python it extended to invoke functions that are not slots.
问题是当你点击时是一个重载信号,在C++的情况下允许你使用默认参数来实现:
The problem is that when you click is an overloaded signal, which in the case of C++ allows you to implement using a default parameter:
void QAbstractButton::clicked(bool checked = false)
但在 python 中必须使用 2 签名:
but in python 2 signatures must be used:
clicked = QtCore.pyqtSignal([], [bool])
因此,在 PyQt 与插槽建立的连接中,它被用于 QMetaObject::connectSlotsByName()
使用对象的 QMetaObject
获取签名使用QMetaMethod
,但是如果它不是插槽,您将无法获得该信息,因此连接相当于调用.
Therefore, in the connection made by PyQt to a slot it is used to QMetaObject::connectSlotsByName()
that uses the QMetaObject
of the object that obtains the signatures using the QMetaMethod
, however if it is not a slot you can not get that information so the connection is equivalent to an invocation.
在 @pyqtSlot()
的情况下,具有以下签名:
In the case of @pyqtSlot()
have the following signature:
@pyqtSlot()
def on_btnSlot_clicked(self):
print('slotted function call')
PyQt 建立的连接如下:
The connection made by PyQt the following:
self.btnSlot.clicked.connect(self.on_btnSlot_clicked)
但是如果 @pyqtSlot(bool)
的签名是:
but if the signature of the @pyqtSlot(bool)
is:
@pyqtSlot(bool)
def on_btnSlot_clicked(self, checked):
print('slotted function call', checked)
PyQt 建立的连接如下:
The connection made by PyQt the following:
self.btnSlot.clicked[bool].connect(self.on_btnSlot_clicked)
但是如果它连接到一个不是插槽的函数,它不会考虑这些元素,因为它使用了QMetaObject
,所以它会与所有可能的签名.
But in the case that it is connected to a function that is not a slot, it does not take into account those elements, since it uses the QMetaObject
, so it will make the connections with all the possible signatures.
self.btnSlot.clicked[bool].connect(self.on_btnFunc_clicked)
self.btnSlot.clicked.connect(self.on_btnFunc_clicked)
<小时>
总结:
当使用
QMetaObject::connectSlotsByName(...)
时,如果它连接到一个@pyqtSlot(...)
,签名是验证.如果信号连接到不是@pyqtSlot(...)
的函数,它们将连接所有可能的签名,因此如果信号用 n 个签名过载,它将被调用 n 次.
When
QMetaObject::connectSlotsByName(...)
is used, if it is connected to a@pyqtSlot(...)
, the signatures are verified. If a signal is connected to a function that is not an@pyqtSlot(...)
they will connect with all possible signatures, so if the signal is overloaded with n signatures it will be called n-times.
您必须使用 @pyqtSlot()
避免了前面的问题,因为它具有快速和节省资源的优势.
You must use @pyqtSlot()
to avoid the previous problem, since apart it has advantages for the rapidity and the saving of resources.
这篇关于QPushButton.clicked() 在使用 .ui 表单自动装配时触发两次的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!