I am trying to execute a Tkinter ttk Button method while the button is pressed, meaning I want the method to keep executing when the button is pressed and stop when I release it, but i can't quite figure it out. Here is the code.
from tkinter import *
from tkinter import ttk
class stuff (object):
def __init__(self, master):
master.title("Grid Master")
master.frame_1 = ttk.Frame(master)
master.frame_1.pack()
master.configure(background = "#FFFFFF")
self.button = ttk.Button(master, text = 'Press', command = self.callback)
self.button.pack()
def callback(self):
print ("Hello world")
def main():
root = Tk()
loop = stuff(root)
root.mainloop()
if __name__ == '__main__':
main()
You can see in the code that the method only prints "Hello world" and I want this function to execute and keep going, printing Hello world until I release the button.
You can use root.after() to repeatedly schedule a job to perform the required task. Note that I have changed the button event to activate when the button is pressed, and to terminate the "after" job when the button is released.
try:
from tkinter import *
from tkinter import ttk
except ImportError:
# Python 2, probably
from Tkinter import *
import ttk
class stuff (object):
def __init__(self, master):
self._master = master
master.title("Grid Master")
master.frame_1 = ttk.Frame(master)
master.frame_1.pack()
master.configure(background = "#FFFFFF")
self.button = ttk.Button(master, text = 'Press')
self.button.bind("<Button-1>", self.button_pressed)
self.button.bind("<ButtonRelease-1>", self.button_released)
self.button.pack()
self.hello_world_frequency = 1 # milliseconds
def hello_world(self):
print ("Hello world")
self._job = self._master.after(self.hello_world_frequency, self.hello_world)
def button_pressed(self, event):
print ("Button down")
self.hello_world()
def button_released(self, event):
print ("Button released")
self._master.after_cancel(self._job)
def main():
root = Tk()
loop = stuff(root)
root.mainloop()
if __name__ == '__main__':
main()
You can make a custom repeating ttk button class that inherits from ttk.Button but adds basic repeating functionality.
Try this. you can use it like
self.button = RepeatButton(master, text='Press', command=self.callback)
and you can set the repeatdelay and repeatinterval arguments, which default to 300 and 100.
class RepeatButton(ttk.Button):
def __init__(self, *args, **kwargs):
self.callback = kwargs.pop('command', None)
self.repeatinterval = kwargs.pop('repeatinterval', 100)
self.repeatdelay = kwargs.pop('repeatdelay', 300)
ttk.Button.__init__(self, *args, **kwargs)
if self.callback:
self.bind('<ButtonPress-1>', self.click)
self.bind('<ButtonRelease-1>', self.release)
def click(self, event=None):
self.callback()
self.after_id = self.after(self.repeatdelay, self.repeat)
def repeat(self):
self.callback()
self.after_id = self.after(self.repeatinterval, self.repeat)
def release(self, event=None):
self.after_cancel(self.after_id)
Related
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()
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.
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.
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()
import tkinter as tk
class App():
def __init__(self):
self.root = tk.Tk()
self.root.config(padx=100, pady=50)
self.root.geometry('800x600')
self.text = tk.Text()
self.entry = tk.Entry()
self.text.place(x=0, y=0)
self.text.after(5000, self.clear_text) # 5000ms
self.root.mainloop()
def clear_text(self):
print ("Le text vient d'etre détruit")
self.text.place_forget()
app = App()
Hello to all,
I am trying to create an Tkinter app where i put some text in a box and if i stop typing for few seconds the text should disappear. I am a beginner, i am stuck!! Thank for giving me an idea!!Yours
Use after and bind Key event to the same method. Use an if statement to check if the event parameter of the method is None, if it is None delete the text.
import tkinter as tk
class App():
def __init__(self):
self.root = tk.Tk()
self.root.config(padx=100, pady=50)
self.root.geometry('800x600')
self.delay = 5000
self.after_id = None
self.text = tk.Text(self.root)
self.text.bind("<Key>", self.clear_text)
self.text.place(x=0, y=0)
self.root.mainloop()
def clear_text(self, event=None):
if not event:
self.text.delete("1.0", "end")
else:
if self.after_id:
self.root.after_cancel(self.after_id)
self.after_id = None
self.after_id = self.root.after(self.delay, self.clear_text)
app = App()
Try this:
import tkinter as tk
from time import perf_counter
class App():
def __init__(self):
self.root = tk.Tk()
self.text = tk.Text(self.root)
self.text.pack()
self.text.bind("<Key>", self.text_entered)
self.text.after(100, self.check_clear_text)
self.time_last_written = perf_counter()
self.root.mainloop()
def text_entered(self, event):
self.time_last_written = perf_counter()
def clear_text(self):
print("Le text vient d'etre détruit")
# Delete all of the text
self.text.delete("0.0", "end")
def check_clear_text(self):
time_diff = perf_counter() - self.time_last_written
print(f"Last key pressed: {time_diff} sec ago.")
if time_diff > 5: # The 5 is in seconds
self.clear_text()
self.text.after(100, self.check_clear_text)
app = App()
It keeps track of the last time you pressed a key and if 5 seconds have passed, it deletes all of the widget's contents using: self.text.delete("0.0", "end")
I want to start a webserver if the user does click on a button. Therefore I have written the following code:
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import messagebox
import time
from multiprocessing import Process
class MainWindow(ttk.Frame):
def __init__(self, parent):
self.parent = parent
ttk.Frame.__init__(self, master=self.parent)
self.parent.grab_set()
self.parent.title("Test_Program")
self.parent.geometry("500x500")
ttk.Button(self.parent, text="Start Web-Server", command=self.start_webserver).pack()
def start_webserver(self):
if __name__ == '__main__':
loading_screen = LoadingScreen(self)
result = [0]
sub_process = Process(target=start_webserver, args=(result,))
sub_process.start()
sub_process.join()
sub_process.terminate()
loading_screen.destroy()
if result[0] == 0:
messagebox.showinfo("Webserver Status", "Webserver is running!")
else:
messagebox.showerror("Webserver Status", "Webserver can't be started an Error occured!")
class LoadingScreen(tk.Toplevel):
def __init__(self, parent):
self.parent = parent
tk.Toplevel.__init__(self, master=self.parent)
self.geometry("200x50")
self.title("Loading Screen")
self.transient(self.parent)
self.grab_set()
ttk.Label(self, text="Copying data!").pack()
status_bar = ttk.Progressbar(self, length=180, mode="indeterminate")
status_bar.start(5)
status_bar.pack()
def __del__(self):
if isinstance(self.parent, tk.Toplevel):
self.parent.grab_set()
def start_webserver(result):
time.sleep(2) # This does represent the time thats necessary to start the webserver
result[0] = 1
def main():
root = tk.Tk()
main_window = MainWindow(root)
main_window.pack()
root.mainloop()
if __name__ == "__main__":
main()
If I click on "start webserver" its two seconds frozen because of time.sleep(2) and no progress bar does get displayed. Afterwards its done. I don't know why there is no progressbar displayed. I have used an array named "result" as parameter to get the return value of start_webserver(result) from sub_process to main_process. Unfortunately this is not working too because the value of "result[0]" stays 0. I don't know what to do. The idea of using an array I have found on stackoverflow too.
Here is the Link:
how to get the return value from a thread in python?
I have found out an answer by my own. Thanks for your help join() wasn't right. I have done it a different way and closing the "loading_screen" as well as opening the messagebox after the job has been done in the second thread.
That's the Code:
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import messagebox
import time
from threading import Thread
class MainWindow(ttk.Frame):
def __init__(self, parent):
self.parent = parent
ttk.Frame.__init__(self, master=self.parent)
self.parent.grab_set()
self.parent.title("Test_Program")
self.parent.geometry("200x50")
ttk.Button(self.parent, text="Start Web-Server", command=self.start_webserver).pack()
def start_webserver(self):
if __name__ == '__main__':
loading_screen = LoadingScreen(self)
thread = Thread(target=start_webserver, args=(loading_screen, ))
thread.start()
class LoadingScreen(tk.Toplevel):
def __init__(self, parent):
self.parent = parent
tk.Toplevel.__init__(self, master=self.parent)
self.geometry("200x50")
self.title("Loading Screen")
self.transient(self.parent)
self.grab_set()
ttk.Label(self, text="Copying data!").pack()
status_bar = ttk.Progressbar(self, length=180, mode="indeterminate")
status_bar.start(2)
status_bar.pack()
def __del__(self):
if isinstance(self.parent, tk.Toplevel):
self.parent.grab_set()
def start_webserver(widget):
return_val = do_some_stuff()
if return_val:
widget.destroy()
messagebox.showinfo("Webserver Status", "Webserver is running!")
else:
widget.destroy()
messagebox.showerror("Webserver Status", "Webserver can't be started an Error occured!\n"
"Please look inside 'Logfiles/logfile' for more details.")
def do_some_stuff():
time.sleep(2) # This does represent the time thats necessary to start the webserver
return True
def main():
root = tk.Tk()
main_window = MainWindow(root)
main_window.pack()
root.mainloop()
if __name__ == "__main__":
main()
I am new to tkinter and would like to do a UI based on the flags shown.
Basically, i would like to close one windows and open another window with the present state or delete the text and show another text with the present state.
class App(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def callback(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.callback)
label = tk.Label(self.root, text="Start Initialization")
label.pack()
self.root.mainloop()
class QQQ:
def quit(self):
self.delete(1.0,END)
class Appo(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def callback(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.callback)
label = tk.Label(self.root, text="Initialization ended")
label.pack()
self.root.mainloop()
for i in range(100000):
time.sleep(0.5)
print(i)
if(i==1):
app = App()
time.sleep(1)
qqq=QQQ()
if(i==10):
app=Appo()
If all you want to do is change the text of the label, then use the config method on the label. The App, Appo, and QQQ classes, as well as the for loop can be combined into a single class as:
import Tkinter as tk #Python 2
#import tkinter as tk #Python 3
import threading
import time
class App(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.root = tk.Tk()
self.start()
def callback(self):
self.root.quit()
def run(self):
self.root.protocol("WM_DELETE_WINDOW", self.callback)
label = tk.Label(self.root, text="Initial Text") # You can use text=""
label.pack()
for i in range(100000):
time.sleep(0.5)
print (i)
if i == 1:
label.config(text="Start Initialization")
time.sleep(1)
label.config(text="")
if i == 10:
label.config(text="Initialization ended")
#self.root.mainloop()
app = App()
app.root.mainloop()
It might be better to use the after method of tkinter for time delays instead of time.sleep()
I'm trying to build a simple program to remind me to take breaks while using the computer. I have a reasonable understanding of python but have never played with GUI programming or threading before, so the following is essentially copying/pasting from stackoverflow:
import threading
import time
import Tkinter
class RepeatEvery(threading.Thread):
def __init__(self, interval, func, *args, **kwargs):
threading.Thread.__init__(self)
self.interval = interval # seconds between calls
self.func = func # function to call
self.args = args # optional positional argument(s) for call
self.kwargs = kwargs # optional keyword argument(s) for call
self.runable = True
def run(self):
while self.runable:
self.func(*self.args, **self.kwargs)
time.sleep(self.interval)
def stop(self):
self.runable = False
def microbreak():
root = Tkinter.Tk()
Tkinter.Frame(root, width=250, height=100).pack()
Tkinter.Label(root, text='Hello').place(x=10, y=10)
threading.Timer(3.0, root.destroy).start()
root.mainloop()
return()
thread = RepeatEvery(6, microbreak)
thread.start()
This gives me the first break notification but fails before giving me a second break notification.
Tcl_AsyncDelete: async handler deleted by the wrong thread
fish: Job 1, “python Documents/python/timer/timer.py ” terminated by
signal SIGABRT (Abort)
Any ideas? I'm happy to use something other than tkinter for gui-stuff or something other than threading to implement the time stuff.
Based on the answers below, my new working script is as follows:
import Tkinter as Tk
import time
class Window:
def __init__(self):
self.root = None
self.hide = 10 #minutes
self.show = 10 #seconds
def close(self):
self.root.destroy()
return
def new(self):
self.root = Tk.Tk()
self.root.overrideredirect(True)
self.root.geometry("{0}x{1}+0+0".format(self.root.winfo_screenwidth(), self.root.winfo_screenheight()))
self.root.configure(bg='black')
Tk.Label(self.root, text='Hello', fg='white', bg='black', font=('Helvetica', 30)).place(anchor='center', relx=0.5, rely=0.5)
#Tk.Button(text = 'click to end', command = self.close).pack()
self.root.after(self.show*1000, self.loop)
def loop(self):
if self.root:
self.root.destroy()
time.sleep(self.hide*60)
self.new()
self.root.mainloop()
return
Window().loop()
I think it would be easier for you to achieve this without threads, which Tkinter does not integrate with very well. Instead, you can use the after and after_idle methods to schedule callbacks to run after a certain timeout. You can create one method that shows the window and schedules it to be hidden, and another that hides the window and schedules it to be shown. Then they'll just call each other in an infinite loop:
import tkinter
class Reminder(object):
def __init__(self, show_interval=3, hide_interval=6):
self.hide_int = hide_interval # In seconds
self.show_int = show_interval # In seconds
self.root = Tkinter.Tk()
tkinter.Frame(self.root, width=250, height=100).pack()
tkinter.Label(self.root, text='Hello').place(x=10, y=10)
self.root.after_idle(self.show) # Schedules self.show() to be called when the mainloop starts
def hide(self):
self.root.withdraw() # Hide the window
self.root.after(1000 * self.hide_int, self.show) # Schedule self.show() in hide_int seconds
def show(self):
self.root.deiconify() # Show the window
self.root.after(1000 * self.show_int, self.hide) # Schedule self.hide in show_int seconds
def start(self):
self.root.mainloop()
if __name__ == "__main__":
r = Reminder()
r.start()
I agree with dano. I thought i'd also contribute though, as my way is somewhat smaller than dano's, but uses time for the gap between when the window is visible. hope this helps #vorpal!
import Tkinter
import time
root = Tkinter.Tk()
def close():
root.destroy()
def show():
root.deiconify()
button.config(text = 'click to shutdown', command = close)
def hide():
root.withdraw()
time.sleep(10)
show()
button = Tkinter.Button(text = 'click for hide for 10 secs', command = hide)
button.pack()
root.mainloop()