Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
325 views
in Technique[技术] by (71.8m points)

python - QPushButton.clicked() fires twice when autowired using .ui form

Consider this setup:

Main script, 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 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>

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?

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.

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.

Can someone, please, explain?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.

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)

but in python 2 signatures must be used:

clicked = QtCore.pyqtSignal([], [bool])

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.


In the case of @pyqtSlot() have the following signature:

@pyqtSlot()
def on_btnSlot_clicked(self):
    print('slotted function call')

The connection made by PyQt the following:

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

but if the signature of the @pyqtSlot(bool) is:

@pyqtSlot(bool)
def on_btnSlot_clicked(self, checked):
    print('slotted function call', checked)

The connection made by PyQt the following:

self.btnSlot.clicked[bool].connect(self.on_btnSlot_clicked)

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)

In conclusion:

  • 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.

  • You must use @pyqtSlot() to avoid the previous problem, since apart it has advantages for the rapidity and the saving of resources.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...