1

I want to use QMessageBox to block its parent QDialog, while doing calculation. I came upon something like that, but this does not work.

msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle('Working ....')
msgBox.setText("Working, please wait ...")
msgBox.setStandardButtons(QtWidgets.QMessageBox.NoButton)
msgBox.exec_()

(doing some time consuming work)

msgBox.close()

What is wrong, how can I do it properly?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Pygmalion
  • 785
  • 2
  • 8
  • 24

1 Answers1

2

You have to execute the time-consuming task in another thread and signal through a signal that the task is finished executing. To close the QMessageBox you must use the accept() method:

import threading

from PyQt5 import QtCore, QtWidgets


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

    def execute(self, func, args):
        threading.Thread(target=self._execute, args=(func, args,), daemon=True).start()

    def _execute(self, func, args):
        self.started.emit()
        func(*args)
        self.finished.emit()


def consuming_work(arg1, arg2):
    import time

    print(arg1, arg2)
    time.sleep(5)
    print("finish")


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    msgBox = QtWidgets.QMessageBox()
    msgBox.setWindowTitle("Working ....")
    msgBox.setText("Working, please wait ...")
    msgBox.setStandardButtons(QtWidgets.QMessageBox.NoButton)
    worker = Worker(msgBox)
    worker.finished.connect(msgBox.accept)
    worker.execute(consuming_work, ["Stack", "Overflow"])
    msgBox.exec_()

Update:

import threading

from PyQt5 import QtCore, QtWidgets


class WorkerMessageBox(QtWidgets.QMessageBox):
    started = QtCore.pyqtSignal()
    finished = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.finished.connect(self.accept)

    def execute(self, func, args):
        threading.Thread(target=self._execute, args=(func, args,), daemon=True).start()
        return self.exec_()

    def _execute(self, func, args):
        self.started.emit()
        func(*args)
        self.finished.emit()


def consuming_work(arg1, arg2):
    import time

    print(arg1, arg2)
    time.sleep(5)
    print("finish")


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    msgBox = WorkerMessageBox()
    msgBox.setWindowTitle("Working ....")
    msgBox.setText("Working, please wait ...")
    msgBox.setStandardButtons(QtWidgets.QMessageBox.NoButton)

    msgBox.execute(consuming_work, ["Stack", "Overflow"])
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thanks for the answer. Would it be possible to define new class based on QMessageBox instead of a generic class? I think this would be easier to use, i.e. Worker.setText(...), Worker.setWindowsTitle(...)... – Pygmalion Feb 08 '20 at 20:59
  • @Pygmalion Yes, it is possible, I think that in general this should be part of your work but this time I will only show you how it could be done. Check my update. – eyllanesc Feb 08 '20 at 21:05
  • I did almost exactly as you, except i put `exec_` on the wrong place. But even your version does not work, I get `AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect'`. I guess something's wrong with my implementation. – Pygmalion Feb 08 '20 at 21:14
  • @Pygmalion I just tested my code in Linux with PtQt5 5.14.1 and it works correctly, I recommend you copy my code without modifying it – eyllanesc Feb 08 '20 at 21:16
  • I found error. Just typing one command on wrong place, and it does not work. I was close, but I couldn't have done it myself, probably. – Pygmalion Feb 08 '20 at 21:20
  • As far as accepting answers is concerned, I accept the best answer a week after asking the question, if at least one answers it properly. That was a recommendation from StackOverflow. Your answer obviously qualify. – Pygmalion Feb 09 '20 at 10:35