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

问题描述

我正在使用 tkinter 构建一个 GUI Python 代码。我有一个在 for 循环中解决的 CSTR 模型;这些值在 for 循环内更新。我有四个正在更新的参数 T、Ca、Tc、q。我正在使用 TopLevel() 在 for 循环内的每次迭代中显示结果。换句话说,我希望每次迭代的结果都显示在显示器上,一旦我处于另一个交互中,我想再次打开一个新的窗口文件并使用新的计算值更新值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)


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

有什么建议么?

谢谢你。

标签: pythontkintertoplevel

解决方案


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

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

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

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

因此,在 tkinter GUI 中,您必须在超时处理程序中进行计算。下面是将长时间运行的作业分成可以在 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 中使用超时功能来定期监控队列并显示结果。这不是一个更简单的解决方案,但可能会提供更高的性能,因为您可以将计算划分为多个进程。

编辑

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

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

你写了两个程序。

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

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

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

我认为这个解决方案的通用名称称为“文件观察器”。这个解决方案太常见了,我不记得我先在哪里说的了。


推荐阅读