QPushButton.clicked() 在使用 .ui 表单自动装配时触发两次 [英] QPushButton.clicked() fires twice when autowired using .ui form

查看:73
本文介绍了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:

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