Qt: QPushButton is blocked by child process

2019-08-12 01:54发布

问题:

Given the following code

button = ...
process = QProcess()
button.clicked.connect(start_process)

def start_process():

    # Disable the button
    button.setEnabled(False)
    # This seems to have no effect (...)

    # This also has no effect, thus commented out.
    # QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    # Execute the program in a blocking way
    process.execute('/usr/bin/libreoffice')
    # (...) as right now, while libreoffice is running and I click the button
    # no animation is taking place, but the click !! is getting registered !!
    # If I terminate libreoffice, these clicks are executed and this function is
    # called again for every click done.

    # When the process and all its children have terminated, enable the button again
    button.setEnabled(True)

I have explained what my problem is in the code. I think it has something to do with the main thread (gui thread) getting blocked by the process.execute(...) call while having the setEnabled(False) call still in a queue or something.

What I want to achieve:

  1. User clicks Button -> Button gets disabled and LibreOffice is launched
  2. While LibreOffice is running, my GUI is blocked and clicks on the button are not registered
  3. User exits LibreOffice, my GUI is unblocked and the Button is enabled again for another start of LibreOffice

What might eventually work:

Instead of using process.execute(...), I use process.start(...) and hook the start() signal to my own function to disable the button and the finished() signal to enable the button again?

What in the end worked:

class C(QObject):
    """
    Host (Communicator) for all my signals
    """
    enableButton = QtCore.Signal()
    disableButton = QtCore.Signal()


class MainWindow(QtGui.QMainWindow):
    def __init__(self, program=None, c=None, parent=None):
        super(MainWindow, self).__init__(parent)
        self.ui = Ui_mainWindow()
        self.ui.setupUi(self)

        self.program = program
        self.c = c  # Reference to the C instance
        self.setupSignals()

    def setupSignals(self):
        self.ui.btnStart.clicked.connect(self.program.start) # Trigger button

        # Register for events from UI Thread
        self.c.enableButton.connect(self.button_enable)
        self.c.disableButton.connect(self.button_disable)

    def button_enable(self):
        self.ui.btnStart.setEnabled(True)

    def button_disable(self):
        self.ui.btnStart.setEnabled(False)


class Program(object):
    def __init__(self, c=None):
        self.c = c
        self.proc = QProcess()  # The process to run in the start method.
                                # needed here because its runs out of scope otherwise.

    def start(self):

        def _onStart():
            self.c.disableButton.emit()
            print("on Start")

        def _onFinish():
            self.c.enableButton.emit()
            print("on Finish")

        def _onError():
            print("on Error")

        self.proc.started.connect(_onStart)
        self.proc.finished.connect(_onFinish)
        self.proc.error.connect(_onError)
        self.proc.start('/usr/bin/libreoffice')  # Waits until process ends

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    c = C()

    program = Program(c=c)
    main_window = MainWindow(program=program, c=c)
    main_window.show()

    sys.exit(app.exec_())

I hope I haven't stripped it too much. If you have questions, go ahead.

回答1:

You already answered your question yourself :), so I just summarize it:

  • QProcess.execute (it is static, btw, so no need to create a QProcess object in this case) starts the child process and waits for the child process to finish - while the child process is running, the calling process is blocked and your UI is frozen

  • To achieve what you require, use QProcess.start() to launch the child process asynchronously, and connect the corresponding signals started() and finished(). See the notes in the documentation for some details to consider.