在进程运行时如何显示带有说明文本的QDialog框

问题描述

概述:我有一个基于GUI的程序,该程序具有内置在按钮和菜单选择项中的多种功能。许多过程都是瞬时的,但是其中一些过程很容易花费超过5、10甚至20秒的时间。

当前问题:当我有一个对话框显示时,它不会显示该框中的文本,例如“正在完成处理,请稍候...”

目标:在长时间运行的过程中,我想显示一个“请稍候”对话框,以便用户知道程序没有停止并且正在使用用户选择的功能

背景信息:GUI内置于pyside2(又名PyQt5)和Python 3.6.5

当前尝试:


方法1-最初,我使用threading模块来启动要运行的主要功能。在函数开始时,我将调用显示该对话框,该对话框将按预期显示,而该函数的其余部分将在后台处理/完成。

方法1问题-在程序变得更加复杂之前,此方法一直有效,并且我需要使用ThreadPoolExecutor来加快速度。 ThreadPoolExecutor和线程模块不能很好地运行,即使它们一起运行也将导致崩溃,并且没有错误消息,因此我不得不放弃该方法


方法2-我尝试使用QDialog框的setModal函数并称为exec_函数

方法2问题-对话框将显示显示所需的文本,但是setModal(False)无效,并且进程将暂停直到窗口关闭


方法3(使用当前方法)-在初始化ProcessRunning对话框窗口类时,我创建了一个pyside2信号,该信号接收一个字符串(该字符串是我要显示的消息)。调用长进程之前的前一行,我调用了信号的emit()函数,与之相连的是显示对话框的函数

方法3问题-窗口显示并且后台进程运行,但是窗口不显示文本,几乎就像后台进程阻止了该过程的一部分。


摘要:尽管问题标题表明我不知道如何显示QDialog框,但真正的问题是在窗口内显示指示用户“等待”的文本。我觉得由于当前的方法没有发生这种情况,因此我没有“正确”显示此框。因此,我正在寻找实现这一目标的“正确”方法

这是一个使用我完整程序中的概念的“简短”示例。 :

import sys
import math
import pyside2
from pyside2 import QtCore,QtGui,QtWidgets
import pandas

class Ui_functionRunning(object):
    def setupUi(self,functionRunning):
        functionRunning.setobjectName("functionRunning")
        functionRunning.resize(234,89)
        self.labelProcessstatus = QtWidgets.QLabel(functionRunning)
        self.labelProcessstatus.setGeometry(QtCore.QRect(10,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")

class FunctionRunning(pyside2.QtWidgets.QDialog,Ui_functionRunning):
    display_Box = pyside2.QtCore.Signal(str)

    def __init__(self):
        super(FunctionRunning,self).__init__()
        self.setupUi(self)

    def showDialog(self,displayText):
        MasterClass.process_running.labelProcessstatus.setText(displayText)
        MasterClass.process_running.show()

class Ui_MainWindow(object):
    def setupUi(self,MainWindow):
        MainWindow.setobjectName("MainWindow")
        MainWindow.resize(800,600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(335,255,126,23))
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0,800,21))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        MainWindow.setStatusBar(self.statusbar)
        self.pushButton.setText("PUSH TO TEST ")

class MainWindowUI(pyside2.QtWidgets.QMainWindow,Ui_MainWindow):
    def __init__(self):
        super(MainWindowUI,self).__init__()
        self.setupUi(self)
        self.pushButton.clicked.connect(self.test_function_running)

    def test_function_running(self):
        MasterClass.process_running.display_Box.emit('testing running and displaying a message')        
        df = pandas.DataFrame({'id': [0,1,2,3,4,5,6,7,8,9],'lon':[-121.28473,-121.29511,-121.32834,-121.29569,-121.29251,-121.25374,-121.28417,-121.29854,-121.21188,-121.25812],'lat': 
            [37.986450,37.911396,37.969345,37.923443,37.990696,37.975395,37.942062,37.993350,37.979430,37.975790]})

        `If your system processes all this too quickly to notice the problem,increase the value in 
         `this first loop,from 10 to something like 20 or 30    
        for x in range(10):
            for i in df.index:
                for r in df.index:
                    if df.loc[i,'lat'] != '':
                        df.loc[i,'disT_to_%s' % df.loc[i,'id']] = self.dist(float(df.loc[i,'lat']),float(df.loc[i,'lon']),float(df.loc[r,'lon']))
                print('%s pass completed - %s times through' % (i,x))
        
        print('finished calculating distances')
        MasterClass.process_running.labelProcessstatus.setText('All Done!')        

    def dist(self,lat_1,lon_1,lat_2,lon_2):
        if lat_1 != lat_2 and lon_1 != lon_2:
            val_1 = math.radians(90 - float(lat_1))
            val_2 = math.cos(val_1)
            val_3 = math.radians(90 - float(lat_2))
            val_4 = math.cos(val_3)
            val_5 = math.radians(90 - float(lat_1))
            val_6 = math.sin(val_5)
            val_7 = math.radians(90 - float(lat_2))
            val_8 = math.sin(val_7)
            val_9 = math.radians(float(lon_1) - float(lon_2))
            val_10 = math.cos(val_9)
            distance = round(math.acos(val_2 * val_4 + val_6 * val_8 * val_10) * 3958.756,1)
            return distance
        else:
            return 0

