如何在非阻塞线程中运行PyQt5 GUI?

时间:2019-01-18 22:41:50

标签: python-3.x multithreading pyqt pyqt5

我有一个PyQt5 GUI类,我希望能够从交互式控制台或正常运行中创建多个实例。我需要这些GUI是非阻塞的,以便可以在后续代码运行时使用它们。

我已经尝试过像这样的答案在每个GUI的单独线程中调用app.exec __(),但是该程序有时会崩溃,因为对答案的评论警告它会:
Run pyQT GUI main app in seperate Thread

现在我正在尝试根据以下答案制作以下代码:
Run Pyqt GUI main app as a separate, non-blocking process

但是当我运行它时,窗口弹出并立即消失

import sys
from PyQt5 import QtWidgets, QtGui, QtCore
import time

class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        # call super class constructor
        super(MainWindow, self).__init__()
        # build the objects one by one
        layout = QtWidgets.QVBoxLayout(self)
        self.pb_load = QtWidgets.QPushButton('Load')
        self.pb_clear= QtWidgets.QPushButton('Clear')
        self.edit = QtWidgets.QTextEdit()
        layout.addWidget(self.edit)
        layout.addWidget(self.pb_load)
        layout.addWidget(self.pb_clear)
        # connect the callbacks to the push-buttons
        self.pb_load.clicked.connect(self.callback_pb_load)
        self.pb_clear.clicked.connect(self.callback_pb_clear)

    def callback_pb_load(self):
        self.edit.append('hello world')
    def callback_pb_clear(self):
        self.edit.clear()

def show():
    app = QtWidgets.QApplication.instance()
    if not app:
        app = QtWidgets.QApplication(sys.argv)
    win = MainWindow()
    win.show()

if __name__ == '__main__':
    show()
    show()

编辑-我看不到这个问题是如何重复的。 “重复”的问题只是轻微的相关,根本无法解决我的问题。

我希望能够通过从交互式会话或脚本中调用show()函数来创建GUI的多个实例(在我的示例中为MainWindow),并且我希望这些窗口在后续代码执行时保留在屏幕上运行。

EDIT2-当我将代码作为脚本运行时,可以通过使用多处理来做我想做的事情,请参见此演示:
https://www.screencast.com/t/5WvJNVSLm9OR

但是,我仍然需要帮助,因为我希望它也可以在交互式Python控制台会话中工作,并且在这种情况下,多处理不起作用。

3 个答案:

答案 0 :(得分:1)

为此无需使用单独的线程或进程。在python交互式会话中导入脚本时,您只需要一种方法来维护对每个新窗口的引用。可以使用一个简单的列表。从命令行运行脚本时,仅需要显式启动事件循环。在交互式会话中,它将由PyQt自动处理。

这是此方法的实现:

...
_cache = []

def show(title=''):
    if QtWidgets.QApplication.instance() is None:
        _cache.append(QtWidgets.QApplication(sys.argv))
    win = MainWindow()
    win.setWindowTitle(title)
    win.setAttribute(QtCore.Qt.WA_DeleteOnClose)
    win.destroyed.connect(lambda: _cache.remove(win))
    _cache.append(win)
    win.show()

if __name__ == '__main__':

    show('Foo')
    show('Bar')

    sys.exit(QtWidgets.QApplication.instance().exec_())

答案 1 :(得分:1)

我最终使用多重处理在一个单独的进程中运行每个GUI实例,然后使用multiprocess module使它在交互模式下工作

main.py

import time
import multiprocess

def start_gui():
    import sys
    from PyQt5 import QtWidgets

    # Set up QApplication
    app = QtWidgets.QApplication.instance()
    if not app:
        app = QtWidgets.QApplication(sys.argv)

    # Make GUi
    import gui
    win = gui.MainWindow()

    app.exec_()

def show():
    thread = multiprocess.Process(target=start_gui)
    thread.start()

if __name__ == '__main__':
    show()
    print('This happens now')
    show()

    # Task that takes 5 seconds
    time.sleep(5)

    print('This happens later')

gui.py

from PyQt5 import QtWidgets

class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        # call super class constructor
        super(MainWindow, self).__init__()
        # build the objects one by one
        layout = QtWidgets.QVBoxLayout(self)
        self.pb_load = QtWidgets.QPushButton('Load')
        self.pb_clear= QtWidgets.QPushButton('Clear')
        self.edit = QtWidgets.QTextEdit()
        layout.addWidget(self.edit)
        layout.addWidget(self.pb_load)
        layout.addWidget(self.pb_clear)
        # connect the callbacks to the push-buttons
        self.pb_load.clicked.connect(self.callback_pb_load)
        self.pb_clear.clicked.connect(self.callback_pb_clear)

        self.show()

    def callback_pb_load(self):
        self.edit.append('hello world')
    def callback_pb_clear(self):
        self.edit.clear()

答案 2 :(得分:1)

这是@ekhumoro的答案的次要附录。我没有足够的声誉,只能添加评论,所以我不得不将此作为答案。

@ekhumoro的答案几乎完全回答了@Esostack的问题,但是在Ipython控制台中不起作用。在经过数小时的自我搜索后,我在一个三岁的主题(here)中遇到了@titusjan的评论,同时也回复了@ekhumoro的一个好的答案。 @ekhumoro答案的缺失部分导致gui窗口对于Ipython尤其是冻结,特别是Ipython应该设置为在启动时或运行后使用qt gui。


要与Ipython配合使用:

使用ipython --gui=qt5

启动Ipython

在运行中的Ipython控制台中,运行魔术命令%gui qt5

要通过Python脚本对其进行修复,可以运行此功能

def fix_ipython():
    from IPython import get_ipython

    ipython = get_ipython()
    if ipython is not None:
        ipython.magic("gui qt5")