【问题标题】:How to display a PyQt5/PySide2 dialog box while other processes run in the background如何在其他进程在后台运行时显示 PyQt5/PySide2 对话框
【发布时间】:2020-04-16 22:17:40
【问题描述】:

概述:我有一个相当大的 GUI 程序,内置于 PyQt5/PySide2 中,可以执行多种操作。有些过程非常快,有些过程可能需要大约一分钟才能完成。我将我的程序设置为显示一种“请稍候...”对话框,以显示任何花费超过一秒左右的进程。我使用 threading 模块和 PyQt5/PySide2 中内置的 signals 来做到这一点。虽然它运行良好,但我发现我也无法使用concurrent.futures 运行线程,因为这些线程模块不能很好地协同工作。 concurrent.futures 没有处理信号,threading 在处理大量函数时通常要慢得多。所以我会在合适的时候选择其中任何一个。

问题:但是,我开始遇到这样的情况,虽然会出现对话框,但它不会显示框中的文本,通常是类似于“请稍候,正在处理请求”的消息。本质上,显示文本的过程被/正在被搁置,直到底层过程完成。该过程完成后,只要窗口没有关闭,它就会显示文本。

障碍:除了上述情况外,我还重写了信号和对话框显示类的部分内容,从表面上看,它们似乎按预期运行,并且我已经包含了一个基本示例的完整代码。但是,当我将这些确切的方法应用于我的较大程序时,它会在第一次开始显示对话框时自行关闭。

问题:我想知道我是否在下面的示例中遗漏了一个基本元素或概念,当应用于更大的程序时,可能会给我带来一些问题。我正在寻找此示例中的潜在危险信号。

编辑:运行此示例时,单击“确定”按钮以测试对话框和线程。 “主”框将消失,只应显示对话框。 3 秒后,对话框消失,出现另一个框。这与我的大型程序的实际功能非常相似。本质上,当您启动时,您“登录”到程序,因此开始菜单消失,然后实际程序初始化并加载。正如您在此示例中看到的那样,该框将短暂显示然后消失,这就是我的程序中发生的情况。用户登录,但在登录后的一秒钟内,程序关闭。我已经尝试过如何让窗口加载的变化。下面列出的实际上至少显示了它,但我使用的其他方法只会导致QApplication::exec: Must be called from the main thread 错误。我尝试了其他一些方法并在下面列出了它们,但显然它们都不起作用。

import sys
from PySide2 import QtWidgets, QtCore
import PySide2
import time
import threading

def startThread(functionName, *args, **kwargs):
    startThread.t = threading.Thread(target=functionName)
    startThread.t.daemon = True
    startThread.t.start()

class UserInput(object):
    def setupUi(self, get_user_input=None):
        # Basic shape
        self.width = 175
        get_user_input.setObjectName("get_user_input")
        get_user_input.resize(175, self.width)

        # Grid layout for the buttons
        self.buttonLayoutGrid = QtWidgets.QWidget(get_user_input)
        self.buttonLayoutGrid.setGeometry(QtCore.QRect(10, 115, 155, 50))
        self.buttonLayoutGrid.setObjectName("buttonLayoutGrid")
        self.buttonLayout = QtWidgets.QGridLayout(self.buttonLayoutGrid)
        self.buttonLayout.setContentsMargins(0, 0, 0, 0)
        self.buttonLayout.setObjectName("buttonLayout")
        self.buttonLayout.setAlignment(PySide2.QtCore.Qt.AlignLeft|PySide2.QtCore.Qt.AlignVCenter)
        # Buttons
        self.buttonOK = QtWidgets.QPushButton(self.buttonLayoutGrid)
        self.buttonOK.setObjectName("buttonOK")
        self.buttonOK.setText("OK")

class FakeBox(PySide2.QtWidgets.QDialog):

    def __init__(self):
        super(FakeBox, self).__init__()
        self.setupUi(self)
        self.buttonProcessCompleted.clicked.connect(self.close)

    def setupUi(self, box_details):
        box_details.setObjectName("box_details")
        box_details.resize(500, 89)
        self.labelProcessStatus = QtWidgets.QLabel(box_details)
        self.labelProcessStatus.setGeometry(QtCore.QRect(10, 0, 221, 51))
        self.labelProcessStatus.setAlignment(QtCore.Qt.AlignCenter)
        self.labelProcessStatus.setWordWrap(True)
        self.labelProcessStatus.setObjectName("labelProcessStatus")
        self.buttonProcessCompleted = QtWidgets.QPushButton(box_details)
        self.buttonProcessCompleted.setEnabled(False)
        self.buttonProcessCompleted.setGeometry(QtCore.QRect(60, 60, 111, 23))
        self.buttonProcessCompleted.setObjectName("buttonProcessCompleted")
        QtCore.QMetaObject.connectSlotsByName(box_details)

