Search code examples
pythonpyqt4qpushbutton

pyqt: how to disable multiple clicks for QPushButton?


When I push a button, some time consuming code runs. While it is running, I want to avoid that the button responds to any further click. When the code is done, then the button can be re-enabled and further clicks should be processed.

I'm trying to do that using:

   self.btn.blockSignals(True)
   self.btn.setEnabled(False)
   ... code ...
   self.btn.blockSignals(True)
   self.btn.setEnabled(False)

But still, if I rapidly click this button 10 times, the code will be executed 10 times...

In reality I'd move the time consuming code to another thread. (Edit: but still the problem is the same - I think??. In reality that solves it. See accepted answer.)

How do I block or ignore clicks to a button while something is running?

Here's a minimal version of my code:

import time
import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()
        self.initUI()

    def initUI(self):
        grid = QtGui.QGridLayout()
        self.setLayout(grid)
        self.btn = QtGui.QPushButton('Count')
        grid.addWidget(self.btn, 1, 1)
        self.txt1 = QtGui.QTextEdit()
        grid.addWidget(self.txt1, 1, 2)
        self.btn.clicked.connect(self.click)
        self.count = 0
        self.show()

    def click(self):
        # Here I want to block any further click in the button, but it is
        # not working - clicking it 10 times quickly will run this 10 times...
        self.btn.blockSignals(True)
        self.btn.setEnabled(False)
        time.sleep(2)  # time consuming code...
        self.count += 1
        self.txt1.append(str(self.count))
        self.repaint()
        self.btn.setEnabled(True)
        self.btn.blockSignals(False)


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

Solution

  • If you put the long-runnning code in a thread, control can return to the the main event-loop once the thread is started, which will allow the gui to update immediately.

    Here is a basic demo based on your example:

    import sys
    from PyQt4 import QtGui, QtCore
    
    class Thread(QtCore.QThread):
        def run(self):
            QtCore.QThread.sleep(2)
    
    class Example(QtGui.QWidget):
        def __init__(self):
            super(Example, self).__init__()
            self.initUI()
    
        def initUI(self):
            grid = QtGui.QGridLayout()
            self.setLayout(grid)
            self.btn = QtGui.QPushButton('Count')
            grid.addWidget(self.btn, 1, 1)
            self.txt1 = QtGui.QTextEdit()
            grid.addWidget(self.txt1, 1, 2)
            self.btn.clicked.connect(self.click)
            self.thread = Thread()
            self.thread.finished.connect(lambda: self.btn.setEnabled(True))
            self.show()
    
        def click(self):
            self.txt1.append('click')
            if not self.thread.isRunning():
                self.btn.setEnabled(False)
                self.thread.start()
    
    if __name__ == '__main__':
    
        app = QtGui.QApplication(sys.argv)
        ex = Example()
        sys.exit(app.exec_())