Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
201 views
in Technique[技术] by (71.8m points)

python - How can I launch pyqt GUI multiple times consequtively in a process?

How can I architect code to run a pyqt GUI multiple times consecutively in a process?

(pyqtgraph specifically, if that is relevant)

The context

A python script that performs long running data capture on measurement equipment (a big for loop). During each capture iteration a new GUI appear and displays live data from the measurement equipment to the user, while the main capture code is running.

I'd like to do something like this:

for setting in settings:
  measurement_equipment.start(setting)
  gui = LiveDataStreamGUI(measurement_equipment)
  gui.display()
  measurement_equipment.capture_data(300) #may take hours
  gui.close()

The main issue

I'd like the data capture code to be the main thread. However pyqt doesn't seems to allow this architecture, as its app.exec_() is a blocking call, allowing a GUI to be created only once per process (e.g., in gui.display() above).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

An application is an executable process that runs on one or more foreground threads each of which can also start background threads to perform parallel operations or operations without blocking the calling thread. An application will terminate after all foreground threads have ended, therefore, you need at least one foreground thread which in your case is created when you call the app.exec_() statement. In a GUI application, this is the UI thread where you should create and display the main window and any other UI widget. Qt will automatically terminate your application process when all widgets are closed.

IMHO, you should try to follow the normal flow described above as much as possible, the workflow could be as follows:

Start Application > Create main window > Start a background thread for each calculation > Send progress to UI thread > Show results in a window after each calculation is finished > Close all windows > End application

Also, you should use ThreadPool to make sure you don't run out of resources.

Here is a complete example:

import sys
import time
import PyQt5

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QRunnable, pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QDialog


class CaptureDataTaskStatus(QObject):
    progress = pyqtSignal(int, int)  # This signal is used to report progress to the UI thread.
    captureDataFinished = pyqtSignal(dict)  # Assuming your result is a dict, this can be a class, a number, etc..


class CaptureDataTask(QRunnable):
    def __init__(self, num_measurements):
        super().__init__()

        self.num_measurements = num_measurements
        self.status = CaptureDataTaskStatus()

    def run(self):
        for i in range(0, self.num_measurements):
            # Report progress
            self.status.progress.emit(i + 1, self.num_measurements)
            # Make your equipment measurement here
            time.sleep(0.1) # Wait for some time to mimic a long action

        # At the end you will have a result, for example
        result = {'a': 1, 'b': 2, 'c': 3}

        # Send it to the UI thread
        self.status.captureDataFinished.emit(result)


class ResultWindow(QWidget):
    def __init__(self, result):
        super().__init__()

        # Display your result using widgets...
        self.result = result

        # For this example I will just print the dict values to the console
        print('a: {}'.format(result['a']))
        print('b: {}'.format(result['b']))
        print('c: {}'.format(result['c']))


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.result_windows = []

        self.thread_pool = QtCore.QThreadPool().globalInstance()

        # Change the following to suit your needs (I just put 1 here so you can see each task opening a window while the others are still running)
        self.thread_pool.setMaxThreadCount(1)

        # You could also start by clicking a button, menu, etc..
        self.start_capturing_data()

    def start_capturing_data(self):
        # Here you start data capture tasks as needed (I just start 3 as an example)
        for setting in range(0, 3):
            capture_data_task = CaptureDataTask(300)
            capture_data_task.status.progress.connect(self.capture_data_progress)
            capture_data_task.status.captureDataFinished.connect(self.capture_data_finished)
            self.thread_pool.globalInstance().start(capture_data_task)


    def capture_data_progress(self, current, total):
        # Update progress bar, label etc... for this example I will just print them to the console
        print('Current: {}'.format(current))
        print('Total: {}'.format(total))

    def capture_data_finished(self, result):
        result_window = ResultWindow(result)
        self.result_windows.append(result_window)
        result_window.show()


class App(QApplication):
    """Main application wrapper, loads and shows the main window"""

    def __init__(self, sys_argv):
        super().__init__(sys_argv)

        self.main_window = MainWindow()
        self.main_window.show()


if __name__ == '__main__':
    app = App(sys.argv)   
    sys.exit(app.exec_())

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...