如何在不冻结界面的情况下在 QLabel 中进行计算时实时更新文本?

How can I update the text in real time while doing the calculations in a QLabel, without the interface freezing?

我对 Q Label 中的实时更新有疑问,因为当我单击 "start" 按钮时,屏幕在执行计算时会保持静止几秒钟,但我需要的是是在 QLabel 中,在进行模拟时会显示状态消息。我尝试过使用线程,但我不太了解它,因为它无法解决问题,如果有人可以帮助我,我将不胜感激,因为我在 Pyqt5 中处理的线程问题不多。 (我不明白) 非常感谢

附上代码和界面图片:


接口

执行前:

执行中:

执行后:


Codigo

import sys
from PyQt5.QtWidgets import QStyleFactory,QApplication, QMainWindow,QFileDialog
from PyQt5.uic import loadUi
from PyQt5.QtGui import QTextCursor
from PyQt5.QtCore import *
import random
import time
import pandas as pd


parametros = [['Area', 0, 5]]

class simulacion(QMainWindow):

    def __init__(self, parent=None):

        QMainWindow.__init__(self, parent)
        loadUi('simulacion.ui', self)
        self.setStyle(QStyleFactory.create('Fusion'))
        self.numsim = 10000000

        self.pushButton.clicked.connect(self.calibracion)
        self.pushButton_2.clicked.connect(self.save)

    def cerrar(self):
        self.close()

    def calibracion(self):
        self.montecarlo(self.numsim)

    def generar_aleatorio(self):
        aleatorio = []
        for i in range(len(parametros)):
            aleatorio.append(random.uniform(parametros[i][1],parametros[i][2]))
        return aleatorio

    def area(self,x1):
        area = 3.1416 * x1**2
        return area

    def estado(self,starttime,last_print,contador, n, area):
        global ult_print
        acttime = time.time()
        if acttime - last_print >= 2:
            avg_time_per_run = (acttime - starttime) / (contador + 1)
            timestr = time.strftime("%H:%M:%S", time.gmtime(round(avg_time_per_run * (n - (contador + 1)))))

            text = ('Simulacion %i de %i - Tiempo estimado restante: %s\n' % (contador, n,timestr)+'Area = %5.3f' % (area)+'\n\n')

            self.textEdit.moveCursor(QTextCursor.End)
            self.textEdit.insertPlainText(text)
            self.textEdit.moveCursor(QTextCursor.End)

            ult_print = time.time()
            return text

    def montecarlo(self,n):
        QApplication.processEvents()
        global ult_print
        text='Iniciando iteraciones con {} repeticiones...\n\n'.format(n)
        self.textEdit.setText(text)
        self.textEdit.moveCursor(QTextCursor.End)
        ult_print = time.time()
        starttime = time.time()
        contador = 0
        self.data=[]
        self.num_sim=[]

        QApplication.setOverrideCursor(Qt.WaitCursor)

        while contador < n:
            contador +=1
            #Generar el numero aleatorio
            z = self.generar_aleatorio()
            #Simulacion del modelo con el numero aleatorio
            y = self.area(z[0])
            #Calculo de la funcion objetivo
            self.estado(starttime,ult_print,contador,n,y)

        QApplication.setOverrideCursor(Qt.CustomCursor)


    def save(self):
        file,_=QFileDialog.getSaveFileName(self,'Guardar Archivo de Simulación','','(*.csv)')

        if file:
            columns= []
            for valor in self.num_sim:
                columns.append('Simulación '+str(valor))
            #print(columns)

            df = pd.DataFrame(self.data,index=columns)
            a = df.transpose()
            a.to_csv(file,sep=';',index=False,encoding='utf-8')

if __name__ == "__main__":
    app = QApplication(sys.argv)
    widget = simulacion()
    widget.show()
    sys.exit(app.exec_())

这里我展示了simulacion.ui

调用的单独代码

Codigo

import sys
from PyQt5.QtWidgets import QStyleFactory,QApplication, 
QMainWindow,QFileDialog
from PyQt5.uic import loadUi
from PyQt5.QtGui import QTextCursor
from PyQt5.QtCore import *
import random
import time
import pandas as pd

'''This part of the code calls the window designed in QTDesigner'''

class simulacion(QMainWindow):

    def __init__(self, parent=None):

        QMainWindow.__init__(self, parent)
        loadUi('simulacion.ui', self)
        self.setStyle(QStyleFactory.create('Fusion'))
        self.numsim = 10000000

        self.pushButton.clicked.connect(self.calibracion)
        self.pushButton_2.clicked.connect(self.save)

    def cerrar(self):
        self.close()

    def calibracion(self):
        self.montecarlo(self.numsim)

    def save(self, data):
        file,_=QFileDialog.getSaveFileName(self,'Guardar Archivo de Simulación','','(*.csv)')
        if file:
            df = pd.DataFrame(data)
            df.to_csv(file,sep=';',index=False,encoding='utf-8')
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = simulacion()
widget.show()
sys.exit(app.exec_())

