Update Tkinter label during threading in Python - python

I am currently trying to program a robot arm which is controlled by a Raspberry Pi.
Everything works fine so far, except for one thing and I already googled and tried everything for many hours but can't find a working solution.
For the movement of the robot arm it is necessary to run all motors "simultaneously" with threads (works fine).
The problem I have is that I need to update a label which shows the current angle of an axis (motor) as soon as it finished its movement but other motors are still running (threads).
After a lot of research I thought I found the solution by using a queue and Tkinters after-method. But it still doesn't work as the labels text only gets updated after all threads terminated.
I wrote an example code where I want to get a label update for motor "one" which will finish its for-loop (100 iterations) before motor "two" (500 iterations). I expected the label to get updated as soon as motor one reached its target while motor two is still runing.
But although I used the after-method it still waits till motor two finished before updating the label.
Hope you can help me!
from tkinter import *
import threading
import time
from queue import *
class StepperMotors:
def __init__(self, root):
self.root = root
self.start_btn = Button(root, text="Start", command=lambda:self.start_movement())
self.start_btn.config(width = 10)
self.start_btn.grid(row=1,column=1)
self.label_one = Label(root, text='')
self.label_one.config(width = 10)
self.label_one.grid(row=2, column=1)
self.label_two = Label(root, text='')
self.label_two.config(width = 10)
self.label_two.grid(row=3, column=1)
def start_movement(self):
self.thread_queue = Queue()
self.root.after(100, self.wait_for_finish)
thread_one = threading.Thread(target=self.motor_actuation, args=(1,100))
thread_two = threading.Thread(target=self.motor_actuation, args=(2,500))
thread_one.start()
thread_two.start()
thread_one.join()
thread_two.join()
def motor_actuation(self, motor, iterations):
for i in range(iterations):
i = i+1
update_text = str(motor) + " " + str(i) + "\n"
print(update_text)
time.sleep(0.01)
self.thread_queue.put(update_text)
def wait_for_finish(self):
try:
self.text = self.thread_queue.get()
self.label_one.config(text=self.text)
except self.thread_queue.empty():
self.root.after(100, self.wait_for_finish)
if __name__ == "__main__":
root = Tk()
root.title("test")
stepper = StepperMotors(root)
root.mainloop()

It’s better to use daemon thread which are non-blocking.
Also, It’s better to have a separation of concerns: a robot (or robot arm) can be a object which has its own life time: a daemon thread. Idem, you can define a "LabelUpdater" which read the state of a robot and update a label.
Let’s define a robot:
it is created on application initialisation and is run when the user click the "Start" button,
The robot moves and reports its angle in a app-level multithreading-queue,
class Robot(threading.Thread):
def __init__(self, name: str, label_queue: queue.Queue, end_pos: int):
super().__init__(name=name)
self.daemon = True
self.label_queue = label_queue
self.end_pos = end_pos
def run(self) -> None:
for angle in range(self.end_pos):
self.label_queue.put(angle)
time.sleep(0.01)
Let’s define a LabelUpdater:
It is created on application initialisation and run forever (it can observe a robot even if it is not running).
It reads the robot queue (for instance each second to avoid blinking) and update the label
class LabelUpdater(threading.Thread):
def __init__(self, name: str, label_queue: queue.Queue, root_app: tkinter.Tk, variable: tkinter.Variable):
super().__init__(name=name)
self.daemon = True
self.label_queue = label_queue
self.root_app = root_app
self.variable = variable
def run(self) -> None:
# run forever
while True:
# wait a second please
time.sleep(1)
# consume all the queue and keep only the last message
last_msg = None
while True:
try:
msg = self.label_queue.get(block=False)
except queue.Empty:
break
last_msg = msg
self.label_queue.task_done()
if last_msg:
self.variable.set(last_msg)
Then, the main application should define:
2 multithreading queues: one for each label,
2 tkinter.StringVar variable which will be updated,
the robots and the label updaters,
the two updaters are started, and will run forever.
class StepperMotors:
def __init__(self, root):
self.root = root
self.label_one_queue = queue.Queue()
self.label_two_queue = queue.Queue()
self.start_btn = tkinter.Button(root, text="Start", command=lambda: self.start_movement())
self.start_btn.config(width=10)
self.start_btn.grid(row=1, column=1)
self.text_one = tkinter.StringVar()
self.text_one.set("one")
self.label_one = tkinter.Label(root, textvariable=self.text_one)
self.label_one.config(width=10)
self.label_one.grid(row=2, column=1)
self.text_two = tkinter.StringVar()
self.text_two.set("two")
self.label_two = tkinter.Label(root, textvariable=self.text_two)
self.label_two.config(width=10)
self.label_two.grid(row=3, column=1)
self.robot_one = Robot("robot_one", self.label_one_queue, 100)
self.robot_two = Robot("robot_two", self.label_two_queue, 500)
self.updater_one = LabelUpdater("updater_one", self.label_one_queue, self.root, self.text_one)
self.updater_two = LabelUpdater("updater_two", self.label_two_queue, self.root, self.text_two)
self.updater_one.start()
self.updater_two.start()
def start_movement(self):
self.robot_one.start()
self.robot_two.start()
Of course, you need a flag or something to check that each robot is not already running.

