如何在一个进程中多次启动 pyqt GUI?
Posted
技术标签:
【中文标题】如何在一个进程中多次启动 pyqt GUI?【英文标题】:How can I launch pyqt GUI multiple times consequtively in a process? 【发布时间】:2019-05-05 06:04:00 【问题描述】:如何构建代码以在一个进程中连续多次运行 pyqt GUI?
(特别是 pyqtgraph,如果相关的话)
上下文
在测量设备上执行长时间运行的数据捕获的 python 脚本(一个大的 for 循环)。在每次捕获迭代期间,都会出现一个新的 GUI,并向用户显示来自测量设备的实时数据,同时主捕获代码正在运行。
我想做这样的事情:
for setting in settings:
measurement_equipment.start(setting)
gui = LiveDataStreamGUI(measurement_equipment)
gui.display()
measurement_equipment.capture_data(300) #may take hours
gui.close()
主要问题
我希望数据捕获代码成为主线程。然而 pyqt 似乎不允许这种架构,因为它的 app.exec_()
是一个阻塞调用,允许每个进程只创建一次 GUI(例如,在上面的 gui.display()
中)。
【问题讨论】:
我会查看线程库。创建一个线程池,池中的每个线程都显示一个图表。当用户杀死一个图表时,您将线程返回到池中,以便它可用于下一个。或者,您的主脚本可以使用os
/subprocess
模块触发进一步的进程。
那么,您是否希望在这样的 GUI 中每次迭代显示一次并实时显示测量数据?
【参考方案1】:
应用程序是在一个或多个前台线程上运行的可执行进程,每个前台线程还可以启动后台线程以执行并行操作或操作,而不会阻塞调用线程。应用程序将在所有前台线程结束后终止,因此,您需要至少一个前台线程,在您的情况下,该线程是在您调用 app.exec_()
语句时创建的。在 GUI 应用程序中,这是您应该创建和显示主窗口和任何其他 UI 小部件的 UI 线程。当所有小部件都关闭时,Qt 将自动终止您的应用程序进程。
恕我直言,你应该尽量按照上面描述的正常流程,工作流程可能如下:
启动应用程序 > 创建主窗口 > 为每个计算启动一个后台线程 > 将进度发送到 UI 线程 > 每次计算完成后在窗口中显示结果 > 关闭所有窗口 > 结束应用程序
另外,您应该使用ThreadPool
来确保您不会耗尽资源。
这是一个完整的例子:
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_())
【讨论】:
【参考方案2】:如果您希望您的 GUI 保持实时更新并且不被冻结,您有两种主要方法来做到这一点:
-
不时刷新 GUI 在耗时的函数中调用
QApplication.processEvents()
。
创建一个单独的线程(我的意思是,QThread
)在其中运行耗时的函数
我个人的偏好是选择后一种方式。 Here 是一个很好的 Qt 多线程入门教程。
看看你的代码:
...
gui.display()
measurement_equipment.capture_data(300) #may take hours
gui.close()
...
您似乎在gui.display
内调用app.exec_
。很有可能您将不得不解耦这两个函数并在gui.display
之外调用app.exec_
并在调用capture_data
之后调用。您还必须将新线程的finished
信号连接到gui.close
。它会是这样的:
...
gui.display() # dont call app.exec_ here
thread = QThread.create(measurement_equipment.capture_data, 300)
thread.finished.connect(gui.close)
app.exec_()
...
希望对你有帮助,不要迟到!!
【讨论】:
@Ivan,请问我的回答对你有用吗?我正在等待您的反馈。谢谢【参考方案3】:您只能拥有一个图形 GUI 线程。这意味着有一些线程在需要时捕获数据并与图形应用程序同步数据。 我们需要知道 GUI 数据显示是显示实时数据还是仅显示一次。
【讨论】:
以上是关于如何在一个进程中多次启动 pyqt GUI?的主要内容,如果未能解决你的问题,请参考以下文章
PyQt:如何在 Raspberry Pi 桌面启动时运行 GUI?