这部分代码是我描述将在 Monte Carlo 算法中使用的函数的地方。其中指定的文本应显示在 QTextEdit 中 值得一提的是,在 Montecarlo 函数中,生成了数据列表,这是保持所有模拟执行的原因。此变量是必需的,以便可以执行模拟 class 中的保存功能

parametros = [['Area', 0, 5]]

def generar_aleatorio():
    aleatorio = []
    for i in range(len(parametros)):
        aleatorio.append(random.uniform(parametros[i][1],parametros[i][2]))
        return aleatorio

def area(x1):
    area = 3.1416 * x1**2
    return area

def estado(starttime,last_print,contador, n, area):
    global ult_print
    acttime = time.time()
    if acttime - last_print >= 2:
        avg_time_per_run = (acttime - starttime) / (contador + 1)
        timestr = time.strftime("%H:%M:%S", time.gmtime(round(avg_time_per_run * (n - (contador + 1)))))

        text = ('Simulacion %i de %i - Tiempo estimado restante: %s\n' % (contador, n,timestr)+'Area = %5.3f' % (area)+'\n\n')

        self.textEdit.moveCursor(QTextCursor.End)
        self.textEdit.insertPlainText(text)
        self.textEdit.moveCursor(QTextCursor.End)

        ult_print = time.time()
        return text

def montecarlo(n):
        global ult_print
        text='Iniciando iteraciones con {} repeticiones...\n\n'.format(n)
        #self.textEdit.setText(text)
        #self.textEdit.moveCursor(QTextCursor.End)
        ult_print = time.time()
        starttime = time.time()
        contador = 0
        data=[]
        num_sim=[]
        #QApplication.setOverrideCursor(Qt.WaitCursor)

        while contador < n:
            contador +=1
            #Generar el numero aleatorio
            z = generar_aleatorio()
            #Simulacion del modelo con el numero aleatorio
            y = area(z[0])
            #Calculo de la funcion objetivo
            estado(starttime,ult_print,contador,n,y)
            data.append(list(z+y))

        #QApplication.setOverrideCursor(Qt.CustomCursor)

适当的解决方案是在另一个线程中执行阻塞任务,并通过信号将数据发送到主线程中的GUI,Qt禁止从非主线程的GUI更新GUI,使用processEvents 强制 GUI 更新一些不能保证正确操作的参数,您可以在以下 link 中阅读有关此主题的更多信息:Should I use QCoreApplication::processEvents() or QApplication::processEvents()?.

在下面的示例中,我将在 2 个信号旁边使用本机 python 线程,一个发送文本,另一个发送数据。

import random
import sys
import time
from threading import Thread
import pandas as pd

from PyQt5.QtCore import QObject, pyqtSignal, Qt
from PyQt5.QtWidgets import QApplication, QFileDialog, QStyleFactory, QMainWindow
from PyQt5.uic import loadUi

parametros = [['Area', 0, 5]]


def generar_aleatorio():
    return random.uniform(*parametros[0][1:])


def area(x1):
    area = 3.1416 * x1 ** 2
    return area


class Helper(QObject):
    send_signal = pyqtSignal(str)
    data_signal = pyqtSignal(list)


helper = Helper()


def estado(starttime, last_print, contador, n, area):
    acttime = time.time()
    if acttime - last_print <= 2:
        avg_time_per_run = (acttime - starttime) / (contador + 1)
        timestr = time.strftime("%H:%M:%S", time.gmtime(round(avg_time_per_run * (n - (contador + 1)))))

        text = 'Simulacion %i de %i - Tiempo estimado restante: %s\n' % (contador, n, timestr) \
               + 'Area = %5.3f\n\n' % area
        helper.send_signal.emit(text)


def montecarlo(n):
    data = []
    text = 'Iniciando iteraciones con {} repeticiones...\n\n'.format(n)
    helper.send_signal.emit(text)
    ult_print = time.time()
    starttime = time.time()

    for contador in range(n):
        z = generar_aleatorio()
        y = area(z)
        estado(starttime, ult_print, contador + 1, n, y)
        ult_print = time.time()
        time.sleep(0.001)
        data.append([z, y])
    helper.data_signal.emit(data)


class simulacion(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        loadUi('simulacion.ui', self)
        self.setStyle(QStyleFactory.create('Fusion'))
        self.numsim = 10000000

        self.pushButton.clicked.connect(self.calibracion)
        self.pushButton_2.clicked.connect(self.save)

    def calibracion(self):
        thread = Thread(target=montecarlo, args=(self.numsim,))
        helper.send_signal.connect(self.textEdit.append, Qt.QueuedConnection)
        helper.data_signal.connect(self.obtener_resultados)
        thread.start()

    def obtener_resultados(self, data):
        self.data = data

    def save(self, data):
        file, _ = QFileDialog.getSaveFileName(self, 'Guardar Archivo de Simulación', '', '(*.csv)')
        if file:
            df = pd.DataFrame(self.data)
            df.to_csv(file, sep=';', index=False, encoding='utf-8')


app = QApplication(sys.argv)
w = simulacion()
w.show()
sys.exit(app.exec_())