Related

Ensure thread running independently of tkinter mainloop doesn't access an element which no longer exists

I'm creating a tkinter application and I want to disable certain GUI elements while a particular set of threads are running. Basically one button starts up all the threads and they get added to a list which is a member of a class named Form. I have a separate thread which is a class member and is constantly checking the list of threads to see if it is empty; as long as there is at least one living thread in the list, the GUI elements should be disabled.
class Form()
def __init__(self):
...
self.check_running_threads_thread = threading.Thread(target = self.check_running_threads)
self.check_running_threads_thread.start()
The issue is that I need this thread to quit running on application exit. The way I have been handling that is like so in main:
root = tk.Tk()
myForm = Form(root)
root.mainloop()
myForm.kill_threads = True
and the thread function that is started on the call of __init()__ of the Form class is:
def check_running_threads(self):
while not self.kill_threads:
for key, val in self.items.items():
if len(self.running_threads) > 0:
if key.endswith("entry"):
val.config(state='readonly')
elif key.endswith('button'):
val['state'] = tk.DISABLED
else:
if key.endswith("entry"):
val.config(state='normal')
elif key.endswith('button'):
val['state'] = tk.NORMAL
self.running_threads = [th for th in self.running_threads if th.is_alive()]
time.sleep(0.1)
where self.items is just a dictionary containing all the tk gui elements, indexed by a unique name.
The problem is that sometimes when the mainloop ends, such as by me exiting out of the application, the check_running_threads() function could be anywhere in the loop before kill_threads gets set to True, meaning it could try to change the state of the tk gui elements when they no longer exist. This leads to the program freezing and never exiting. The tk window is destroyed, but the command line window hangs forever.
What is the proper way to handle this? How can I ensure a thread running independently of the tkinter mainloop doesn't access an element which no longer exists?
2022-09-05 Edit:
Here is a complete minimal example:
import tkinter as tk
from tkinter import ttk
import threading
import time
import random
class Form():
def __init__(self, root):
self.kill_threads = False
self.running_threads = []
self.items = {}
self.btn = ttk.Button(root, text='Press me, wait for num threads to be zero, then click "X" button', command=self.run_threads)
self.btn.pack(side=tk.LEFT)
self.items['my_button'] = self.btn
for jj in range(60):
new = ttk.Button(root, text='', width=0)
self.items[f'my_{jj}_button'] = new
new.pack(side=tk.LEFT)
self.check_running_threads_thread = threading.Thread(target = self.check_running_threads)
self.check_running_threads_thread.start()
def thread_func(self):
time.sleep(random.uniform(0.5, 2.5))
def run_threads(self):
for ii in range(30):
mythread = threading.Thread(target = self.thread_func)
mythread.start()
self.running_threads.append(mythread)
def check_running_threads(self):
while not self.kill_threads:
for key, val in self.items.items():
if key.endswith("button"):
if len(self.running_threads) > 0:
val['state'] = tk.DISABLED
else:
val['state'] = tk.NORMAL
print("Num threads running:", len(self.running_threads))
self.running_threads = [th for th in self.running_threads if th.is_alive()]
time.sleep(0.2)
root = tk.Tk()
myForm = Form(root)
root.mainloop()
myForm.kill_threads = True
All the excess buttons are there to represent other GUI elements that are in my actual program. The more there are, the more likely it seems the program hangs on exit.
Here's a video of me running it. You can see the behavior is sometimes inconsistent (it doesn't always hang, such as the third time I ran it):
https://youtu.be/JhKxRYVujQg
You can create a new method in your Form class that will be called on close:
root = tk.Tk()
myForm = Form(root)
root.protocol("WM_DELETE_WINDOW", myForm.wait_till_threads_killed)
root.mainloop()
Where you method would look something like this:
def wait_till_threads_killed(self):
self.kill_threads = True
self.check_running_threads_thread.join()
for t in self.running_threads:
t.join()
self.parent.destroy()
After having added root as parent in the __init__ method:
def __init__(self, root):
self.parent = root
Edit: adding destroy() otherwise the window won't close + waiting for all threads
Edit2: note that this is only needed if you want to wait for the threads to finish. If want to kill them on exit, just set them as daemons in your initial code (then you don't necessarily need protocol):
mythread = threading.Thread(target = self.thread_func, daemon=True)

Starting and stopping a thread from a gui

I'm currently developing a program with a GUI to gather data from a sensor and visualise it within the GUI.
The data from the sensor is stored in a list for further calculations.
What I'm currently trying to do, is starting the logging process in a new thread.
This should stop the GUI from freezing.
My current code:
import tkinter as tk
import time
import threading
class GUI:
def __init__(self, tk_object):
self.gui = tk_object
self.gui.title("Logger")
self.gui.resizable(width=True, height=True)
self.testlabel = tk.Label(self.gui, text="Recording in process")
self.testlabel.grid(row = 7, column = 0)
self.testlabel.config(bg='white')
btn1 = tk.Button(master, text="Start Recording", width=16, height=5, command=lambda: self.start_functions())
btn1.grid(row=2,column=0)
btn2 = tk.Button(master, text="Stop Recording", width=16, height=5, command=lambda: self.stop_functions())
btn2.grid(row=3,column=0)
def start_functions(self):
"""Calls the method get_sample in a new thread and changes the bg-color of the label to red"""
Thread_1 = threading.Thread(target=self.get_sample(), name='Thread1')
Thread_1.start()
self.testlabel.config(bg='red')
def stop_functions(self):
"""Stops all active threads and changes bg-color of the label back to white"""
#stop_threads = threading.Event()
#stop_threads.set()
threading.Event().set()
self.testlabel.config(bg='white')
def get_sample(self):
"""get data and append it to a list"""
while not threading.Event().is_set():
time.sleep(1)
res_cel.append(res_cel[-1]+1)
x_value.append(x_value[-1]+1)
print(res_cel)
print(x_value)
master = tk.Tk()
master_object = GUI(master)
master.mainloop()
Currently, the get_sample method contains a placeholder.
I'm trying to stop the Thread1 (where the get_sample method is running in) via the Event() handler.
while not threading.Event().is_set():
This doesn't seem to work in a proper way.
Are there better ways to do this?
Before this approach I tried using a class to handle the threads (This was found on stackoverflow, but I can't seem to find it anymore, sorry):
class Controller(object):
def __init__(self):
self.thread1 = None
self.stop_threads = Event()
def run(self):
.. do somehting
and starting / stopping the threads via:
def starting_thread(self):
self.stop_threads.clear()
self.thread1 = Thread(target = self.run)
self.thread1.start()
def stopping_thread(self):
self.stop_threads.set()
self.thread1 = None
Note: These functions are within the Controller Class.
With this solution I wasn't able to alter the lables background color in the GUI Class, since it doesn't know what object it is referencing to.
Im fairly new to programming python so I'd be glad if someone could explain to me what I'm missing here.
Thanks
xSaturn
You'd better create your own Thread object with a method to stop it. A thread stop running when it goes of its run method. Look at the example bellow
import time
import tkinter as tk
from threading import Thread
class MyThread(Thread):
thread_running = False
def run(self):
k = 0
self.thread_running = True
while self.thread_running:
print(k)
k +=1
time.sleep(1)
print("thread ended")
def stop(self):
self.thread_running = False
class Window(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.thread = None
tk.Button(self, text="launch thread", command=self.launch_thread)\
.grid(row=1, column=0)
tk.Button(self, text="stop thread", command=self.stop_thread)\
.grid(row=2, column=0)
def launch_thread(self):
if self.thread:
print("thread already launched")
else:
print("thread launched")
self.thread = MyThread()
self.thread.start()
def stop_thread(self):
if self.thread:
self.thread.stop()
self.thread = None
else:
print("no thread running")
if __name__ == '__main__':
win = Window()
win.mainloop()
See this topic for more information
Is there any way to kill a Thread?

Python, Tkinter: how can I prevent tkinter gui mainloop crash using threading

Hi I have a small python gui interface with two buttons, start(That starts a counter) and stop (that is suppose to stop the counter), the counter is an infinite loop since I do not want it to end unless the second button is clicked. The problem is the second button cannot be clicked while the function from the first one is still running.
I read that I need to use threading and I have tried but I do not fully understand how I can do this. Please help.
from Tkinter import *
import threading
class Threader(threading.Thread):
def run(self):
for _ in range(10):
print threading.current_thread().getName()
def main(self):
import itertools
for i in itertools.count(1, 1):
print i
def other(self):
print "Other"
m = Threader(name="main")
o = Threader(name="other")
try:
'''From here on we are building the Gui'''
root = Tk()
'''Lets build the GUI'''
'''We need two frames to help sort shit, a left and a right vertical frame'''
leftFrame = Frame(root)
leftFrame.pack(side=LEFT)
rightFrame = Frame(root)
rightFrame.pack(side=RIGHT)
'''Widgets'''
'''Buttons'''
playButton = Button(leftFrame, text="Play", fg="blue", command=m.main)
stopButton = Button(rightFrame, text="Stop", fg="red", command=o.other)
playButton.pack(side=TOP)
stopButton.pack(side=BOTTOM)
root.mainloop()
except Exception, e:
print e
Here's a short example of using threading. I took out your other function and I don't know why your using itertools here. I took that out as well and simply setup using a simple threading example.
A few things:
You setup using threading.Thread as the base class for Threader, but you never actually initialized the base class.
Whenever you use threading you generally want to define a run method and then use start() to start the thread. Calling start() will call run.
You need to use threading to prevent your GUI blocking, because tkinter is just one thread on a giant loop. So, whenever you have some long running process it blocks this thread until the current process is complete. That's why it's put in another thread. Python has something called the GIL, which prevent's true parallelization (I made up that word) since it only one thread can ever be used at a time. Instead, it uses time slicing, the GIL sort of "polls" between them to give the appearance of multiple tasks running concurrently. For true parallel processing you should use multiprocessing.
In the below code I have used self.daemon = True. Setting the thread to be a daemon will kill it when you exit the main program (In this case the Tk GUI)
from tkinter import *
import threading, time
class Threader(threading.Thread):
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
self.daemon = True
self.start()
def run(self):
while True:
print("Look a while true loop that doesn't block the GUI!")
print("Current Thread: %s" % self.name)
time.sleep(1)
if __name__ == '__main__':
root = Tk()
leftFrame = Frame(root)
leftFrame.pack(side=LEFT)
rightFrame = Frame(root)
rightFrame.pack(side=RIGHT)
playButton = Button(leftFrame, text="Play", fg="blue",
command= lambda: Threader(name='Play-Thread'))
stopButton = Button(rightFrame, text="Stop", fg="red",
command= lambda: Threader(name='Stop-Thread'))
playButton.pack(side=TOP)
stopButton.pack(side=BOTTOM)
root.mainloop()
For something as simple as a counter, Tkinter's after() method is usually a better choice. You can use an instance variable to set it to on and off.
class TimerTest():
def __init__(self, root):
self.root=root
Button(root, text="Play", fg="blue",
command=self.startit).grid(row=1, column=0)
Button(root, text="Stop", fg="red",
command=self.stopit).grid(row=1, column=1)
self.is_running=True
self.count=IntVar()
Label(root, textvariable=self.count,
bg="lightblue").grid(row=0, column=0, columnspan=2, sticky="ew")
def startit(self):
self.is_running=True
self.increment_counter()
def increment_counter(self):
if self.is_running:
c=self.count.get()
c += 1
self.count.set(c)
root.after(1000, self.increment_counter) ## every second
def stopit(self):
self.is_running = False
root = Tk()
TT=TimerTest(root)
root.mainloop()

How to implement indeterminate Progressbar in Tkinter?

My application has some time-consuming background processes to be run occasionally. I want to show that the application is working using a simple indeterminate ttk.Progressbar.
However, my implementation only shows static progress bar.
Anyway, here is how I implemented it.
class MyApp(ttk.Frame):
def __init__(self, master):
ttk.Frame.__init__(self, master)
fname = 'test.txt'
self.build_ui()
self.process_file(fname)
def process_file(self, fname):
self.show_progress(True)
fdata = self.read_file(fname)
fdata = self.spellcheck(fdata)
self.show_progress(False)
return fdata
def show_progress(self, start)
if start:
self.prog_win = tk.Toplevel()
self.prog_win.title('Working...')
self.prog_win.resizable(0, 0)
self.progress_bar = ttk.Progressbar(self.prog_win,
orient=tk.HORIZONTAL,
mode='indeterminate',
takefocus=True)
self.progress_bar.grid()
self.progress_bar.start()
else:
self.progress_bar.stop()
self.prog_win.destroy()
root = tk.Tk()
root.update()
gui = MyApp(root)
gui.mainloop()
The code above does not work as intended. The Progressbar appears static, it does not move and just hangs there forever. I tried to use threading but if I start show_progress in a separate Thread it always gets executed after the processing has been done.
What am I doing wrong?
The problem is that self.read_file() and self.spellcheck() are blocking which prevents Tkinter from updating your progress bar in its main loop. A simple way to solve this is to do your processing in a separate thread, and periodically check to see if the thread finished its work.
import threading
class MyApp(ttk.Frame):
def __init__(self, master):
ttk.Frame.__init__(self, master)
fname = 'test.txt'
self.build_ui()
self.process_file(fname)
def process_file(self, fname):
self.show_progress(True)
# Start thread to process file.
self.thread = threading.Thread(target=self.process_file_worker, args=(fname,))
self.thread.daemon = True # Allow the program to terminate without waiting for the thread to finish.
self.thread.start()
# Start checking the thread.
self.process_file_check()
def process_file_check(self):
if self.thread.is_alive():
# Thread is still running, check thread again in 10 milliseconds.
self.after(10, self.process_file_check)
else:
# Thread finished, handle processed results.
# Do something with `self.fdata`.
self.show_progress(False)
def process_file_worker(self, fname):
# This is run inside the thread.
fdata = self.read_file(fname)
fdata = self.spellcheck(fdata)
self.fdata = fdata
def show_progress(self, start):
if start:
self.prog_win = tk.Toplevel()
self.prog_win.title('Working...')
self.prog_win.resizable(0, 0)
self.progress_bar = ttk.Progressbar(self.prog_win,
orient=tk.HORIZONTAL,
mode='indeterminate',
takefocus=True)
self.progress_bar.grid()
self.progress_bar.start()
else:
self.progress_bar.stop()
self.prog_win.destroy()
root = tk.Tk()
root.update()
gui = MyApp(root)
gui.mainloop()

GUI programming using Tkinter Python

I have a Tkinter GUI having 2 entry fields, 2 buttons ( initialization of these not shown in code). There is one more button (initialized in code) which performs the main task of performing change detection on two images. Also there is a progress bar.
Now, when the task of change detection has been completed, I want to display the 4 images(pre, post, aligned, chng) returned by wave.changedetection() in a separate Tkinter window. I want the new window to come only after changedetection() has completed.(wave.py is my own file, not some module)
Unfortunately, if I try to add code to make new window, Tk.Toplevel() ,after the wave.changedetection() call, nothing happens and the main GUI window becomes unresponsive and has to be killed.
There is no way to know when the new created thread (start_thread)completes it's work, so that I can do Tk.Toplevel() there.
How can I do what I require?
class GUI(Tkinter.Tk):
def __init__(self, parent)
Tkinter.Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.button = Tkinter.Button(text = "Start")
self.button.bind('<Button-1>', self.OnButtonClick)
self.button.pack()
self.int = Tkinter.IntVar()
self.pgbar = Tkinter.ProgressBar(variable = self.int, mode = determinate)
def OnButtonClick(self,event):
#this func has been made since I have more buttons.It may seem redundant here
self.button['command'] = self.start_thread()
self.update_idletasks()
def start_thread(self):
self.int_var.set(1)
q = queue.Queue()
self.secondary_thread = threading.Thread(target = self.change)
self.secondary_thread.start()
self.after(50, self.check_queue, q)
def check_queue(self, q):
while True:
try:
x = wave.q.get_nowait()
except queue.Empty :
self.after(50,self.check_queue,q)
break
else:
self.int_var.set(x)
if x == 6:
self.button3['state'] = 'normal'
break
def change(self):
'''The 6 functions of wave.changedetection() change the value of self.int
due to which progress bar progresses.'''
pre, post, aligned, chng = wave.changedetection(self.entry_1.get(),
self.entry_2.get())
if __name__ == '__main__':
gui = GUI(None)
gui.mainloop()
code to update progress bar taken from here (2nd answer,Honest Abe's answer)
You have to be able to differentiate name spaces, i.e. this is in the main window and this is in the Toplevel. I would suggest that you get the Toplevels working first and then decide if you want to add threading or not. The code below is a simple example of creating Toplevels and shows how to place widgets in a specific name space (window in this case). You may or may not want a separate "create a Toplevel" class if there are functions you want to associate with each Toplevel's namespace. Also there are examples on the web on using Tkinter's "after" to update a progressbar. That is a different question so start another thread if you have questions about the progressbar.
try:
import Tkinter as tk ## Python 2.x
except ImportError:
import tkinter as tk ## Python 3.x
from functools import partial
class OpenToplevels():
""" open and close additional Toplevels with a button
"""
def __init__(self):
self.root = tk.Tk()
self.button_ctr=0
## in the "root" namespace *********************
but=tk.Button(self.root, text="Open a Toplevel",
command=self.open_another)
but.grid(row=0, column=0)
tk.Button(self.root, text="Exit Tkinter", bg="red",
command=self.root.quit).grid(row=1, column=0, sticky="we")
self.root.mainloop()
def close_it(self, id):
## destroy the window in this id's namespace ***********
id.destroy()
## id.withdraw()
## id.iconify()
def open_another(self):
self.button_ctr += 1
id = tk.Toplevel(self.root)
id.title("Toplevel #%d" % (self.button_ctr))
## in the "id for this Toplevel" namespace ***********
tk.Button(id, text="Close Toplevel #%d" % (self.button_ctr),
command=partial(self.close_it, id),
bg="orange", width=20).grid(row=1, column=0)
Ot=OpenToplevels()

Categories