class MasterClass:

    def __init__(self):
        super(MasterClass,self).__init__()
        MasterClass.app = pyside2.QtWidgets.QApplication(sys.argv)
        MasterClass.process_running = FunctionRunning()
        MasterClass.process_running.display_Box.connect(MasterClass.process_running.showDialog)
        MasterClass.main_ui = MainWindowUI()
        MasterClass.main_ui.show()
        MasterClass.app.exec_()

if __name__ == '__main__':
    MasterClass()

解决方法

您引用的方法1可能有效,但需要注意一些事项;任何用于更新用户界面的调用都应通过PySide2.QtCore.Signal()

使用信号来完成

更改您的MainWindowUI类,使其看起来像这样(保留您的dist函数,无需在那里进行任何更改):

class MainWindowUI(PySide2.QtWidgets.QMainWindow,Ui_MainWindow):
    # Create your signal here
    initiate_function = PySide2.QtCore.Signal()

    def __init__(self):
        super(MainWindowUI,self).__init__()
        self.setupUi(self)
        self.pushButton.clicked.connect(self.test_function_running)

    def test_function_running(self):
        MasterClass.process_running.display_box.emit('testing running and displaying a message')

        def test():  
            df = pandas.DataFrame({'id': [0,1,2,3,4,5,6,7,8,9],'lon':[-121.28473,-121.29511,-121.32834,-121.29569,-121.29251,-121.25374,-121.28417,-121.29854,-121.21188,-121.25812],'lat': 
                [37.986450,37.911396,37.969345,37.923443,37.990696,37.975395,37.942062,37.993350,37.979430,37.975790]})

            for x in range(10):
                for i in df.index:
                    for r in df.index:
                        if df.loc[i,'lat'] != '':
                            df.loc[i,'DIST_to_%s' % df.loc[i,'id']] = self.dist(float(df.loc[i,'lat']),float(df.loc[i,'lon']),float(df.loc[r,'lon']))
                    print('%s pass completed - %s times through' % (i,x))
                    # You could even update you dialog box mid-process
                    MasterClass.process_running.labelProcessStatus.setText('%s pass completed - %s times through' % (i,x))        
            
            print('finished calculating distances')
            # Below,the signal is emitted and simultaneously runs the function assigned below,in the MasterClass __init__
            MasterClass.main_ui.initiate_function.emit()
            MasterClass.process_running.labelProcessStatus.setText('All Done!')        
        
        t = threading.Thread(target=test)
        t.daemon = True
        t.start()
        
    def update_ui(self):
        print('Updating UI here')
        self.pushButton.setText('New Button Text')

现在更新您的MasterClass如下:

class MasterClass:

    def __init__(self):
        super(MasterClass,self).__init__()
        MasterClass.app = PySide2.QtWidgets.QApplication(sys.argv)
        MasterClass.process_running = FunctionRunning()
        MasterClass.process_running.display_box.connect(MasterClass.process_running.showDialog)
        MasterClass.main_ui = MainWindowUI()
        # After the MasterClass object is initialized,you can connect a function to the signal that was added to the MainWindowUI class
        MasterClass.main_ui.initiate_function.connect(MasterClass.main_ui.update_ui)
        # update_ui is the new function added above,all it does is change the text on the button,but it could be more complex if needed
        MasterClass.main_ui.show()
        MasterClass.app.exec_()

这应该有助于在PySide2中使用线程,也可以解决threadingThreadPoolExecutor遇到的问题。因此,这里的主要要点是,当您需要在主线程中运行某个进程时(例如,对UI进行更新),请使用信号。它可以用于其他用途,但是仅在需要刷新UI时绝对必要。