Delf Delf - 2 months ago 23
Python Question

PyQT: PushButton receives commands while disabled

I came across this problem and tried to break it down to the simplest code I could imagine: I created a GUI using Qt Designer V4.8.7 that only consists of a single pushButton with all default settings. It's called 'Test2.ui'. When the button is pressed, it's supposed to get disabled, print something in the terminal and afterwards gets enabled again. What happens is that I'm able to click on the disabled pushButton and it will repeat all the printing as many times as I clicked. This even works when I set the button invisible instead of disable it. I found similar problems on the internet, but none of the solutions seems to work for me – it's driving me mad. Anyone has an idea?

from __future__ import division, print_function
import sys
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import QTimer
from time import sleep

qtCreatorFile = "Test2.ui" # Enter file here.

Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)


class MyApp(QtGui.QMainWindow, Ui_MainWindow):

def __init__(self):
QtGui.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.pushButton.pressed.connect(self.Test_Function)


def Test_Function(self):
self.pushButton.setEnabled(False)
QtGui.QApplication.processEvents()
print('Test 1')
sleep(1)
print('Test 2')
sleep(1)
self.pushButton.setEnabled(True)

if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())


Here is the code for 'Test2.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>445</width>
<height>393</height>
</rect>
</property>
<property name="mouseTracking">
<bool>false</bool>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QPushButton" name="pushButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>160</x>
<y>150</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Test</string>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>445</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

Answer

Your example code will not work because the test function blocks the gui. Whilst it is blocking, the button's disabled state is not updated properly, and so the clicked signal can still be emitted. The best way to avoid blocking the gui is to do the work in a separate thread:

class Worker(QtCore.QObject):
    finished = QtCore.pyqtSignal()

    def run(self):
        print('Test 1')
        sleep(1)
        print('Test 2')
        sleep(1)
        self.finished.emit()

class MyApp(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self):
        ...
        self.pushButton.pressed.connect(self.handleButton)

        self.thread = QtCore.QThread(self)
        self.worker = Worker()
        self.worker.moveToThread(self.thread)
        self.worker.finished.connect(self.handleFinished)
        self.thread.started.connect(self.worker.run)

    def handleButton(self):
        self.button.setEnabled(False)
        self.thread.start()

    def handleFinished(self):
        self.thread.quit()
        self.thread.wait()
        self.button.setEnabled(True)