I have a simple tkinter callback that scrubs forward through a video when a key is pressed or held down.
root.bind('<Right>', callback_scrubFwd)
root.mainloop()
This plays back the video very nicely. How can I trigger this callback to be called continuously, which is what happens when the key is held down by the user, only automatically? I've tried ordinary while loops or nested/timed function calls but these lock up the interface.
If you want a function to run continuously, at the end of the function you can call after to put another invocation of the callback on the event queue:
def callback_scrubFwd():
<your code here>
root.after(1000, callback_scrubFwd)
If you want to be able to stop the auto-repeat you can add a flag that you check for each time it is called:
def callback_scrubFwd():
<your code here>
if do_autorepeat:
root.after(1000, callback_scrubFwd)
Related
I created an example code because my original is too big and has private information(My own) in it.
While running a program from a Tkinter GUI, it runs the program but makes the GUI unresponsive because of time.sleep() blocking the GUI from updating.
I am trying to avoid using timers because it fires a different function after a duration instead of simply pausing the function and then continuing the same function.
Is there an alternative that does not block the GUI but still adds a delay inside of the function?
Example Code:
from tkinter import *
import time
wn = Tk()
wn.geometry("400x300")
MyLabel = Label(wn, text="This is a Status Bar")
MyLabel.pack()
def MyFunction():
Value = 1
while Value < 10:
print("Do something")
time.sleep(1) **# - here blocks everything outside of the function**
MyLabel.config(text=Value)
# A lot more code is under here so I cannot use a timer that fires a new function
Value = 1
MyButton = Button(wn, text="Run Program", command=MyFunction)
MyButton.pack()
wn.mainloop()
Edit: Thanks so much, you're answers were fast and helpful, I changed the code and added "wn.mainloop()" after the delay and replaced "time.sleep(1)" with wn.after(100, wn.after(10, MyLabel.config(text=Value))
here is the final code:
from tkinter import *
import time
wn = Tk()
wn.geometry("400x300")
MyLabel = Label(wn, text="This is a Status Bar")
MyLabel.pack()
def MyFunction():
Value = 0
while Value < 10:
print("Do something")
wn.after(10, MyLabel.config(text=Value))
Value += 1
wn.mainloop()
MyButton = Button(wn, text="Run Program", command=MyFunction)
MyButton.pack()
wn.mainloop()
The short answer is that you can use wn.after() to request a callback after a certain amount of time. That's how you handle it. You get a timer tick at a one-per-second rate, and you have enough state information to let you proceed to the next state, then you go back to the main loop.
Put another way, timers are exactly how you have to solve this problem.
Fundamentally, any callback function in Tkinter runs in the main GUI thread, and so the GUI thread will block until the function exits. Thus you cannot add a delay inside the function without causing the GUI thread to be delayed.
There are two ways to solve this. One would be to refactor your function into multiple pieces so that it can schedule the remaining work (in a separate function) via .after. This has the advantage of ensuring that all of your functions are running in the main thread, so you can perform GUI operations directly.
The other way is to run your function in a separate thread that is kicked off whenever your main callback is executed. This lets you keep all the logic inside the one function, but it can no longer perform GUI operations directly - instead, any GUI operations would have to go through an event queue that you manage from the main thread.
You can combine after() and wait_variable() to simulate time.sleep() without blocking tkinter from handling pending events and updates:
def tk_sleep(delay):
v = wn.IntVar()
# update variable "delay" ms later
wn.after(delay, v.set, 0)
# wait for update of variable
wn.wait_variable(v)
Using tk_sleep() in your while loop:
def MyFunction():
Value = 1
while Value < 10:
print("Do something")
tk_sleep(1000) # waits for one second
MyLabel.config(text=Value)
# A lot more code is under here so I cannot use a timer that fires a new function
Value += 1
I've been learning python for a month now and run into my first brick wall. I have a large art viewer GUI program and at one point want to put an image on screen with a countdown counter-approx every 5 secs. I thought of a code such as the one below The problem is that this uses update and all my reading says that update is bad (starts a new event loop (?)) and that I should use update_idletasks. when I replace update with update_idletasks in the code below the countdown button is not visible until it reaches single figures, update superficially works fine. But also the q bound key calls the subroutine but has no effect
from tkinter import *
import sys
import time
root = Tk()
def q_key(event):
sys.exit()
frame=Frame(root, padx=100, pady=100, bd=10, relief=FLAT)
frame.pack()
button=Button(frame,relief="flat",bg="grey",fg="white",font="-size 18",text="60")
button.pack()
root.bind("q",q_key)
for x in range(30, -1, -5) :
button.configure(text=str(x))
button.update()
print(x)
button.after(5000)
root.mainloop()
In this case you don't need update nor update_idletasks. You also don't need the loop, because tkinter is already running in a loop: mainloop.
Instead, move the body of the loop to a function, and call the function via after. What happens is that you do whatever work you want to do, and then schedule your function to run again after a delay. Since your function exits, tkinter returns to the event loop and is able to process events as normal. When the delay is up, tkinter calls your function and the whole process starts over again.
It looks something like this:
def show(x):
button.configure(text=x)
if x > 0:
button.after(5000, show, x-5)
show(30)
I have a strange issue with TKinter after() method. I'm calling function func_a() (blocking call that takes some ms) in main thread and func_b() in after() to read a value at regular interval. It works like a charm, I can get some updated value during func_a() execution
I do not need any graphical interface, so I do not use anymore TKinter, now I'm calling func_a() in main thread. I create a separate thread to call func_b(). The issue is that the call to func_a() stops the execution of func_b() separate thread. I need to wait for func_a() returns to have some periodic call of func_b(). I do not have source of func_a() and func_b() (python C bindings). But maybe some thread locking mechanism prevents func_b() call when func_a() is called.
The question is, what is implemententation behind tkinter after? How can I achieve same behavior as Tkinter after(): be able to call func_b() when func_a()is called, without using TKinter?
Code looks like that :
pos_th= threading.Thread(target=read_pos, args=(0.1,))
pos_th.daemon = True
pos_th_stop = False
pos_th.start()
func_a()
def read_pos(period):
while not pos_th_stop :
func_b()
time.sleep(period)
The question is, what is implemententation behind tkinter after?
When it comes down to it, it's really quite simple. Tkinter's mainloop method is little more than an infinite loop that waits for items to appear on the event queue. When it finds an event, it pulls it off of the queue and calls the handlers for that event.
after simply puts an item on the event queue. During each iteration of mainloop tkinter will examine the timestamp on the function added by after, and if the given amount of time has elapses, the function is pulled off of the queue and run.
If you create a hotkey activated by '1' and a hotkey activated by '2' before hotkey 1's function is finished then the second hotkey won't call it's assigned function.
I can create hotkeys which call functions in python using the keyboard module. I have included an example in the following code which works for calling functions.
import keyboard
import time
def hk_print(word,word2):
time.sleep(5)
print(word,word)
keyboard.add_hotkey('1', hk_print, args= ('hotkey','test1'))
keyboard.add_hotkey('2', hk_print, args= ('hotkey','test2'))
keyboard.wait()
Is there anything I can add to this code to make the hotkey activated functions interrupt eachother? So If I press 1 and then 2 it will stop going through the first function and start doing the second one right away?
It's doable, but it'll take some effort. You're getting into async territory.
What I would suggest is using keyboard.record() and using threading.Timer() in lieu of time.sleep(). You would create a new callback which would start recording all the events as soon as the key is pressed and start a Timer() with some timeout, let's say 5 seconds.
After the time has elapsed, you would have to stop recording, then evaluate the events that were captured during the wait, drop all the 1s and 2s pressed save for the very last one, then call ANOTHER callback - hk_print with the right set of args, depending on what the final press was.
I am writing a program using tkinter, but I do not understand how it works. Normally, code is executed top-down, but with tkinter it obviously does not.
For example, I have bound a function to the left mouse button, and this function is executed every time I click the button. But how is the other code around that treated? My problem is that I in the start of my program initialize a variable that is used as an argument in the bound function, and then it is changed in the function and returned. But every time the function is called, the variable seems to be reset to its initial value.
Does anyone know why this is?
I have it written like this:
var = "black"
var = c.bind("<Button-1>", lambda event: func(event, arg=var))
The function "func" changes var and returns it, but the next time I press the button the variable is always "black".
Thanks in advance!
Tkinter does indeed run top down. What makes tkinter different is what happens when it gets to the bottom.
Typically, the last executable statement in a tkinter program is a call to the mainloop method of the root window. Roughtly speaking, tkinter programs look like this:
# top of the program logic
root = tkinter.Tk()
...
def some_function(): ...
...
some_widget.bind("<1>", some_function)
...
# bottom of the program logic
root.mainloop()
mainloop is just a relatively simple infinite loop. You can think of it as having the following structure:
while the_window_has_not_been_destroyed():
event = wait_for_next_event()
process_event(event)
The program is in a constant state of waiting. It waits for an event such as a button click or key click, and then processes that event. Conceptually, it processes the event by scanning a table to find if that event has been associated with the widget that caught the event. If it finds a match, it runs the command that is bound to that widget+event combination.
When you set up a binding or associate a command with a button, you are adding something to that table. You are telling tkinter "if event X happens on widget Y, run function Z".
You can't use a return result because it's not your code that is calling this function. The code that calls the function is mainloop, and it doesn't care what the function returns. Anything that gets returned is simply ignored.