Using Python Tk as a front-end for threaded code - python

I know that Python’s Tk interface has some problems when using threads, and I’ve already run into problems with it. My idea is now to use a Queue.Queue to somehow pass events to the Tk mainloop, similarly to the following example.
from Tkinter import *
import Queue
import time
# this queue will be filled by other threads
queue = Queue.Queue()
queue.put("Helloooo!")
queue.put("hi there, everyone!")
class Application(Frame):
def update(self):
while True:
try:
text = queue.get(False)
self.hi_there["text"] = text
time.sleep(3)
except Queue.Empty:
self.quit()
def create_widgets(self):
self.hi_there = Label(self)
self.hi_there["text"] = "Hello"
self.hi_there.pack({"side": "left"})
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.create_widgets()
root = Tk()
app = Application(master=root)
app.update()
app.mainloop()
Of course, I must not call update myself (this will execute everything before the UI is even shown) but need Tk to handle that during its mainloop.
Is there any foolproof way to accomplish that with Tk which will not break under certain circumstances? Or should I just resort to Qt for that?

As a general rule of thumb a GUI application should never, ever call sleep, should never have an infinite loop (except for the event loop), and you should never call 'update'. The only exception to never calling update is that is is ok only when you truly understand why you should not.
Create a method that does two things: check the queue, and then use 'after' to call itself after some small period of time. Then, call this method once at the beginning of your program just before starting the event loop, after all other initialization has taken place.
For a working example of such a function, see How to create a timer using tkinter?

Related

Tkinter - Thread communication and shared result - with progressbar

