When a Tkinter button is clicked and the command is run, the GUI seems frozen until the command returns.
Example, the counter wont update until after 2 seconds:
import tkinter as tk
import time
class Window():
def __init__(self):
self.clicks = 0
self.root = tk.Tk()
self.button_text = tk.StringVar(value="Click " + str(self.clicks))
self.button = tk.Button(self.root, textvariable=self.button_text,
command=self.click)
self.button.pack()
def click(self):
self.clicks += 1
self.button_text.set("Click " + str(self.clicks))
time.sleep(2)
if __name__ == '__main__':
Window().root.mainloop()
Is there any way to allow the window to be updated during a callback?
You can call the button's update_idletasks method:
def click(self):
self.clicks += 1
self.button_text.set("Click " + str(self.clicks))
##################################
self.button.update_idletasks()
##################################
time.sleep(2)
Adding that line to click will cause the button's text to update immediately.
The sleep is blocking the gui's event loop
Related
I want that when the "Start" button is pressed, it executes code and counts from 3 (3... 2... 1... Ready!), with delay between each and printing the numbers in the program window. I used tkinter. But I can't get the countdown to appear when I press the "Start" button. And I don't know how to add the code that is executed after pressing the button.
import tkinter as tk
from tkinter import ttk
import time
class Aplicacion:
def __init__(self,master):
self.master=master
self.master.title('Title')
self.master.geometry('290x50')
self.inicializar_gui()
def inicializar_gui(self):
lbl_bienvenido=tk.Label(self.master,text='Hi!',font=('Helvetica',10))
lbl_bienvenido.place(x=0,y=0)
btn_login=tk.Button(self.master,text='Start')
btn_login['command']=self.comando
btn_login.place(x=10,y=23)
def comando(self):
entry = ttk.Entry(state=tk.DISABLED, takefocus=False)
entry.place(x=63, y=25)
entry.insert(0, "1...")
# print("3")
# time.sleep(1)
# print("2")
# time.sleep(1)
# print("1")
# time.sleep(1)
# print("Ready!")
def main ():
root = tk.Tk()
ventana = Aplicacion(root)
root.mainloop()
if __name__ == "__main__":
main()
here is an exemple that you could adjust to your need :
import tkinter
root = tkinter.Tk()
txt = tkinter.Text(root)
a = 4
def countdown():
global a
if a>0:
txt.delete('1.0','end')
a -=1
txt.insert('0.0',str(a))
root.after(1000,countdown)
if a == 0 :
txt.delete('1.0','end')
txt.insert('0.0','Ready!!')
btn = tkinter.Button(root,text = 'START',command = countdown)
txt.pack()
btn.pack()
root.mainloop()
I am learning GUI development using Tkinter. I want to show multiple messages on the label which I have stored in a string. I used sleep to view the changes.However only the last message string is shown at execution.
from tkinter import *
import time
master = Tk()
def onClick():
for i in range(0,len(list_of_str)):
w.configure(text=list_of_str[i])
time.sleep(5)
list_of_str = ['first','second','third','fourth','fifth']
w = Label(master, text="Hello, world!")
b = Button(master,text='Click me',command = onClick)
w.pack()
b.pack()
mainloop()
I am a noobie. So thanks for helping !
A simple solution to your problem is to use a combination of the try/except method and using after().
In tkinter sleep() will pause the application instead of providing a timer. For tkinter you want to use the after() method to scheduled an event after a set amount of time instead. The after() method is meant for this exact problem and is what you will always use in tkinter for a delayed event.
In my below example I modified your onClick function to take 1 argument and to use that in our after() method to select the next item in the list after 5 seconds. Note that for the after() method time is done in milliseconds so 5000 is 5 seconds.
from tkinter import *
master = Tk()
def onClick(ndex):
try:
w.configure(text=list_of_str[ndex])
master.after(5000, onClick, ndex+1)
except:
print("End of list")
list_of_str = ['first','second','third','fourth','fifth']
w = Label(master, text="Hello, world!")
b = Button(master,text='Click me',command = lambda: onClick(0))
w.pack()
b.pack()
mainloop()
I think you want this:
from tkinter import *
import time
master = Tk()
global i
i = 0
def onClick():
master.after(1, change)
def change():
global i
if i == len(list_of_str):
pass
else:
w.configure(text=list_of_str[i])
i += 1
master.after(1000, onClick)
list_of_str = ['first','second','third','fourth','fifth']
w = Label(master, text="Hello, world!")
b = Button(master,text='Click me',command = onClick)
w.pack()
b.pack()
mainloop()
time.sleep is a no-no in tkinter. I advise you make your gui in a class and it wil be easier.
example with class:
import tkinter as tk
from tkinter import *
class GUI:
def __init__(self, master):
self.list_of_str = ['first','second','third','fourth','fifth']
self.count = 0
self.master = master
self.w = Label(master, text="Hello, world!")
self.w.pack()
self.b = Button(master,text='Click me',command = self.onClick)
self.b.pack()
def onClick(self, event=None):
if self.count == len(self.list_of_str):
pass
else:
self.w.configure(text=self.list_of_str[self.count])
self.count += 1
self.master.after(1000, self.onClick)
def main():
root = tk.Tk()
app = GUI(root)
root.mainloop()
if __name__ == '__main__':
main()
I am trying to have a ttk progressbar call a function that adds to a value that is displayed in the Tkinter window. I currently have a function that is called at the same time the progressbar begins:
def bar1_addVal():
global userMoney
userMoney += progBar1_values.value
moneyLabel["text"] = ('$' + str(userMoney))
canvas1.after((progBar1_values.duration*100), bar1_addVal)
return
but I cannot seem to get the exact amount of time it takes for the progressbar to finish each iteration. Is there a way to have the progressbar call a function every time it completes?
You can use threading to check for the variable in a loop. Then you wont interrupt the main loop.
I made a little example of this:
import threading, time
from ttk import Progressbar, Frame
from Tkinter import IntVar, Tk
root = Tk()
class Progress:
val = IntVar()
ft = Frame()
ft.pack(expand=True)
kill_threads = False # variable to see if threads should be killed
def __init__(self):
self.pb = Progressbar(self.ft, orient="horizontal", mode="determinate", variable=self.val)
self.pb.pack(expand=True)
self.pb.start(50)
threading.Thread(target=self.check_progress).start()
def check_progress(self):
while True:
if self.kill_threads: # if window is closed
return # return out of thread
val = self.val.get()
print(val)
if val > 97:
self.finish()
return
time.sleep(0.1)
def finish(self):
self.ft.pack_forget()
print("Finish!")
progressbar = Progress()
def on_closing(): # function run when closing
progressbar.kill_threads = True # set the kill_thread attribute to tru
time.sleep(0.1) # wait to make sure that the loop reached the if statement
root.destroy() # then destroy the window
root.protocol("WM_DELETE_WINDOW", on_closing) # bind a function to close button
root.mainloop()
Edit: Updated the answer, to end the thread before closing window.
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)
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()