如何在后台其他进程 运行 时显示 PyQt5/PySide2 对话框
How to display a PyQt5/PySide2 dialog box while other processes run in the background
概述:我有一个相当大的 GUI 程序,内置于 PyQt5/PySide2 中,可以执行大量操作。有些过程非常快,有些过程最多可能需要一分钟左右才能完成。我将我的程序设置为显示一种 'Please Wait...' 对话框,用于任何花费超过一秒左右的进程。我使用 threading
模块结合 PyQt5/PySide2 内置的 signals
来完成此操作。虽然它工作正常,但我发现我也无法使用 concurrent.futures
运行 线程,因为这些线程模块不能很好地协同工作。 concurrent.futures
没有处理信号,并且 threading
在连续处理大量函数时通常要慢得多。所以我会在合适的时候选择使用其中任何一个。
问题:但是,我开始遇到这样的情况,虽然会出现对话框,但它不会在框中显示文本,这通常是 "PLEASE WAIT, PROCESSING REQUEST" 行的消息。本质上,显示文本 was/is 的过程会被暂停,直到基础过程完成。该过程完成后,只要 window 未关闭,它就会显示文本。
障碍:除了上述情况外,我还重新编写了部分信号和对话框显示 类,从表面上看,它似乎按预期运行,我已经包含了完整的代码的一个基本例子。但是,当我将那些确切的方法应用到我的较大程序时,它会在第一次开始显示对话框时自行关闭。
问题:我想知道我是否在下面的示例中遗漏了一个基本元素或概念,当将其应用于更大的程序时,可能会给我带来一些问题。我正在寻找此示例中的潜在危险信号。
编辑:当你运行这个例子时,点击确定按钮来测试对话框和线程。 'main' 框将消失,只应显示对话框。 3 秒后,对话框消失,出现另一个框。这非常类似于我的大型程序的实际功能。本质上,当您启动程序时 'login',因此开始菜单消失,然后实际程序初始化并加载。正如您在这个示例中看到的那样,该框将短暂显示然后消失,这就是我的程序中发生的情况。用户登录,但在登录后的一秒钟内,程序关闭。我已经尝试了如何加载 window 的变体。下面列出的方法实际上至少显示了它,但我使用的其他方法只会导致 QApplication::exec: Must be called from the main thread
错误。我已经尝试了一些其他方法并在下面列出了它们,但显然 none 其中有效。
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()
您所在的那条线 MASTER.Second_UI.show()
可能就是您遇到问题的地方。您在主线程中创建了一个实例,这很好,但是您需要在该 class 中创建一个可以发出 show()
方法的信号。使 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)
然后在你的 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
最后,在您的 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()
这应该按照您描述的进度进行,并将最后一个 window 显示在最后。
概述:我有一个相当大的 GUI 程序,内置于 PyQt5/PySide2 中,可以执行大量操作。有些过程非常快,有些过程最多可能需要一分钟左右才能完成。我将我的程序设置为显示一种 'Please Wait...' 对话框,用于任何花费超过一秒左右的进程。我使用 threading
模块结合 PyQt5/PySide2 内置的 signals
来完成此操作。虽然它工作正常,但我发现我也无法使用 concurrent.futures
运行 线程,因为这些线程模块不能很好地协同工作。 concurrent.futures
没有处理信号,并且 threading
在连续处理大量函数时通常要慢得多。所以我会在合适的时候选择使用其中任何一个。
问题:但是,我开始遇到这样的情况,虽然会出现对话框,但它不会在框中显示文本,这通常是 "PLEASE WAIT, PROCESSING REQUEST" 行的消息。本质上,显示文本 was/is 的过程会被暂停,直到基础过程完成。该过程完成后,只要 window 未关闭,它就会显示文本。
障碍:除了上述情况外,我还重新编写了部分信号和对话框显示 类,从表面上看,它似乎按预期运行,我已经包含了完整的代码的一个基本例子。但是,当我将那些确切的方法应用到我的较大程序时,它会在第一次开始显示对话框时自行关闭。
问题:我想知道我是否在下面的示例中遗漏了一个基本元素或概念,当将其应用于更大的程序时,可能会给我带来一些问题。我正在寻找此示例中的潜在危险信号。
编辑:当你运行这个例子时,点击确定按钮来测试对话框和线程。 'main' 框将消失,只应显示对话框。 3 秒后,对话框消失,出现另一个框。这非常类似于我的大型程序的实际功能。本质上,当您启动程序时 'login',因此开始菜单消失,然后实际程序初始化并加载。正如您在这个示例中看到的那样,该框将短暂显示然后消失,这就是我的程序中发生的情况。用户登录,但在登录后的一秒钟内,程序关闭。我已经尝试了如何加载 window 的变体。下面列出的方法实际上至少显示了它,但我使用的其他方法只会导致 QApplication::exec: Must be called from the main thread
错误。我已经尝试了一些其他方法并在下面列出了它们,但显然 none 其中有效。
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()
您所在的那条线 MASTER.Second_UI.show()
可能就是您遇到问题的地方。您在主线程中创建了一个实例,这很好,但是您需要在该 class 中创建一个可以发出 show()
方法的信号。使 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)
然后在你的 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
最后,在您的 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()
这应该按照您描述的进度进行,并将最后一个 window 显示在最后。