I am building an interface in Tkinter in which the main window (let's call it 'root') contains a button (say, 'create'). Furthermore, assume I have already defined a function 'f'. I would like to create the following effect: clicking on 'create' would execute 'f' in the background and at the same time open an indeterminate progress bar in a new window. Moreover, and this is the tricky part for me, I want the progress bar to close automatically after 'f' is done executing. How can I achieve this? Could you please provide a minimal working example? I think that key lies on constructing a proper function to pass as 'command' option to 'create'.
This is what I have thus far. It is not even running properly, as the progress bar runs indefinitely and the task starts being executed only after the progress bar is closed (or after closing 'root'). However, it feels like this is really close, and there is some small issue that I should fix but that I cannot see:
from tkinter import *
from tkinter.ttk import *
import threading
import time
root = Tk() # Main window
def create_command():
stop_flag = threading.Event() # create a flag to stop the progress bar
def f():
# function to do some task
print("Starting task...")
time.sleep(5) # simulate some time-consuming task
print("Task complete.")
stop_flag.set() # set the stop flag to indicate that progress_check() should stop
progress_bar_window = Toplevel(root) # Progress bar window
progress_bar = Progressbar(progress_bar_window, orient= 'horizontal', length= 300, mode= 'indeterminate') # Create progress bar
progress_bar.pack()
progress_bar.start()
def progress_check():
# function to run an infinite loop
while not stop_flag.is_set():
print("Running infinite loop...")
time.sleep(1)
progress_bar.stop()
progress_bar_window.destroy()
progress_bar_window.mainloop() # Start mainloop for progress bar window
# create separate threads to run the functions
thread1 = threading.Thread(target=f, args=())
thread2 = threading.Thread(target=progress_check, args=())
thread1.start() # start executing f
thread2.start() # start the progress_check
# wait for f to finish before stopping the infinite loop
thread2.join()
stop_flag.set() # set the stop flag to indicate that progress_bar() should stop
create_button = Button(root, text= "Create", command= create_command)
create_button.pack()
root.mainloop()
Look at this:
from tkinter import ttk
import tkinter as tk
import threading
import time
root = tk.Tk() # Main window
def create_command():
# create a flag to stop the progress bar
stop_flag = threading.Event()
def f():
print("Starting task...\n", end="")
time.sleep(5)
print("Task complete.\n", end="")
# set the stop flag to indicate that progress_check() should stop
stop_flag.set()
progress_bar_window = tk.Toplevel(root)
progress_bar = ttk.Progressbar(progress_bar_window, orient="horizontal",
length=300, mode="indeterminate")
progress_bar.pack()
progress_bar.start()
def progress_check():
# If the flag is set (function f has completed):
if stop_flag.is_set():
# Stop the progressbar and destroy the toplevel
progress_bar.stop()
progress_bar_window.destroy()
else:
# If the function is still running:
print("Running infinite loop...\n", end="")
# Schedule another call to progress_check in 100 milliseconds
progress_bar.after(100, progress_check)
# start executing f in another thread
threading.Thread(target=f, daemon=True).start()
# Start the tkinter loop
progress_check()
create_button = tk.Button(root, text= "Create", command=create_command)
create_button.pack()
root.mainloop()
Explanation:
To run a loop alongside tkinter, you should use .after, like in this question. I changed progress_check so that tkinter calls it every 100 milliseconds until stop_flag is set. When stop_flag is set, the progressbar stops and the Toplevel is destroyed.
A few minor points:
from ... import * is discouraged
With tkinter, you don't need more than 1 .mainloop() unless you are using .quit(). .mainloop() doesn't stop until all tk.Tk windows have been destroyed.
There is no point in creating a new thread, if you are going to call .join() right after.
First of all, don't use wildcard imports!
Wildcard-imports can lead to name conflicts, for instance swap the wildcard imports from ttk and tkinter. You end up using tkinter buttons even if you want to use ttk buttons. Same issue might appear with PhotoImage and pillow. The magic word is "qualified-names".
Also I like to have some sort of structure in my code, I prefer classes. However, even in a procedural code there can be some sort of structure. For instance:
imports
1.0) built-in modules
1.1) import external modules
1.2) import own modules
Constants and global variables
free functions
main window definitions
...
every logical block can be separated with comments that indicates what the following code might do or represents. This could also be useful to "jump" with the search function of your IDE to the point you want to work next, in larger scripts and modules this becomes handy.
A slightly different version of your code can be found below and it is not intended to be used:
import tkinter as tk
from tkinter import ttk
import threading
import time
def start_worker_thread():
'This function starts a thread and pops up a progressbar'
def generate_waiting_window():
'nested function to generate progressbar'
#disable button to inform user of intended use
start_btn.configure(state=tk.DISABLED)
#toplevel definitions
toplevel = tk.Toplevel(root)
toplevel.focus()
#progressbar definitions
progress = ttk.Progressbar(
toplevel, orient=tk.HORIZONTAL, length=300, mode='indeterminate')
progress.pack(fill=tk.BOTH, expand=True)
progress.start()
return toplevel
def long_blocking_function():
'This function simulates a long blocking call'
stopped = threading.Event()
n = 0
while not stopped.is_set():
n += 1
print('working in turn', n)
time.sleep(0.5)
if n == 10:
stopped.set()
nonlocal thread_info
thread_info = n
#important!! last logical line
toplevel.destroy()
return None
toplevel = generate_waiting_window()
thread_info = None
thread = threading.Thread(target=long_blocking_function)
thread.start()
toplevel.wait_window()
start_btn.configure(state='normal')
result_lbl.configure(text='Result is: '+str(thread_info))
print('thread exited on turn', thread_info)
#Main window definitions
root = tk.Tk()
start_btn = ttk.Button(root, text="Start", command=start_worker_thread)
start_btn.pack()
result_lbl = tk.Label(root, text='Result is: None')
result_lbl.pack()
#start the application
root.mainloop()
#after application is destroyed
While this code is efficient for this simple task, it requires understanding what it does to debug it. That is why you won't find code like this often. It is here for demonstrative purposes. So what is wrong with the code and how does it differ from the meanwhile canonical way of using threads in tkinter.
First of all, it uses nested function. While this might not an issue here, computing the same function over and over again, can slow down your code significantly.
Second it uses tkwait and therefore has some caveats over the linked answer.
Also threading.Event is a low-level primitive for communication, while there are cases you could use it, tkinter offers own tools for it and these should be preferred.
In addition it does not use a threadsafe storage for the data and this could also lead to confusion and non reliable data.
A better approach and a slight improvement to the canonical way can be found here:
import tkinter as tk
from tkinter import ttk
import threading
import sys
import queue
import time
inter_thread_storage = queue.Queue()
temporary_toplevel = None
EXIT = False
def on_thread_ended_event(event):
start_btn.configure(state=tk.NORMAL)
result = inter_thread_storage.get_nowait()
result_lbl.configure(text='Result is: '+str(result))
global temporary_toplevel
temporary_toplevel.destroy()
temporary_toplevel = None
def worker_thread_function():
'Simulates a long blocking function'
n = 0
while n < 10 and not EXIT:
n += 1
print('working in turn', n)
time.sleep(0.5)
if not EXIT:
inter_thread_storage.put(n)
root.event_generate('<<ThreadEnded>>')
def start_worker_thread():
'This function starts a thread and pops up a progressbar'
#toplevel definitions
toplevel = tk.Toplevel(root)
toplevel.focus()
#progressbar definitions
progress = ttk.Progressbar(
toplevel, orient=tk.HORIZONTAL, length=300, mode='indeterminate')
progress.pack(fill=tk.BOTH, expand=True)
progress.start()
#thread definitions
thread = threading.Thread(target=worker_thread_function)
thread.start()
#disable button to inform user of intended use
start_btn.configure(state=tk.DISABLED)
#store toplevel temporary
global temporary_toplevel
temporary_toplevel = toplevel
#Main window definitions
root = tk.Tk()
root.bind('<Destroy>',lambda e:setattr(sys.modules[__name__], 'EXIT', True))
root.bind('<<ThreadEnded>>', on_thread_ended_event)
start_btn = ttk.Button(root, text="Start", command=start_worker_thread)
start_btn.pack()
result_lbl = tk.Label(root, text='Result is: None')
result_lbl.pack()
#start the application
root.mainloop()
#after application is destroyed
This is how it works:
generate a new event
Make sure your toplevel can be reached, with global or alternatives.
store data threadsafe like in a Queue
fire the event and let tkinter call your function safely in the mainloop.
it has a flag for the edge case, where the user closes the main window before the thread finished.
Let me know, if you have questions to my answer.

