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

发布于 2021-01-29 14:59:49

考虑以下设置:

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

有人可以解释吗?

关注者
0
被浏览
141
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    首先,如果使用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):
        print('slotted function call')
    

    由PyQt建立的连接如下:

    self.btnSlot.clicked.connect(self.on_btnSlot_clicked)
    

    但是如果的签名@pyqtSlot(bool)是:

    @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()来避免前面的问题,因为除此以外,它还具有快速性和节省资源的优点。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看