【问题标题】:How to update tkinter texbox in Python during program runtime如何在程序运行时更新 Python 中的 tkinter 文本框
【发布时间】:2021-09-28 19:46:13
【问题描述】:

我正在编写一个 Python GUI 程序,使用 tkinter 根据设置点列表从仪器读取数据,并在程序运行时在 tkinter 滚动文本框中显示每次读取的结果,一旦用户单击“开始” " 按钮。

但是,我的程序仅在所有循环结束时显示结果,而不是在每次循环后更新文本框。在实际使用中,由于测试点较多,程序可能会运行几十分钟。

我尝试使用 .after 方法,让 GUI 更新,但程序不会在每个循环期间更新文本框。

如何修改我的代码,以便在每次循环期间更新 GUI 中的 tkinter 文本框?

这是我使用随机结果作为示例仪器读数的简化代码:

import tkinter as tk
import tkinter.scrolledtext as tkscrolled
from tkinter import ttk

def start_measurement():
    import random
    test_points = [tp for tp in range(1,5)]
    for setpoint in test_points:
        data_reading = random.normalvariate(setpoint,0.05/2)
        data_results.insert(tk.INSERT,"Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
        data_results.after(1000)
    data_results.insert(tk.INSERT,"\nDone\n")
    
root = tk.Tk()
    
# Create main frame for entire program: mainframe
mainframe = ttk.Frame(root, padding="10 10 10 10", style='bgstyle.TFrame')
mainframe.grid(row=0, column=0, sticky=(tk.N, tk.W, tk.E, tk.S))
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
    
# Create Data Output Text Box with Vertical Scrollbar
data_label = ttk.Label(mainframe, text="Results")
data_label.grid(row=1, column=0, padx=(30,30), pady=(20,0), sticky=(tk.S,tk.W,tk.E))
data_results =  tkscrolled.ScrolledText(mainframe, width=100, height=20)
data_results.grid(row=2, column=0, padx=(30,30), pady=(0,20), sticky=(tk.N,tk.W,tk.E))

start_button = ttk.Button(mainframe, width=15, text="Start", command=lambda:start_measurement())
start_button.grid(row=0, column=0, padx=(30,30), pady=(20,0), sticky=(tk.N, tk.W))

root.mainloop()

【问题讨论】:

  • 简单,不要使用除.after“循环”以外的任何循环或使用线程(和.after循环)
  • Matiiss,我不太明白你的建议“不要使用除 .after "loops" 之外的任何循环”。你能说得更具体点吗?
  • 好吧,请不要在tkinter 中使用forwhile 循环(除非它们非常快或在其他情况下您将它们放入线程和东西中,但不是慢循环在主线程中占用),基本上接受的答案有点像我的意思
  • @Matiiss,知道了。感谢您的澄清。我会尽量坚持使用 .after 递归函数,而不是其他循环。
  • 顺便说一句,它们并不完全是递归函数,由于最大递归深度,它们会导致 UnboundLocalError 或某事,它们只是在 mainloop 上放置一个事件,然后从那里调用函数而不是从本身

标签: python tkinter


【解决方案1】:

data_results.after(1000) 的行为类似于 time.sleep(1)。所以更新不会显示,直到 tkinter mainloop() 在函数退出后收回控制权。

您应该使用.after(),如下所示:

def start_measurement(setpoint=1):
    import random
    if setpoint < 5:
        start_button.config(state=tk.DISABLED) # prevent multiple executions
        data_reading = random.normalvariate(setpoint, 0.05/2)
        data_results.insert(tk.INSERT, "Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
        # execute this function one second later with updated setpoint
        data_results.after(1000, start_measurement, setpoint+1)
    else:
        data_results.insert(tk.INSERT, "\nDone\n")
        start_button.config(state=tk.NORMAL) # enable for next execution

【讨论】:

  • 谢谢,完美!我的设定点是编造的,所以我必须针对实际设定点进行一些调整,这是一个很长的列表,而不是整数。只是好奇,使用递归的唯一解决方案是您在 .after 语句中所做的方式吗?
  • 你可以使用线程。但不建议在线程中更新 tkinter 小部件,因为 tkinter 不是线程安全的,因此您需要找到另一种方法来告诉主线程更新这些小部件。使用.after() 会更容易一些。
【解决方案2】:

你可以像这样使用root.after -

data_results.after(1000,lambda:data_results.insert(tk.INSERT,"\nDone\n"))

因此,大约 1 秒后,它会插入 Done。你应该把它排除在 for 循环之外 -

def start_measurement():
    test_points = [tp for tp in range(1,5)]
    for setpoint in test_points:
        data_reading = random.normalvariate(setpoint,0.05/2)
        data_results.insert(tk.INSERT,"Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
    data_results.after(1000,lambda:data_results.insert(tk.INSERT,"\nDone\n"))

【讨论】:

  • 谢谢。但是,循环内部需要 1000 毫秒的延迟来等待仪器获得稳定的读数。
【解决方案3】:

如下更改您的代码。

def start_measurement():
import random
test_points = [tp for tp in range(1,5)]
for setpoint in test_points:
    data_reading = random.normalvariate(setpoint,0.05/2)
    data_results.insert(tk.INSERT,"Setpoint = {:.3f}, Reading = {:.3f}\n".format(setpoint, data_reading))
    data_results.update()
    data_results.after(1000)
data_results.insert(tk.INSERT,"\nDone\n")

data_results.update() 添加到您的代码中。这会每秒更新您的文本框。在所有迭代之后,您会显示“完成”。

【讨论】:

  • 你改变了什么?
  • @PCM 一行:data_results.update()
  • @WalidSiddik 我知道。但答案应该是updated,并附上解释。
  • 谢谢。这确实有效,这是对原始代码的改进,但 .after(1000) 在等待时仍会冻结 GUI。它确实会在每个循环中更新一次,因此它是一个可行的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-14
  • 2021-07-10
  • 1970-01-01
  • 2023-03-05
  • 1970-01-01
  • 2019-05-19
  • 1970-01-01
相关资源
最近更新 更多