Tcl_AsyncDelete Error. Unable to terminate Tk

I am using Tkinter in a ROS node to create a GUI and publish the scale values to another ROS Node. I have accomplished this. The problem comes when I try to close this GUI and rerun the node. The log message that I get is as follows:
Exception RuntimeError: 'main thread is not in main loop' in <bound method DoubleVar.__del__ of <Tkinter.DoubleVar instance at 0x7f19ea0c3ab8>> ignored
Tcl_AsyncDelete: async handler deleted by the wrong thread
Aborted (core dumped)
According to this, I think I will have to terminate Tk from its own thread. But I do not know how to do this. My code is as follows:
#!/usr/bin/env python
import rospy
from std_msgs.msg import Float64MultiArray
from Tkinter import *
from calibration_camera_lidar.msg import Euler_val
import tkMessageBox
class slider():
def __init__(self):
rospy.loginfo("init")
rospy.init_node('slider', anonymous=True, disable_signals=True)
self.spub = rospy.Publisher('Slider_values', Euler_val, queue_size=10)
self.final_ev = Euler_val()
self.listener()
def listener(self):
rospy.Subscriber("Euler_values", Float64MultiArray, self.callback)
rospy.spin()
def callback(self, data):
self.eulerval = list(data.data)
self.final_ev.Euler_angles = [self.eulerval[0], self.eulerval[1], self.eulerval[2]]
self.spub.publish(self.final_ev)
rospy.loginfo(self.final_ev)
self.slider_value()
def callback_exit(self):
if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"):
self.root.destroy()
self.root.quit()
rospy.signal_shutdown("shutdown")
def slider_value(self):
self.root = Tk()
self.root.title("fine tune")
self.root.protocol("WM_DELETE_WINDOW", self.callback_exit)
self.y_var = DoubleVar()
self.y_scale = Scale( self.root, from_=self.eulerval[0]-1, to=self.eulerval[0]+1, length=300, label="yaw", resolution=0.0000000000001, variable = self.y_var, orient=HORIZONTAL, command=self.pub_y)
self.y_scale.set(self.eulerval[0])
self.y_scale.pack(anchor=CENTER)
self.label = Label(self.root)
self.label.pack()
self.root.mainloop()
def pub_y(self, val_y):
self.eulerval[0] = float(self.y_scale.get())
self.final_ev.Euler_angles = [self.eulerval[0], self.eulerval[1], self.eulerval[2]]
self.spub.publish(self.final_ev)
rospy.loginfo(self.final_ev)
if __name__ == '__main__':
try:
slider()
except:
rospy.loginfo("Node terminated.")
I would be grateful if you could help. Thanks!
The problem is that rospy is internally multithreaded yet Tk is very keen on only being used from a single thread. (Technically, it's possible to use Tk from multiple threads — by appropriate quarantining of window objects and so on — but it's really tricky to get right and you probably don't want that.)
The easiest approach in general is to make two classes, one that just handles Tk (with incoming and outgoing messages all queued) and the other which does the bridging into the rest of the code. Then, when you want the Tk GUI to appear you run a thread that just does that and then talk to that thread just by its queues. Which sounds like a lot more work, but you can't defeat Tk's internal awareness of threads other than by keeping it strictly on one thread.
However, it might be enough to change the shutdown sequence a bit to be like this.
def callback_exit(self):
if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"):
self.root.destroy()
rospy.signal_shutdown("shutdown")
sys.exit(0)
Assuming that you're in the correct thread. If not, you'll need a direct os._exit(0) instead and that's considered dangerous for good reason (yet it might be necessary).

Tkinter event based background label updating

My program is doing complex calculation that last for around 30 minutes. I would like to be updated about the values of some variables in a Tkinter GUI with ideally certain variables linked to certain labels so that they update automatically whenever they change their values. While I understand that I could create 2 threads (one for the main program and one for TKinter that calls itself every second or so), I'm wondering if there's a more elegant way to handle this. Any suggestions are appreciated.
The below example shows how it could possibly look like, but the example doesn't work.
import Tkinter as tk
import time
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.counter = tk.Label(self, text="")
self.counter.pack()
# start the clock "ticking"
self.mainLoop()
def mainLoop(self):
for i in range (10000):
j=i^2
self.counter.configure(text=str(j))
time.sleep(0.1)
if __name__== "__main__":
app = SampleApp()
app.mainloop()
However, it does work when the mainLoop is replaced with this:
def mainLoop(self):
now = time.strftime("%H:%M:%S" , time.gmtime())
self.counter.configure(text=now)
# call this function again in one second
self.after(1000, self.mainLoop)
My questions:
Why does it work with "self.after" but not with a for loop
Is there a more elegant way to update labels event driven (not based on a button, but on a calculation that changes certain variables)
To answer first question -
Why does it work with "self.after" but not with a for loop
The __init__() method of a class is called when an object of that class in created. Now in your case, the class gets created in line -
app = SampleApp()
And in your __init__() , you call - self.mainLoop() .
In your for loop case, all this is running in the main thread. And so it would not return from the self.mainLoop() untill it has completed, since you are not start self.mainLoop() as a separate thread, you are doing that in the same thread. Hence, the control would reach the root.mainloop() line only after self.mainLoop()` has completed and returned the control back.
In case of using after() method, it behaves something like it registers an event to be fired after some amount of time, and immediately returns, it does not wait for that much amount of time and neither for the function to return. Hence the control immediately gets returned and it calls root.mainloop() to show the GUI.

python time.sleep() delays previous commands

When trying to interrupt a tkinter application, it seems that time.sleep() puts some previous command on the hold. According to my understanding and previous experiences, label1's text should be set to "before" for one second and then changed to "after". However, the "before" value never shows up and "after" gets printed normally one second after execution.
from tkinter import *
import time
class Display(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
label1 = Label(text = "before")
label1.grid()
self.after(1000, label1.config(text = "after"))
def main():
root = Tk()
Display(root)
root.mainloop()
if __name__ == '__main__':
main()
Note that using time.sleep(...) yeilds the same result as the tkinter after(...)
from tkinter import *
import time
class Display(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
label1 = Label(text = "before")
label1.grid()
time.sleep(1)
label1.config(text = "after")
def main():
root = Tk()
Display(root)
root.mainloop()
if __name__ == '__main__':
main()
I suppose tkinter is waiting for something to execute the graphical work (console don't have the problem) but I don't see what and the tkinter doc dosen't tackle that issue.
Is there a simple way to get the obvious expected result?
time.sleep(...) does not give the same result as the after(...) call.
The time.sleep method runs, but does not show anything before the second has passed. You can see this by putting print('before') before your sleep call. You'll see the print statement is executed one second before the window is created. This is due to Tkinter not updating anything during a sleep. So nothing happens until the sleep is over, after which the label is immediately updated. Moreover, the mainloop isn't called before the sleep is over, so Tkinter isn't in its mainloop yet. You can force Tkinter to update by using self.parent.update() right before your sleep call. You will see that the window shows, having the label 'before', waits one second and the label changes to 'after'. However, during this second the window is unresponsive, which is why using sleep together with Tkinter is a bad idea.
Instead of sleep, after is almost always the better option since it returns immediately, so it doesn't block further execution, and schedules the specified function to be executed after the specified amount of time. However, after expects a function name to be passed, but you pass a function call. This means that at the time that the after call is evaluated, the function is run and the return value (which is None) is passed as function name. Therefore, you see that the label is changed at the time the window opens, because the change is made at the time the after call is evaluated. What you should do is pass a function name to after:
def update_label:
label1.config(text = "after")
self.after(1000, update_label)
Or shorter, by creating an anonymous function:
self.after(1000, lambda: label1.config(text = "after"))
This will give you the expected result of showing a label with 'before', which changes to 'after' after a second, without blocking Tkinter's mainloop.
It's not surprising that doesn't work. Think about what the Tk framework is doing: it's creating your windows, display = Display(), and at that moment the thread stops. The object is not fully created. Do you expect Tk to render an object that's half way through its constructor?
First, try moving any thread-related code out of the constructor. The constructor should be allowed to finish normally, and thread-code should be in another function.
Second, it's bad practice to use thread.sleep in the middle of your normal logic. Normally, you start a separate thread, and that thread can wait and sleep if it wants to. (Though I don't know whether Tk has a special way of doing things.)

Destroying a Toplevel in a thread locks up root

I have a program I've been writing that began as a helper function for me to find a certain report on a shared drive based on some information in that report. I decided to give it a GUI so I can distribute it to other employees, and have ran into several errors on my first attempt to implement tkinter and threading.
I'm aware of the old adage "I had one problem, then I used threads, now I have two problems." The thread did, at least, solve the first problem -- so now on to the second....
My watered down code is:
class GetReport(threading.Thread):
def __init__(self,root):
threading.Thread.__init__(self)
# this is just a hack to get the StringVar in the new thread, HELP!
self.date = root.getvar('date')
self.store = root.getvar('store')
self.report = root.getvar('report')
# this is just a hack to get the StringVar in the new thread, HELP!
self.top = Toplevel(root)
ttk.Label(self.top,text="Fooing the Bars into Bazes").pack()
self.top.withdraw()
def run(self):
self.top.deiconify()
# a function call that takes a long time
self.top.destroy() #this crashes the program
def main():
root = Tk()
date,store,report = StringVar(),StringVar(),StringVar()
#####
## labels and Entries go here that define and modify those StringVar
#####
def launchThread(rpt):
report.set(rpt)
# this is just a hack to get the StringVar in the new thread, HELP!
root.setvar('date',date.get())
root.setvar('store',store.get())
root.setvar('report',report.get())
# this is just a hack to get the StringVar in the new thread, HELP!
reportgetter = GetReport(root)
reportgetter.start()
ttk.Button(root,text="Lottery Summary",
command=lambda: launchThread('L')).grid(row=1,column=3)
root.mainloop()
My expected output is for root to open and populate with Labels, Entries, and Buttons (some of which are hidden in this example). Each button will pull data from the Entries and send them to the launchThread function, which will create a new thread to perform the foos and the bars needed to grab the paperwork I need.
That thread will launch a Toplevel basically just informing the user that it's working on it. When it's done, the Toplevel will close and the paperwork I requested will open (I'm using ShellExecute to open a .pdf) while the Thread exits (since it exits its run function)
What's ACTUALLY happening is that the thread will launch its Toplevel, the paperwork will open, then Python will become non-responsive and need to be "end processed".
As far as I know you cannot use Threading to alter any GUI elements. Such as destroying a Toplevel window.
Any Tkinter code needs to be done in the main loop of your program.
Tkinter cannot accept any commands from threads other than the main thread, so launching a TopLevel in a thread will fail by design since it cannot access the Tk in the other thread. To get around this, use the .is_alive method of threads.
def GetReport(threading.Thread):
def __init__(self,text):
self.text = text
super().__init__()
def run(self):
# do some stuff that takes a long time
# to the text you're given as input
def main():
root = Tk()
text = StringVar()
def callbackFunc(text):
top = Toplevel(root)
ttk.Label(top,text="I'm working here!").pack()
thread = GetReport(text)
thread.start()
while thread.is_alive():
root.update() # this keeps the GUI updating
top.destroy() # only when thread dies.
e_text = ttk.Entry(root,textvariable=text).pack()
ttk.Button(root,text="Frobnicate!",
command = lambda: callbackFunc(text.get())).pack()

Categories