I am writing a Python GUI program using tkinter to read data from an instrument based on a list of setpoints and to show the results of each reading in a tkinter scrolled textbox as the program is running, once the user clicks the "Start" button.
However, my program only shows the results at the end of all loops, rather than update the textbox after each loop. In actual usage, the program might run for many tens of minutes due to the large number of test points.
I tried using the .after method, to allow the GUI to update, but the program does not update the textbox during each loop.
How can I modify my code so that the tkinter textbox in the GUI is updated during each loop?
Here is my simplified code using random results as a sample instrument reading:
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()
CodePudding user response:
You can use root.after
like this -
data_results.after(1000,lambda:data_results.insert(tk.INSERT,"\nDone\n"))
So, after approximately 1 second it inserts Done
. And you should keep this out of the for loop -
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"))
CodePudding user response:
data_results.after(1000)
behaves like time.sleep(1)
. So the updates are not shown until tkinter mainloop()
takes back the control after the function exits.
You should use .after()
like below:
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
CodePudding user response:
Change your code as follows.
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")
Add data_results.update()
to your code.This updates your textbox every second.After all iteration You get 'Done' displayed.