【问题标题】:How to run PyQt5 GUIs in non-blocking threads?如何在非阻塞线程中运行 PyQt5 GUI?
【发布时间】:2019-01-18 22:41:50
【问题描述】:

我有一个 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 控制台会话中工作,而多处理在这种情况下不起作用。

【问题讨论】:

  • 您不需要单独的线程或进程。只需创建 一个 QApplication,然后打开多个窗口。显然,在控制台外运行时还必须调用app.exec_(),否则脚本将立即结束。
  • @ekhumoro 我在哪里调用 app.exec__() ?如果我将 app.exec__() 添加到 show() 然后它会阻塞,直到我关闭窗口。我希望能够调用 show() 两次以打开 2 个 GUI 窗口并让它们在后续代码运行时都可用
  • @ekhumoro 我希望能够在控制台会话期间通过重复调用 show() 创建任意数量的 GUI 实例,而不仅仅是 2。一旦你调用,你的代码仍然会阻塞app.exec_(),对吧?我希望在不必先关闭 GUI 的情况下运行后续代码。
  • @eyllanesc 我不想继续创建小部件。我的 GUI 类是一个电子表格,其目的是让您在使用 Pandas 进行数据分析时在 GUI 窗口中查看 Pandas DataFrames。因此,当我在控制台中输入命令时,需要打开 GUI 以供我查看。我希望我的模块被导入,然后我希望能够调用 show(df) 来弹出一个或多个电子表格,并且在运行后仍然有代码,无论是在交互式会话中还是在单个脚本中。
  • 我添加了一个答案,它应该可以满足您的需求。它对我来说很好,但我只使用标准控制台进行测试,所以我不能保证它可以在所有 python IDE 中工作。

标签: python-3.x multithreading pyqt pyqt5


【解决方案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_())

【讨论】:

  • 我认为这不能解决问题,因为在 GUI 实例运行时仍然没有其他代码可以运行。 sys.exit(QtWidgets.QApplication.instance().exec_()) 行阻塞。如果我在 iPython 会话中使用您的代码,窗口会立即冻结。如果我导入到正常的控制台会话,它们不会冻结并允许我继续输入,但它们会在处理过程中冻结。我需要 GUI 在其他代码运行时保持功能
  • 你的情况如下:screencast.com/t/cqU8fH6w 这就是我想要它做的事情(但是这个解决方案使用了在交互式中不起作用的多处理):screencast.com/t/rM0usLfc
  • @Esostack 像这样不断增加额外的要求真的是不能接受的。我的回答完全符合您在问题中的要求:“我希望能够通过从交互式会话或脚本调用 show() 函数来创建 GUI 的多个实例(在我的示例中为 MainWindow),并且我希望这些窗口在后续代码运行时留在我的屏幕上”。请重写您的整个问题,以便详细说明所有您的要求。人们不应该费尽千辛万苦地了解你想要什么。
  • 我没有添加额外的要求或不清楚。我什至给出了我想要的视频演示。你真的认为我移动球门柱是因为我不认为这是“非阻塞”和“留在我的屏幕上”吗? screencast.com/t/aUjAW9MupynQ cmets 中没有我的问题或标题中未说明的内容。无论如何......我现在已经使用 pathos 的多进程库解决了它,但我不确定它有多稳定,如果没有人有更好的解决方案,我会发布它。
  • 嘿,所以我在 7 个月后回到这个问题,因为我刚刚了解到在交互式会话期间窗口变得无响应是 IPython 特有的问题。我的困惑是,我认为您在暗示它们会冻结是意料之中的,并且它符合我“留在屏幕上”的要求!
【解决方案2】:

这是@ekhumoro 答案的一个小补充。我没有足够的声誉来添加评论,所以我不得不写这个作为答案。

@ekhumoro 的回答几乎完全回答了@Esostack 的问题,但在 Ipython 控制台中不起作用。在自己寻找这个问题的答案数小时后,我在一个三年前的帖子 (here) 中看到了来自@titusjan 的评论,也回应了@ekhumoro 的一个很好的答案。 @ekhumoro 的答案的缺失部分导致 Ipython 的 gui 窗口特别冻结,即 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")

【讨论】:

  • 啊,是的,我实际上是在我自己的项目中发现的,但忘记在这里发布。感谢您的补充。
  • 感谢@Esostack 的编辑,我认为很高兴一次看到所有解决方案,它实际上回答了我的另一个问题(但还没有问)。因为from PyQt5 import QtWebEngineWidgets 必须在激活IPython 中的qt5 gui 支持之前运行(因为它必须在QCoreApplication 实例创建之前导入),所以这是在导入之后运行的完美选择.作为旁注 - ipython.enable_gui('qt5') 也可以代替 'ipython.magic("gui qt5")` 但我不知道其中一个比另一个有什么优势。
猜你喜欢
  • 1970-01-01
  • 2012-06-11
  • 2015-05-07
  • 1970-01-01
  • 2021-06-14
  • 1970-01-01
  • 1970-01-01
  • 2012-10-03
  • 2014-05-24
相关资源
最近更新 更多