I've been developing a tkinter application for downloading a file from the internet and showing the progress of it on a progress bar (Progressbar).
Since the download process was different from the GUI, (and since tkinter runs the GUI the main thread on an infinite loop), I figured to make a different thread for the download process to carry on, and it would update the progress bar in the background
I would then go on to wait for the background process to end, and call the root.destroy() method to end the application, but the application doesn't close, on closing the application manually - it would return the following error
_tkinter.TclError: can't invoke "destroy" command: application has been destroyed
Here is the code for the
class download:
#Initializing the root
def __init__(self):
self.root = Tk()
#creating the progress bar and packing it to the root
def create_progress_bar(self):
self.progress_bar = Progressbar(self.root, orient = HORIZONTAL, length = 200, mode = 'determinate')
self.progress_bar.pack(pady=10)
#function to start the mainloop
def start_application(self):
self.root.mainloop()
#updating the progress bar from the background thread
def update_progress(self, count, blocksize, totalsize):
update = int(count * blocksize * 100 / totalsize)
self.progress_bar['value'] = update
self.root.update_idletasks()
#downloading the file from the background thread
def download_file(self, download_url):
#using the reporthook to get information about download progress
urllib.request.urlretrieve(download_url, 'a.jpg', reporthook=self.update_progress)
#closing the root function
def close(self):
self.root.destroy()
#handling the download
def handle_download(self, download_url):
#creating a different thread
self.process = Thread(target=self.download_file, args=(download_url,))
#creating the progress bar
self.create_progress_bar()
#starting the background thread
self.process.start()
#starting the GUI
self.start_application()
#waiting for the background thread to end
self.process.join()
#closing the application
self.close()
ob = download()
ob.handle_download('link')
For what it's worth, I did try to make things happen on the same thread, but the application could not respond to the updates.
Any insight on the matter would be extremely welcome.
Since your program does self.root.destroy() only after self.root.mainloop() has returned, it does not reach the point of destruction unless you're closing the application manually, and then it's pointless to call .destroy().
You could check from time to time whether the download thread is running, and destroy the window when not.
def watch(self):
if self.process.is_alive(): self.root.after(500, self.watch)
else: self.close()
#handling the download
def handle_download(self, download_url):
…
#starting the background thread
self.process.start()
self.watch()
…
#closing the application
#self.close() is too late here
Related
My question is regarding initializing a GUI built with tkinter. I'm starting another thread within the GUI that's running a script which takes 10 minutes to finish. The reason I'm doing this in another thread is to be able to keep the GUI responsive during these 10 minutes.
Right now I'm trying to do the following (simplified)
import tkinter as tk
from threading import Thread
class GUI:
def __init__(self):
self.master = tk.Tk()
self.master.geometry("1400x700")
//
...
//
self.master.mainloop()
def run_long_script(self): # Called by button in GUI
self.t1 = Thread(target = long_script)
self.start()
def long_script(self):
try:
...
except InterruptedError as error:
GUI()
This works okay, but when I try to close the GUI with long_script running, I get the error message main thread not in main loop. How should I design the code to be able to close the program correctly?
Tkinter like many other GUIs can use widgets only in main thread
(or rather in thread which runs mainlooop).
Other thread has to update global value (which is shared between threads) or use queue to send value to main thread. And main thread has to use after(milliseconds, function_name) to run periodically function which will get value from global variable or from queue and update progress bar.
Minimal working code.
import threading
import time
import tkinter as tk
import tkinter.ttk as ttk
# --- functions ---
def long_script():
global progress_value
for i in range(20):
print('loop:', i)
# update global variable
progress_value += 5
time.sleep(.5)
def run_long_script():
global progress_value
global t
if t is None: # run only one thread
# set start value
progress_value = 0
# start updating progressbar
update_progressbar()
# start thread
t = threading.Thread(target=long_script)
t.start()
else:
print('Already running')
def update_progressbar():
global t
# update progressbar
pb['value'] = progress_value
if progress_value < 100:
# run it again after 100ms
root.after(100, update_progressbar)
else:
# set None so it can run thread again
t = None
# --- main ---
# default value at start
progress_value = 0
t = None
# - gui -
root = tk.Tk()
pb = ttk.Progressbar(root, mode="determinate")
pb.pack()
b = tk.Button(root, text="start", command=run_long_script)
b.pack()
root.mainloop()
im trying to learn python. But i have problems with the threading. First i failed in the "Proces" class because i putted the loop on the wrong place and my program newer returned from the other class.
But now i think all is correct and it still does not work. I need to have a GUI where i want to be able to write my conditions via text entries and i need another class "Proces" that will do stuff, checking status ower internet and so on constantly or in a specified interval...
The Problem is that my tkinter GUI is freezing after pressing something
here is my GUI.py file:
import tkinter as tk
from Proces import Proces
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
button = tk.Button(frame, text="QUIT", fg="red",command=quit).pack(side=tk.LEFT)
pr = Proces()
print("\nGUI: proces init...")
pr.start()
print("\nGUI: Start ended")
root.mainloop()
here is the Proces.py file:
import time, threading
class Proces(threading.Thread):
def loop(self):
while True:
time.sleep(2)
print("\nProces: looping")
def __init__(self):
threading.Thread.__init__(self)
print("\nProces: Starting proces")
time.sleep(2)
def run(self):
self.deamon = True
print("\nProces: Starting loop")
self.loop()
*This is the output: *
Proces: Starting proces
GUI: proces init...
Proces: Starting loop
GUI: Start ended
Proces: looping
Proces: looping
Proces: looping
Proces: looping
*But the GUI of the tkinter does not react.*
How should i do this kind of task?
Thank you for your help, advice and answer
I think you don't have problem about starting thread etc. However, you should be able to control your thread, meaning your thread should return based on some condition. It seems you have a button to quit. I assume you want to finish the process by clicking the button.
To do that, when you click to button, your main thread should pass a variable to Process in order to break your while loop, which is your thread basically.
Here is a code that you can work on.
import tkinter as tk
import time
import threading
class MainWindow:
def __init__(self, master):
self.master = master
self.quit_button = tk.Button(self.master, text="QUIT", command=lambda:self.quit(), width=20)
self.quit_button.pack(side=tk.LEFT)
self.process = None
print("\nGUI: process init...")
self.start_task()
def quit(self):
self.process.stop_process = True
print("\nGUI: Start ended")
def start_task(self):
self.process = Process()
self.process.start()
class Process(threading.Thread):
def loop(self):
while True:
if not self.stop_process:
time.sleep(2)
print("\nProcess: looping")
else:
print("\nProcess: looping ended")
return
def __init__(self):
threading.Thread.__init__(self)
self.stop_process = False
print("\nProcess: Starting proces")
time.sleep(2)
def run(self):
self.deamon = True
print("\nProcess: Starting loop")
self.loop()
if __name__ == '__main__':
root = tk.Tk()
app = MainWindow(master=root)
root.mainloop()
So you start yout tkinter, which is your main thread. Then iniating another class within your main thread, which is inherits the thread. So you have two seperate thread that working. When you clicked to "quit" button, you pass variable stop_process, which breaks the loop and return, meaning ending your thread. Your main thread is still alive for running your tkinter window.
I hope it will help
I try to build a GUI application to grab frames from a camera and display them in a Tkinter GUI. The Tkinter mainloop is executed in the main thread, while the frame grabbing and updating of the gui takes place in a separate thread.
The code below works as a video stream is grabbed and displayed correctly in my gui window. However when I invoke the on_close() method by clicking the "x" to close the gui, the gui will close, but the program won't terminate fully. Last CLI output will be "Mainloop stopped!", but the program does not terminate as I would expect. So, I suspect the additional thread keeps on running, even though I quit the while loop in the threads run() method via the stop event.
This is my code:
import threading
import cv2
import tkinter
from tkinter import ttk
from PIL import Image
from PIL import ImageTk
import camera
class mainThread(threading.Thread):
def __init__(self, gui):
threading.Thread.__init__(self)
self.stop_event = threading.Event()
self.gui = gui
def stop(self):
print("T: Stop method called...")
self.stop_event.set()
def run(self):
print("Connecting to camera...")
cam = camera.Camera(resolution=(1280, 960), exposure=-3, bit_depth=12)
cam.connect(device_id=0)
while (not self.stop_event.is_set()):
print("running..., stop event = " + str(self.stop_event.is_set()))
# retrieve frame and frame time
frame, _, _ = cam.get_frame()
# display frame
self.gui.updater(frame)
# wait for displaying image
cv2.waitKey(1)
class gui(object):
def __init__(self, root):
self.root = root
# create and start thread running the main loop
self.main_thread = mainThread(self)
self.main_thread.start()
# bind on close callback that executes on close of the main window
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
# change window title
self.root.title("MCT Laser Welding Quality Control")
# set min size of root window and make window non resizable
self.root.minsize(600, 400)
self.root.resizable(False, False)
# configure grid layout
self.root.rowconfigure(0, weight=1)
self.root.rowconfigure(1, weight=1)
self.root.columnconfigure(0, weight=1)
# create image panel
self.image_panel = None
def on_close(self):
self.main_thread.stop()
self.root.destroy()
def updater(self, image):
# TODO: resize frame first
# convert image to tkinter image format
image = Image.fromarray(image)
image = ImageTk.PhotoImage(image)
# if the panel does not exist, create and pack it
if self.image_panel is None:
# show the image in the panel
self.image_panel = ttk.Label(self.root, image=image)
self.image_panel.image = image # keep reference
# pack object into grid
self.image_panel.grid(row=0, column=0)
# just update the image on the panel
else:
self.image_panel.configure(image=image)
self.image_panel.image = image # keep reference
def run(self):
self.root.mainloop()
if __name__ == "__main__":
# create a main window
root = tkinter.Tk()
# set style
style = ttk.Style()
style.theme_use("vista")
# create gui instance
user_interface = gui(root)
# run the user interface
root.mainloop()
print("Mainloop stopped!")
EDIT:
I found out, that the line
image = ImageTk.PhotoImage(image)
is preventing the thread from stopping as described above. When I remove this line and simply update the label text with an incrementing number, everything works as excepted and the thread terminates, when I close the gui window. Any ideas, why ImageTk.PhotoImage() causes the thread to not terminate properly?
You need to join the thread to wait for it to close.
def stop(self):
print("T: Stop method called...")
self.stop_event.set()
self.join()
Also, you may need to close the open connection to the camera.
while (not self.stop_event.is_set()):
...
cam.close() # not sure about exact API here
Can somebody help me please, I'm making an exercise about class and running task on other thread then tkinter. I want to change the label in another class. Can't get my script to work.
I tried different things but I'm having some troubles with understanding the inheriting from classes and the threads, so this is just an example to learn more about it.
from tkinter import *
import tkinter as tk
from tkinter import ttk
import threading
#Gloabl for stopping the run task
running = True
#class 1 with window
class App():
def __init__(self):
#making the window
self.root = tk.Tk()
self.root.geometry("400x400+300+300")
self.root.protocol("WM_DELETE_WINDOW", self.callback)
self.widgets()
self.root.mainloop()
# stop task and close window
def callback(self):
global running
running = False
self.root.destroy()
# all the widgets of the window
def widgets(self):
global labelvar
#startbutton
self.start_button = tk.Button(self.root, text="Start", command=lambda:App2())
self.start_button.pack()
#stopbutton
self.stop_button = tk.Button(self.root, text="Stop", command=lambda:self.stop())
self.stop_button.pack()
#Defining variable for text for label
labelvar = "Press start to start running"
self.label = tk.Label(self.root, text=labelvar)
self.label.pack()
#stop the task
def stop(self):
global running
running = False
#class 2 with task in other thread
class App2(threading.Thread):
def __init__(self):
global running
#check if task can be run
running = True
threading.Thread.__init__(self)
self.start()
def run(self):
#starting random work
for i in range(10000):
print(i)
labelvar = "running"
App.label.pack()
#checking if task can still be running else stop task
if running == False:
break
labelvar = "stopped"
App.label.pack()
#initiate main app
app = App()
As I said in a comment, tkinter doesn't support multithreading itself, but you can do it as long as only one thread, usually the main one, uses (or "talks") it.
If you want to affect what the GUI displays, the other thread(s) must communicate somehow with the GUI thread. This is often done through a queue.Queue, but in this relatively simple case it can be done through a global variable provided that concurrent access to it is controlled by some means—sharing memory space (i.e. global variables) is one of the advantages of multithreading vs multitasking, but it has to be done and done correctly.
An easy way to share a resource like this is by using a threading.Lock dedicated for that purpose. (See the Wikipedia article Lock (computer science) for more details.)
All references to this shared resource (the running flag) should only be done after "acquiring" the Lock and "releasing" it afterwards. Fortunately it's trivial to do this using a Python with statement (as shown below).
Another crucial aspect of the multithreading problem is how any information exchanged between the two threads is processed. In this case I choose to make the tkinter thread poll the running flag, watch for changes, and update any affected widgets accordingly. This can be done by using the universal widget method after() which tells tkinter to schedule a future call (inside the 'mainloop') to a user-supplied function or method and to pass it certain arguments. To get this to happen repeatedly, the called function can reschedule itself to run again by calling after() before it finishes.
Below is a modified version of your code that does these thing. Note that App2 never calls tkinter or touches any of its widgets, which is why it works.
import threading
from time import sleep
from tkinter import *
import tkinter as tk
from tkinter import ttk
DELAY = 100 # millisecs between status label updates
# global flag and a Lock to control concurrent access to it
run_flag_lock = threading.Lock()
running = False
# class 1 with window
class App():
def __init__(self):
global running
self.root = tk.Tk()
self.root.geometry("400x400+300+300")
self.root.protocol("WM_DELETE_WINDOW", self.quit)
self.create_widgets()
with run_flag_lock:
running = False
self.root.after(DELAY, self.update_status, None) # start status widget updating
self.root.mainloop()
# create all window widgets
def create_widgets(self):
self.start_button = tk.Button(self.root, text="Start", command=self.start)
self.start_button.pack()
self.stop_button = tk.Button(self.root, text="Stop", command=self.stop)
self.stop_button.pack()
self.status_label = tk.Label(self.root, text='')
self.status_label.pack()
def update_status(self, run_state):
""" Update status label text and state of buttons to match running flag. """
# no need to declare run_flag_lock global since it's not being assigned a value
with run_flag_lock:
if running != run_state: # status change?
if running:
status_text = 'Press Stop button to stop task'
run_state = True
else:
status_text = 'Press Start button to start task'
run_state = False
self.status_label.config(text=status_text)
# also update status of buttons
if run_state:
self.start_button.config(state=DISABLED)
self.stop_button.config(state=ACTIVE)
else:
self.start_button.config(state=ACTIVE)
self.stop_button.config(state=DISABLED)
# run again after a delay to repeat status check
self.root.after(DELAY, self.update_status, run_state)
# start the task
def start(self):
global running
with run_flag_lock:
if not running:
app2 = App2() # create task thread
app2.start()
running = True
# stop the task
def stop(self):
global running
with run_flag_lock:
if running:
running = False
# teminate GUI and stop task if it's running
def quit(self):
global running
with run_flag_lock:
if running:
running = False
self.root.destroy()
# class 2 with task in another thread
class App2(threading.Thread):
def __init__(self):
super(App2, self).__init__() # base class initialization
self.daemon = True # allow main thread to terminate even if this one is running
def run(self):
global running
# random work
for i in range(10000):
print(i)
# Normally you shouldn't use sleep() in a tkinter app, but since this is in
# a separate thread, it's OK to do so.
sleep(.25) # slow printing down a little
# stop running if running flag is set to false
with run_flag_lock:
if not running:
break # stop early
with run_flag_lock:
running = False # task finished
# create (and start) main GUI app
app = App()
I'm attempting to create a Tkinter GUI that runs the mainloop in its own thread, enabling me to run things in other threads (which potentially involves updating elements in the Tkinter GUI). I have the construct below.
import tkinter as tk
from threading import Thread
class DisplayWindow(object):
def __init__(self):
self.running = False
def start(self):
self.running = True
self.thread = Thread(target = self.run)
self.thread.start()
def callback(self):
self.running = False
self.root.destroy()
def run(self):
self.root = tk.Tk()
self.root.protocol('WM_DELETE_WINDOW', self.callback)
self.root.geometry('600x600')
tk.Label(self.root, text='Hello World').pack()
self.root.mainloop()
I can do something like the following with this
win = DisplayWindow()
win.start()
My understanding of how this works is that when win.start() is called the first time, a new thread is created, set to run the run method, and the thread is started. This thread executes the creation of a Tkinter GUI that runs the mainloop until the window is closed (at which time the thread should become inactive). If I close the window and call win.start() again, I expect that the second call should just repeat the process, creating a new thread that calls the run method. However, calling start a second time simply crashes python.
What am I doing wrong?