Python PyQT: How to call a GUI function from a wor

2019-02-07 11:18发布

问题:

I have a pyqt gui and calling a long process (ffmpeg) which I put on a separate thread to not block the gui. I then want to update a progress bar when one command of a longer list of commands finishes. The problem is, that I can't call a function in the gui thread out of the worker thread. So I let run a ticker in the worker thread, but when I update the progress bar with a while loop and reading the ticker value, the gui gets blocked again. How can I solve this. I used currently python threading and not Qthread. Thx for any help!

import threading, pexpect

self.cmd_list = ['ffmpeg -i file outfile','and so on']

self.stop_proc = False
self.executeCMD()

def spawn_ffmpeg_cmd(self):
    for cmd in self.cmd_list:
        if self.stop_proc == False:
            thread = pexpect.spawn(cmd)
            print "\nstarted: %s" % cmd
            cpl = thread.compile_pattern_list([pexpect.EOF,"frame= *\d+ fps=*\d+",'(.+)'])

            while True:
                i = thread.expect_list(cpl, timeout=None)
                if i == 0: # EOF
                    print "the sub process exited"
                    self.pgticker += 1
                    break
                elif i == 1:
                    frame_number_fps = thread.match.group(0)
                    print frame_number_fps
                    thread.close
                elif i == 2:
                    pass
    self.startButton.setEnabled(True)


def executeCMD(self):
    self.startButton.setEnabled(False)
    self.pgticker = 0
    threading.Thread(target=self.spawn_ffmpeg_cmd, name="_proc").start()


def stopprocess(self):
    self.stop_proc = True
    self.cmd_list = []
    os.system('pkill ffmpeg') 
    self.pgticker = len(self.cmd_list)
    self.startButton.setEnabled(True)


def updateProgress(self):  
    pgfactor = 100 / len(self.cmd_list)
    progress = 0.0
    progress = pgfactor*int(self.pgticker)
    self.progressBar.setProperty("value", progress)

回答1:

In short: Move to QThread and use Qt's signals and slots, they are the preferred way to communicate between threads.

This answer provides some examples how this could look like: https://stackoverflow.com/a/6789205/2319400

In your case, using the "SomeObject" version from the above could look like this:

class Worker(QtCore.QObject):

    madeProgress = QtCore.pyqtSignal([int])
    finished = QtCore.pyqtSignal()

    def __init__(self, cmdlist):
        self.cmdlist = cmdlist

    def run(self):
        for icmd, cmd in enumerate(self.cmdlist):
            # execute your work
            # processCommand(cmd)

            # signal that we've made progress
            self.madeProgress.emit(icmd)

        # emit the finished signal - we're done
        self.finished.emit()

Then move this worker to a QThread instance you create. Following the pattern from the linked answer, you can then connect the madeProgress signal to the setValue slot of a progressbar:

workerThread = QThread()
workerObject = Worker(cmdlist)
workerObject.moveToThread(workerThread)
workerThread.started.connect(workerObject.run)
workerObject.finished.connect(workerThread.quit)

# create a progressbar with min/max according to
# the length of your cmdlist
progressBar = QProgressBar()
progressBar.setRange(0, len(cmdlist))

# connect the worker's progress signal with the progressbar
workerObject.madeProgress.connect(progressBar.setValue)

# start the thread (starting your worker at the same time)
workerThread.start()