QPushButton.clicked() 在使用 .ui 表单自动装配时触发两次

考虑这个设置:


主脚本,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()信号。


有人可以请解释一下吗?


犯罪嫌疑人X
浏览 459回答 1
1回答

慕桂英4014372

首先,如果使用 Qt 的信号槽自动装配机制,则使用方法QMetaObject::connectSlotsByName(),因此这种行为是由于将该函数从 C++ 转换为 Python,在 C++ 的情况下,仅 QMetaObject::connectSlotsByName() 函数连接到插槽,但在 Python 中它扩展到调用不是插槽的函数。问题是当你点击时是一个重载信号,在 C++ 的情况下允许你使用默认参数来实现:void QAbstractButton::clicked(bool checked = false)但在 python 中必须使用 2 个签名:clicked = QtCore.pyqtSignal([], [bool])因此,在 PyQt 与插槽建立的连接中,它习惯于QMetaObject::connectSlotsByName()使用使用QMetaObject获取签名的对象的QMetaMethod,但是如果它不是插槽,您将无法获得该信息,因此该连接等效于调用。在@pyqtSlot()具有以下签名的情况下:@pyqtSlot()def on_btnSlot_clicked(self):&nbsp; &nbsp; print('slotted function call')PyQt 建立的连接如下:self.btnSlot.clicked.connect(self.on_btnSlot_clicked)但如果签名@pyqtSlot(bool)是:@pyqtSlot(bool)def on_btnSlot_clicked(self, checked):&nbsp; &nbsp; 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(...)的签名进行验证。如果一个信号连接到一个不是 a 的函数,@pyqtSlot(...)它们将连接所有可能的签名,所以如果信号被 n 个签名重载,它将被调用 n 次。您必须使用@pyqtSlot()以避免之前的问题,因为除了它具有快速和节省资源的优势。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python