Here we will learn to use the .after() method from tkinter(ttkbootstrap) method to automatically update the tkinter GUI widgets like Labels, Textboxes at regular intervals without user intervention to create a responsive and usable GUI that can multitask effectively without freezing.
When you are building a GUI with tkinter/ttkbootstrap ,you may want to check the status of certain variable or functions continuously at regular interval and update those variables on the tkinter GUI like on a textbox or Label widget without freezing the GUI.
Here, we are using ttkbootstrap theme extension along with tkinter and Python.
Make sure that you have ttkbootstrap theme extension installed on your system, otherwise codes below will not run.
If you are new to tkinter/ttkbootstrap ,Do check out our tutorial on Python GUI design using ttkbootstrap and tkinter
Consider the example below,
We have a tkinter GUI (below image) with two buttons and a textbox. You want to display the data coming from a sensor on the tkinter/ttkbootstrap Text Box GUI every 1 second .
The Text Box GUI needs to be constantly updated with new data every second so user can view the latest data. The tkinter GUI must query the sensor function in background at specific intervals to get data and then update that data to the GUI automatically.
At the same time the GUI should respond to other GUI elements like user pressing buttons without the interface getting unresponsive, so for such applications we need to use the
.after() method of the tkinter window
to constantly run a specific function in the background at periodic intervals.Here we can put the function that query the sensor inside the .after() function.
Syntax of the .after() method is
root = ttkb.Window()
root.after(delay_in_milli_seconds,function_to_run_periodically)
#here "delay_in_milli_seconds" defines the period in milliseconds at which the function runs the "function_to_run_periodically" function
#here "function_to_run_periodically" is the function that will be run
Here is a very basic code for running background tasks using .after() method to update tkinter/ttkbootstrap GUI in realtime.
import ttkbootstrap as ttkb
def run_periodic_background_func():
print('Read from Sensor/Update textbox') #Put update function here
root.after(1000,run_periodic_background_func)
root = ttkb.Window()
root.geometry('400x400')
run_periodic_background_func() # call the update function once
root.mainloop()
Here we will create a tkinter/ttkbootstrap Window object and assign to root. Create a window of size 400X400.
After which we call the run_periodic_background_func() once to start it over. This step is important to start the .after() method.
Inside the function
def run_periodic_background_func():
print('Read from Sensor/Update textbox') #Put update function here
#read_from_sensor()
root.after(1000,run_periodic_background_func)
we will print 'Read from Sensor/Update textbox' then it will reach the root.after() function.
root.after() will wait 1000 milliseconds or 1 second and then it will call the run_periodic_background_func() and the cycle repeats every 1000 milliseconds.
You can change the wait period to any value you want ,depending on your application.
You can put your "own update function", instead of the print() statement, every one second that "your own update function" will be called and run.
Here is the full source code for a GUI update function.
#run periodic back ground tasks from tkinter gui
from tkinter import *
import ttkbootstrap as ttkb
from ttkbootstrap.scrolled import ScrolledText
import tkinter as tk
from time import sleep
count = 0
def update_function():
global count
count = count + 1
background_actions_textbox.insert(END,f'Read from Dummy Sensor,Value = {count}\n')
background_actions_textbox.see(tk.END) #for auto scrolling
# sleep(5)
root.after(100, update_function) # run itself again after 100 ms
def button_1_handler():
my_button_text.insert(END,f'You Pressed Button 1\n')# add text
my_button_text.see(tk.END) #for auto scrolling
def button_2_handler():
my_button_text.insert(END,f'You Pressed Button 2\n')# add text
my_button_text.see(tk.END) #for auto scrolling
root = ttkb.Window(themename = 'superhero') # theme = superhero
root.geometry('600x500')
root.title('Running Periodic background tasks in ttkbootstrap/tkinter')
#create textbox for button actions
my_button_text = ScrolledText(root,height = 4,width = 50,wrap = WORD,autohide = True)
my_button_text.pack(padx = 20,pady = 20)
#create button
button_1 = ttkb.Button(text = 'Button 1',command = button_1_handler).pack(pady =10)
button_2 = ttkb.Button(text = 'Button 2',command = button_2_handler).pack(pady =10)
#create textbox for background actions
background_actions_textbox = ScrolledText(root,height = 10,width = 50,wrap = WORD,autohide = True)
background_actions_textbox.pack(padx = 20,pady = 20)
update_function() #call update function to start root.after() method
root.mainloop()
When you run this code. you will get a window as shown below. Make sure that ttkbootstrap is installed on your system.
The above method is used to update data coming from serial port in our python logger software
Here background_actions_textbox is used to display the results of the scheduled action that is activated by root.after() method.
In our case ,it will simply print the text string "Read from Dummy Sensor,Value = " along with a count number every 100 milliseconds.
count = 0
def update_function():
global count
count = count + 1
background_actions_textbox.insert(END,f'Read from Dummy Sensor,Value = {count}\n')
background_actions_textbox.see(tk.END) #for auto scrolling
# sleep(5)
root.after(100, update_function) # run itself again after 100 ms
Here count is declared global and is constantly incremented every time the function ( update_function() ) calls itself using root.after() method .
Data is inserted into the text box along with updated value using
background_actions_textbox.insert(END,f'Read from Dummy Sensor,Value = {count}\n')
then
background_actions_textbox.see(tk.END) #for auto scrolling
ensures that the last value that is inserted into the textbox remains in focus, giving the appearance that the textbox is scrolling.
root.after(100, update_function) # run itself again after 100 ms
ensures that the update_function is called every 100ms
Limitations of .after() method
One issue with the .after() method is that ,if the function you are running inside the .after() method takes too long to return. The response of your GUI will be slow and sluggish.
You can see that in the above code by uncommenting the sleep() function inside the after() method
# sleep(5) #to simulate a function that takes too long
root.after(100, update_function) # run itself again after 100 ms
One way to improve the responsiveness of functions that takes too long to run is to use threading.
The GUI which runs as the main thread will create a separate thread and run that function inside that thread concurrently, thereby the performance of the GUI is not effected.
The data generated from the long running Python thread can be passed on to the thread handling the tkinter GUI (main thread) through various data structures like queue's, deque's or stacks.
If you are interested in learning about threading in Python. Do check out our Video on Python threading here.
References
- Creating and Sharing data between Python threads for the Absolute Beginner
- A Short introduction to Python GUI design using ttkbootstrap and tkinter
- GUI Serial port Data Logging System to CSV text file using Python and tkinter (ttkbootstrap)
Tags
- Log in to post comments