简体   繁体   English

如何在其他进程在后台运行时显示 PyQt5/PySide2 对话框

[英]How to display a PyQt5/PySide2 dialog box while other processes run in the background

Overview: I have a fairly large GUI program, built in PyQt5/PySide2 that performs a multitude of operations.概述:我有一个相当大的 GUI 程序,内置于 PyQt5/PySide2 中,可以执行多种操作。 Some processes are very quick and a few can take up to around a minute to complete.有些过程非常快,有些过程可能需要大约一分钟才能完成。 I had my program set up to display a sort of 'Please Wait...' dialog box for any process that took more than a second or so.我将我的程序设置为显示一种“请稍候...”对话框,以显示任何花费超过一秒左右的进程。 I did this using the threading module in conjunction with the signals built into PyQt5/PySide2.我将threading模块与 PyQt5/PySide2 中内置的signals结合使用。 While it worked fine, I found that I could not also run threading using concurrent.futures , because these threading modules did not function well together.虽然它工作得很好,但我发现我也不能使用concurrent.futures运行线程,因为这些线程模块没有 function 很好地结合在一起。 concurrent.futures did not handle the signals and the threading was generally much slower when processing a multitude of functions back-to-back. concurrent.futures不处理信号,并且在背靠背处理大量函数时threading通常要慢得多。 So I take my pick when it is appropriate to use either one.所以我会在合适的时候选择其中任何一个。

Problem: However, I started experiencing instances where, though the dialog box would appear, it would not display the text within the box, which usually was a message along the lines of "PLEASE WAIT, PROCESSING REQUEST".问题:然而,我开始遇到这样的情况,虽然对话框会出现,但它不会在框中显示文本,这通常是一条类似于“请等待,正在处理请求”的消息。 Essentially, the process of displaying the text was/is being held up until the underlying process finishes.本质上,显示文本的过程被/正在被搁置,直到底层过程完成。 After the process finishes, as long as the window isn't closed, it would then show the text.该过程完成后,只要 window 没有关闭,它就会显示文本。

Obstacle: In addition to the above described situation, I have re-written parts of the signal and dialog display classes, which on the surface, appear to function as expected and I have included the full code of a basic example.障碍:除了上述情况之外,我还重写了部分信号和对话显示类,从表面上看,它们如预期的那样出现在 function 中,并且我已经包含了一个基本示例的完整代码。 However, when I apply those exact methods to my larger program, it closes itself when it first begins to display the dialog box.但是,当我将这些确切的方法应用于我的大型程序时,它会在第一次开始显示对话框时自行关闭。

Question: I am wondering if I am missing a basic element or concept in this below example, which when applied to a much bigger program, possibly creates some issues for me.问题:我想知道我是否在下面的示例中遗漏了一个基本元素或概念,当应用于更大的程序时,可能会给我带来一些问题。 I am looking for the potential red flags in this example.我正在寻找此示例中的潜在危险信号。

EDIT: When you run this example, click on the OK button to test the dialog box and threading.编辑:运行此示例时,单击“确定”按钮以测试对话框和线程。 The 'main' box will disappear and only the dialog box should be displayed. “主”框将消失,只应显示对话框。 After the 3 seconds are up, the dialog box disappears and another box appears. 3 秒后,对话框消失,出现另一个框。 This closely resembles the actual functionality of my larger program.这与我的大型程序的实际功能非常相似。 Essentially, when you start up you 'login' to the program, so the start menu disappears and then the actual program initializes and loads up.本质上,当您启动时,您“登录”到程序,因此开始菜单消失,然后实际程序初始化并加载。 As you can see with this example, the box will display briefly then disappears and this is what happens in my program.正如您在此示例中看到的那样,该框将短暂显示然后消失,这就是我的程序中发生的情况。 A user logs in, but then within a second of logging in, the program closes.用户登录,但在登录后的一秒钟内,程序关闭。 I have tried variations on how to get the window to load.我已经尝试过如何让 window 加载的变体。 The one listed below actually displays it at least, but other methods I've used will just result in a QApplication::exec: Must be called from the main thread error.下面列出的至少实际上显示了它,但我使用的其他方法只会导致QApplication::exec: Must be called from the main thread错误。 I've tried a few other methods and listed them below, though obviously none of them work.我尝试了其他一些方法并在下面列出了它们,但显然它们都不起作用。

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()

The line where you have MASTER.Second_UI.show() Is probably where you're getting held up.您拥有MASTER.Second_UI.show()的那一行可能是您遇到问题的地方。 You created an instance in your main thread, which is good, but you'll need to create a signal in that class that you can emit the show() method.您在主线程中创建了一个实例,这很好,但您需要在 class 中创建一个信号,您可以发出show()方法。 Make the FakeBox class look like this:使FakeBox class 看起来像这样:

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)

And then in your MASTER class look like this:然后在您的MASTER class 中看起来像这样:

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

And then lastly, in your display_msg() function, change it to this:最后,在您的display_msg() function 中,将其更改为:

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()     

This should follow the progression as you described and will keep the last window displayed at the end.这应该按照您描述的进度进行,并将最后一个 window 显示在最后。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM