Reputation: 381
I am making a tkinter GUI that requests information to a server that takes some time to respond. I really don't know how to tell tkinter to wait for the response in a clever way so that the window loop doesnt freeze.
What I want to achieve is to make the popup window responsive, and to see the animation of the progressbar. I don't really know if it helps but I intend to use this GUI on Windows.
Here is my code: (I used time.sleep to simulate sending and recieving from a server)
import tkinter as tk
import tkinter.ttk as ttk
import time
def send_request(data):
# Sends request to server, manages response and returns it
time.sleep(10)
class Window(tk.Toplevel):
def __init__(self, root, *args, **kargs):
super().__init__(root, *args, **kargs)
# Options of the window
self.geometry("500x250")
self.resizable(False, False)
self.grab_set()
# Widgets of the window
self.button = tk.Button(self, text="Send Request", command=self.start_calc)
self.button.pack()
self.bar = ttk.Progressbar(self, orient = "horizontal", mode= "indeterminate")
self.bar.pack(expand=1, fill=tk.X)
def start_calc(self):
# Prepares some data to be send
self.data_to_send = []
# Start bar
self.bar.start()
# Call send request
self.after(10, self.send_request_and_save_results)
def send_request_and_save_results(self):
# Send request with the data_to_send
result = send_request(self.data_to_send)
# Save results
# Close window
self.quit()
self.destroy()
class App:
def __init__(self, root):
self.root = root
self.button = tk.Button(root, text="Open Window", command=self.open_window)
self.button.pack()
def open_window(self):
window = Window(self.root)
window.mainloop()
root = tk.Tk()
root.geometry("600x300")
app = App(root)
root.mainloop()
Upvotes: 0
Views: 904
Reputation: 381
Thanks to @codester_09 suggestion of using threading.Thread
I have managed to get it working but I don't really understand why. After many errors this is what I did.
Now I wonder if what I did is safe.
import tkinter as tk
import tkinter.ttk as ttk
import time
from threading import Thread
def send_request_and_save_results(data):
global results
print("Start sending: ", data)
# Sends request to server, manages response and returns it
time.sleep(10)
print("Finished sending")
# Save results
results[0] = "Hello"
results[1] = "Hola"
class Window(tk.Toplevel):
def __init__(self, root, *args, **kargs):
super().__init__(root, *args, **kargs)
# Options of the window
self.geometry("500x250")
self.resizable(False, False)
self.grab_set()
# Widgets of the window
self.button = tk.Button(self, text="Send Request", command=self.start_calc)
self.button.pack()
self.bar = ttk.Progressbar(self, orient = "horizontal", mode= "indeterminate")
self.bar.pack(expand=1, fill=tk.X)
def start_calc(self):
# Prepares some data to be send
self.data_to_send = [1, 2, 3]
# Start bar
self.bar.start()
# Call send request
t1 = Thread(target=self.send_request_and_save_results)
t1.start()
def send_request_and_save_results(self):
# Send request with the data_to_send
send_request_and_save_results(self.data_to_send)
# Close window
self.after(10, self.close)
def close(self):
self.quit()
self.destroy()
class App:
def __init__(self, root):
self.root = root
self.button = tk.Button(root, text="Open Window", command=self.open_window)
self.button.pack()
def open_window(self):
window = Window(self.root)
window.mainloop()
#
root = tk.Tk()
root.geometry("600x300")
app = App(root)
results = [None] * 10
root.mainloop()
Upvotes: 0
Reputation: 7680
I came up with this solution:
import tkinter as tk
import tkinter.ttk as ttk
import time
from threading import Thread
def send_request_and_save_results(data, flag):
# This is called in another thread so you shouldn't call any tkinter methods
print("Start sending: ", data)
time.sleep(10)
print("Finished sending")
# Signal that this function is done
flag[0] = True
class Window(tk.Toplevel):
def __init__(self, root, *args, **kargs):
super().__init__(root, *args, **kargs)
# Options of the window
self.geometry("500x250")
self.resizable(False, False)
self.grab_set()
# Widgets of the window
self.button = tk.Button(self, text="Send Request", command=self.start_calc)
self.button.pack()
self.bar = ttk.Progressbar(self, orient="horizontal", mode="indeterminate")
self.bar.pack(expand=1, fill="x")
def start_calc(self):
# Prepares some data to be send
self.data_to_send = [1, 2, 3]
# Start bar
self.bar.start()
# Call send request
self.send_request_and_save_results()
def send_request_and_save_results(self):
# Create a flag that wukk signal if send_request_and_save_results is done
flag = [False]
# Send request with the data_to_send and flag
t1 = Thread(target=send_request_and_save_results,
args=(self.data_to_send, flag))
t1.start()
# A tkinter loop to check if the flag has been set
self.check_flag_close_loop(flag)
def check_flag_close_loop(self, flag):
# if the flag is set, close the window
if flag[0]:
self.close()
# Else call this function again in 100 milliseconds
else:
self.after(100, self.check_flag_close_loop, flag)
def close(self):
# I am pretty sure that one of these is unnecessary but it
# depends on your program
self.quit()
self.destroy()
class App:
def __init__(self, root):
self.root = root
self.button = tk.Button(root, text="Open Window", command=self.open_window)
self.button.pack()
def open_window(self):
window = Window(self.root)
window.mainloop()
root = tk.Tk()
root.geometry("600x300")
app = App(root)
root.mainloop()
Notice how all tkinter
calls are in the main thread. This is because sometimes tkinter
doesn't play nice with other threads.
All I did was call send_request_and_save_results
with a flag that the function sets when it is done. I periodically chech that flag in the check_flag_close_loop
method, which is actually a tkinter loop.
The flag is a list
with a single bool
(the simplest solution). That is because python passes mutable objects by reference and immutable objects by value.
Upvotes: 2
Reputation: 6224
Use threading
module to run your code and get the request parallelly
Just call your function like this.
t1 = Thread(target=window.send_request_and_save_results)
t1.start()
Updated Code
import tkinter as tk
import tkinter.ttk as ttk
import time
from threading import Thread
def send_request(data):
# Sends request to server, manages response and returns it
time.sleep(10)
class Window(tk.Toplevel):
def __init__(self, root, *args, **kargs):
super().__init__(root, *args, **kargs)
# Options of the window
self.geometry("500x250")
self.resizable(False, False)
self.grab_set()
# Widgets of the window
self.button = tk.Button(self, text="Send Request", command=self.start_calc)
self.button.pack()
self.bar = ttk.Progressbar(self, orient = "horizontal", mode= "indeterminate")
self.bar.pack(expand=1, fill=tk.X)
def start_calc(self):
# Prepares some data to be send
self.data_to_send = []
# Start bar
self.bar.start()
# Call send request
self.after(10, self.send_request_and_save_results)
def send_request_and_save_results(self):
# Send request with the data_to_send
result = send_request(self.data_to_send)
# Save results
# Close window
self.quit()
self.destroy()
class App:
def __init__(self, root):
self.root = root
self.button = tk.Button(root, text="Open Window", command=self.open_window)
self.button.pack()
def open_window(self):
window = Window(self.root)
t1 = Thread(target=window.send_request_and_save_results)
t1.start()
window.mainloop()
root = tk.Tk()
root.geometry("600x300")
app = App(root)
root.mainloop()
I hope this will help you.
Upvotes: 0