class FUNCTION_RUN(PySide2.QtWidgets.QDialog):
    display_dialog_window = PySide2.QtCore.Signal(str)
    display_process_complete = PySide2.QtCore.Signal(str)
    process_complete_no_msg = PySide2.QtCore.Signal()

    def __init__(self):
        super(FUNCTION_RUN, self).__init__()
        self.setupUi(self)
        self.buttonProcessCompleted.clicked.connect(self.close)

    def setupUi(self, functionRunning):
        functionRunning.setObjectName("functionRunning")
        functionRunning.resize(234, 89)
        self.labelProcessStatus = QtWidgets.QLabel(functionRunning)
        self.labelProcessStatus.setGeometry(QtCore.QRect(10, 0, 221, 51))
        self.labelProcessStatus.setAlignment(QtCore.Qt.AlignCenter)
        self.labelProcessStatus.setWordWrap(True)
        self.labelProcessStatus.setObjectName("labelProcessStatus")
        self.buttonProcessCompleted = QtWidgets.QPushButton(functionRunning)
        self.buttonProcessCompleted.setEnabled(False)
        self.buttonProcessCompleted.setGeometry(QtCore.QRect(60, 60, 111, 23))
        self.buttonProcessCompleted.setObjectName("buttonProcessCompleted")
        QtCore.QMetaObject.connectSlotsByName(functionRunning)

    def show_msg(self, msg_text=None):
        self.setWindowTitle("RUNNING")
        self.labelProcessStatus.setText(msg_text)
        self.setModal(False)
        self.show()

    def process_complete(self, msg_text=None):
        self.setWindowTitle("FINISHED")
        self.labelProcessStatus.setText(msg_text)
        self.buttonProcessCompleted.setText('OK')
        self.buttonProcessCompleted.setEnabled(True)
        self.setModal(False)
        self.show()

    def process_finished(self):
        self.hide()

class UserInputPrompt(PySide2.QtWidgets.QDialog, UserInput):

    def __init__(self):
        super(UserInputPrompt, self).__init__()
        self.setupUi(self)
        self.buttonOK.clicked.connect(self.scoreMass)

    def some_more_text(self):
        print('Some more...')

    def scoreMass(self):
        startThread(MASTER.UI.display_msg)

    def display_msg(self):
        def dialog():
            MASTER.UI.hide()
            m = ' Processing things...'
            MASTER.processing_window.display_dialog_window.emit(m)
            MASTER.UI.some_more_text()
            time.sleep(3)
            MASTER.Second_UI.show()
            MASTER.processing_window.process_complete_no_msg.emit()    

        dialog()    

class MASTER(object):
    def __init__(self):
        super(MASTER, self).__init__()
        MASTER.UI = UserInputPrompt()
        MASTER.Second_UI = FakeBox()
        MASTER.processing_window = FUNCTION_RUN()
        MASTER.processing_window.display_dialog_window.connect(MASTER.processing_window.show_msg)
        MASTER.processing_window.display_process_complete.connect(MASTER.processing_window.process_complete)
        MASTER.processing_window.process_complete_no_msg.connect(MASTER.processing_window.process_finished)
        MASTER.UI.show()
        app.exec_()

def main(): 
    MASTER()

if __name__ == '__main__':
    global app
    app = PySide2.QtWidgets.QApplication(sys.argv)
    main()

【问题讨论】:

  • 是的,您错过了基于 UI 的框架最重要的方面之一:主线程,通常负责显示/与 GUI 交互,必须从不 具有阻塞功能(如time.sleep);所有可能阻塞/时间/资源消耗的东西都必须移动到一个单独的线程。另外,考虑使用QProgressDialog
  • 有道理。我会做出调整并再试一次。谢谢!
  • 好吧,这只会让我能够摆脱错误。显示对话框仍然不正常。
  • 抱歉,您的评论并没有真正的用处:如果我们不知道这些错误是什么,我们该如何帮助您?我建议你用你正在使用的新代码创建一个新问题,或者至少用你现在正在使用的修改后的代码编辑这个问题。

标签: python multithreading pyqt5 pyside2 qdialog


【解决方案1】:

MASTER.Second_UI.show() 所在的行可能是您遇到问题的地方。您在主线程中创建了一个实例,这很好,但是您需要在该类中创建一个可以发出 show() 方法的信号。使FakeBox 类看起来像这样:

class FakeBox(PySide2.QtWidgets.QDialog):
    show_new_prompt = PySide2.QtCore.Signal()

    def __init__(self):
        super(FakeBox, self).__init__()
        self.setupUi(self)
        self.buttonProcessCompleted.clicked.connect(self.close)

然后在你的MASTER 类中看起来像这样:

class MASTER(object):
    def __init__(self):
        super(MASTER, self).__init__()
        MASTER.UI = UserInputPrompt()
        MASTER.Second_UI = FakeBox()
        MASTER.Second_UI.show_new_prompt.connect(MASTER.Second_UI.show)
        # Keeping everything after this line

最后,在您的 display_msg() 函数中,将其更改为:

def display_msg(self):
    def dialog():
        MASTER.UI.hide()
        m = ' Processing things...'
        MASTER.processing_window.display_dialog_window.emit(m)
        MASTER.UI.some_more_text()
        time.sleep(3)
        MASTER.processing_window.process_complete_no_msg.emit()    
        MASTER.Second_UI.show_new_prompt.emit()

    dialog()     

这应该遵循您所描述的进程,并将保持最后一个窗口显示在最后。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-20
    • 1970-01-01
    • 2019-07-22
    相关资源
    最近更新 更多