简体   繁体   中英

How to structure large pyqt5 GUI without subclassing QThread and using QPushButtons to do long-running tasks

I'm looking at creating a program with a PyQt5 GUI. The program will start with a UI with numerous buttons. These buttons will be used to open other programs/completed long running tasks. I know I need to use QThread, but I am unsure how to structure the programs so that it scales properly.

I've been at this for ages and have read numerous posts/tutorials. Most lean down the subclassing route. In the past, I have managed to create a working program subclassing QThread, but I have since read that this metholodogy is not preferred.

I have a feeling I should be creating a generic worker and passing in a function with *args and **kwargs, but that is not in my skillset yet.

I originally created a thread for each button during the GUI init, but that seemed like it was going to get out of hand quickly.

I am currently at the stage of creating a thread under the slot connected to the button.clicked signal. I am not sure if I then have to have a worker for each button or if I can/should make a generic worker and pass in a function. Note: I have tried to do this but have not been able to do it.

#Import standard modules
import sys

#Import third-party modles
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication, QPushButton, QVBoxLayout, QWidget

class Worker(QObject):

    #Custom signals?? or built-in QThread signals?
    started = pyqtSignal()    
    finished = pyqtSignal()

    def __init__(self): 
        super().__init__()
        self.started.emit()

    @pyqtSlot()
    def do_something(self):
            for _ in range(3):
                print('Threading...')
                QThread.sleep(1)
            self.finished.emit()

class Window(QMainWindow):

    def __init__(self):
        super().__init__()
        self.initUi()

    def initUi(self):
        #Create GUI
        self.centralWidget  = QWidget()
        self.setCentralWidget(self.centralWidget )  
        self.vertical_layout = QVBoxLayout(self.centralWidget)
        self.setWindowTitle('QThread Test')
        self.setGeometry(300, 300, 300, 50)
        self.button1=QPushButton("Task 1", self, clicked=self._task1_clicked)
        self.button2=QPushButton("Task 2", self, clicked=self._task2_clicked)
        self.vertical_layout.addWidget(self.button1)
        self.vertical_layout.addWidget(self.button2)
        self.vertical_layout.addStretch()

    def _task1_clicked(self):
        print('task1 clicked')

        #Create the worker
        self.my_worker = Worker()          

        #Create thread; needs to be done before connecting signals/slots
        self.task1_thread = QThread()

        #Move the worker to the thread
        self.my_worker.moveToThread(self.task1_thread)        

        #Connect worker and thread signals to slots
        self.task1_thread.started.connect(self._thread_started)
        self.task1_thread.started.connect(self.my_worker.do_something)
        self.my_worker.finished.connect(self._thread_finished) 

        #Start thread
        self.task1_thread.start() 

    def _task2_clicked(self):
        print('task2 clicked')

    def _thread_started(self):
        print('thread started')

    def _thread_finished(self):
        print('thread finished')
        self.my_worker.isRunning = False
        self.task1_thread.quit()
        self.task1_thread.wait()
        print('The thread is running: ' + str(self.task1_thread.isRunning()))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    form = Window()
    form.show()
    app.exec_()

The above seems to work, but I feel like I have stumbled on to it and it is not the correct way of doing this. I do not want this to be my 'go-to' method if it is completely wrong. I'd like to be able to generate more complicated (more buttons doing things) programs compared to a one button/one task program.

In addition, I can't seem to get the QThread started and finished signals to fire without basically making them custom built signals. This is one reason I think I am going about this wrong.

from PyQt5 import QtCore

class AsyncTask(QtCore.QThread):
    taskDone = QtCore.pyqtSignal(dict)
    def __init__(self, *, task, callback=None, parent = None):
        super().__init__(parent)
        self.task = task
        if callback != None:
            self.taskDone.connect(callback)
        if callback == None:
            callback = self.callback
        self.start()

    def run(self):
        try:
            result = self.task()
            print(result)
            self.taskDone.emit(result)
        except Exception as ex:
            print(ex)

    def callback(self):
        print('callback')

Please try code above, call like this: AsyncTask(task=yourTaskFunction, callback=yourCallbackFunction)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM