如何在 Python 中的每次迭代中使用 for 循环内的 TopLevel Tkinter 显示和更新包含结果的消息或标签?

How to Display and Update a Message or Label containg results using TopLevel Tkinter inside a for loop at every iteration in Python?

我正在使用 tkinter 构建 GUI Python 代码。我有一个在 for 循环内求解的 CSTR 模型;值在 for 循环内更新。我有四个正在更新的参数 T、Ca、Tc、q。我正在使用 TopLevel() 在 for 循环内的每次迭代中显示结果。换句话说,我希望每次迭代的结果在结果显示在显示器上时显示出来,一旦我进入另一个交互,我想再次打开一个新的 window 文件并使用更新值for 循环中的新计算值。我在 for 循环的迭代中使用了 destroy 函数。

请在下面找到我的代码(为了简单起见,我附上了部分代码):

 # Time Interval (min)
t = np.linspace(0,800,401) # every 2 seconds
duration=8000 # I also tried 2000,4000,6000,1000,etc.

    # Simulate CSTR
    for i in range(len(t)-1):
        # simulate one time period (0.02 sec each loop)
        ts = [t[i],t[i+1]]
        #read CSTR Process T and Ca (to be controlled)

        y = odeint(cstr,x0,ts,args=(u[i],u2[i],Tf,Caf))
        # retrieve measurements
        Ca[i+1] = y[-1][0]
        T[i+1] = y[-1][1]
        # insert measurement
        m.T.MEAS = T[i+1]
        m.Ca.MEAS=Ca[i+1]

        #run MPC controller

        # solve MPC
        m.solve(disp=True)

        # retrieve new Tc and q values

        u[i+1] = m.Tc.NEWVAL
        u2[i+1]=m.q.NEWVAL

        #run optimization

        x=optimizer(numa,numb,numc,numd,Tsp[i+1],Casp[i+1],u[i+1],u2[i+1],numi,numj,nume,numf,numg,numh,numk,numl) # or put T and Ca (not sp)


        #updating the setpoints of the controller from the optimizer

        #Set the setpoint temperature equal to the optimized Temperature

        Tsp[i+1]=x[1]

        m.T.SPHI = Tsp[i+1] + DT
        m.T.SPLO = Tsp[i+1] - DT

        #Set the setpoint conc. equal to the optimized conc.
        Casp[i+1]=x[4]

        m.Ca.SPHI = Casp[i+1] + DT
        m.Ca.SPLO = Casp[i+1] - DT

        # retrieve new Tc and q values

        #update Tc and q values from optimizer as targets


        f[i]=m.Tc.NEWVAL #MPC
        f2[i]=x[2] #Optimizer (target)

        z[i]=m.q.NEWVAL #MPC
        z2[i]=x[3] #Optimizer (target)



        x2=optimize(f[i],f2[i],nume,numf)# MINIMIZES THE ERROR DIFFERENCE BETWEEN MPC AND OPTIMIZER Tc value
        x3=optimize2(z[i],z2[i],numg,numh)# MINIMIZES THE ERROR DIFFERENCE BETWEEN MPC AND OPTIMIZER q value

        #updating the Tc and q MV values

        u[i+1] = x2
        u2[i+1]=x3

        # update initial conditions
        x0[0] = Ca[i+1]
        x0[1] = T[i+1]
        #to show the results on GUI

        top1=Toplevel()

        output1=" q optimum= %d m^3/s \n"%(z[i]) #takes the output q of MPC
        output1+="q target= %d m^3/s \n"%(z2[i])#takes the output of the optimizer
        output1+="Tc optimum= %d K \n"%(f[i])#takes the output Tc of MPC
        output1+="Tc target= %d K \n"%(f2[i])#takes the output of the optimizer
        top1.title('Manipulated Variables')

        Message(top1,text=output1,padx=30,pady=30).pack()
        top1.after(duration,top1.destroy)


实际发生的情况是 window 仅在 for 循环的所有运行完成后才打开(花费大量时间),而不是在 for 循环中的每个 iteration/calculation 都打开!如何在每次迭代时在 for 循环内显示 window 而不是在所有循环完成后显示?是否有某种功能或我可以手动强制显示 window 的东西?例如,我能够使用 plt 模块显示绘图,该模块在 for 循环内的每次迭代中都会更新,我想使用 toplevel() 来做完全相同的事情。

有什么建议吗?

谢谢。

在 GUI 中,您的代码基本上是 GUI 工具包事件循环中的 guest(tkinter 中的 mainloop)。

所以你不能在 GUI 程序中有一个 long-运行ning 循环;您必须以不同的方式构建代码。例如,假设您有这样一个循环;

# *Long* list of data
data = [ ... ]
# storage for results
result = []
for item in data:
    rv = process(item)
    result.append(rv)

如果您在 GUI 程序中 运行 这样一个循环,它会中断事件流。实际上,它会冻结 GUI 并且渲染没有响应。

因此在 tkinter GUI 中,您必须在超时处理程序中进行计算。下面是将长时间的 运行ning 作业分成可以在 GUI 的事件循环内处理的小块的原理草图。

import tkinter as tk
from tkinter import messagebox

# *Long* list of data
data = [ ... ]
# storage for results
result = []
index = 0

def iteration():
    '''Handles one iteration in the event loop.'''
    rv = process(data[index])
    result.append(rv)
    index += 1
    if index < len(data):
        root.after(100, iteration)
    else:
        messagebox.showinfo('Calculations', 'Finished!')


def create_UI():
    # Create and place the widgets here.
    pass


if __name__ == '__main__':
    root = tk.Tk(None)
    widgets = create_UI(root)
    root.after(100, iteration)
    root.mainloop()

如您所见,在这种情况下,您的代码结构必须不同。

请注意,在 CPython 上一次只能 一个 线程可以执行 Python 字节码,因此使用线程并不是一个很好的解决方案这个。特别是因为很多 GUI 工具包不是线程安全的,所以你应该从一个线程调用 GUI 函数。

另一种解决方案是 运行 在不同的程序中进行计算。然后该程序可以使用 Queue 与 GUI 通信。您必须在 GUI 中使用超时功能来定期监视队列并显示结果。这不是一个更简单的解决方案,但可能会提供更高的性能,您可以将计算分成几个过程。

编辑:

请注意,在第一个解决方案中,每次迭代只需要相对较少的时间,例如100 毫秒。如果需要更长的时间,GUI 会感觉反应迟钝。如果这样做不可行,则使用第二种解决方案(单独的程序)。

对于这个问题,我真的没有方便的参考。但是一个简单的模式是这样的;

你编写了两个程序。

第一个程序是一个简单的 Python 脚本,它在常规的 for 循环中进行计算。唯一特别的是,在每次迭代结束时,它会将 T、Ca、Tc 和 q 的值写入文件。

第二个程序是Python tkinter 程序,它使用after 方法检查上述文件的修改时间。如果检测到文件已更改,它会读取文件并更新其显示。

这种方法完全解耦进行计算和显示中间结果的问题,这意味着您可以单独编写和测试它们。

我认为此解决方案的通用名称称为 "file watcher"。这个解决方案太常见了,我不记得我第一次说的地方了。