2

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>
Delf
  • 25
  • 1
  • 4
  • This may not be python: but in c++ Qt this is intentional: http://doc.qt.io/qt-5/qabstractbutton.html enabled buttons still receive the mouse events. You should check the python documentation for this. Seems like you need to check whether the button is enabled in your click handler. – Hayt Sep 09 '16 at 11:12
  • Could you provide the code for`Test2.ui` please? That would make testing much easier. – Ian Sep 09 '16 at 11:47
  • I added the code for the .ui file. Replacing the .setEnabled command with .setDown resulted in the same issue. – Delf Sep 09 '16 at 12:56

1 Answers1

2

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.pushButton.setEnabled(False)
        self.thread.start()

    def handleFinished(self):
        self.thread.quit()
        self.thread.wait()
        self.pushButton.setEnabled(True)
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thank you so, so much. I couldn't figure this out in two days! – Delf Sep 09 '16 at 21:30
  • What is the point of the penultimate line 'self.thread.wait()'? – Delf Sep 09 '16 at 22:06
  • @Delf. Calling `quit()` tells the thread to exit its event-loop, but that can only happen if there's nothing blocking it. So it's necessary to `wait()` until control returns to the event-loop in order to be certain that the thread has completely finished. Don't be tempted to leave it out just because it doesn't seem to have any effect in this example - in general, it's always required. – ekhumoro Sep 09 '16 at 23:09