Tkinter - Executing Functions Over Time

Tkinter — executing functions over time

Event-driven programming requires a different mindset from procedural code. Your application is running in an infinite loop, pulling events off of a queue and processing them. To do animation, all you need to do is place items on that queue at an appropriate time.

Tkinter widgets have a method named after which lets you schedule functions to run after a certain period of time. The first step is to write a function that does one "frame" of your animation. In your case, you're defining animation as switching between two colors. A function that checks the current color, then switches to the other color is all you need:

def blink(rect, canvas):
current_color = canvas.itemcget(rect, "fill")
new_color = "red" if current_color == "white" else "white"
canvas.itemconfigure(rect, fill=new_color)

Now, we just need to have that function run three times at one second intervals:

root.after(1000, blink, rect, canv)
root.after(2000, blink, rect, canv)
root.after(3000, blink, rect, canv)

When you start your main loop, after one second the color will change, after another second it will change again, and after a third second it will change again.

That works for your very specific need, but that's not a very good general solution. A more general solution is to call blink once, and then have blink call itself again after some time period. blink then must be responsible to know when to stop blinking. You can set a flag or counter of some sort to keep track of how many times you've blinked. For example:

def blink(rect, canvas):
...
# call this function again in a second to
# blink forever. If you don't want to blink
# forever, use some sort of flag or computation
# to decide whether to call blink again
canvas.after(1000, blink, rect, canvas)

As a final bit of advice, I recommend that you define your program as a class, then create an instance of that class. This makes it so that you don't need global functions, and you don't need to pass around so many arguments. It doesn't really matter for a 20 line program, but it starts to matter when you want to write something substantial.

For example:

from tkinter import *

class MyApp(Tk):
def __init__(self):
Tk.__init__(self)
fr = Frame(self)
fr.pack()
self.canvas = Canvas(fr, height = 100, width = 100)
self.canvas.pack()
self.rect = self.canvas.create_rectangle(25, 25, 75, 75, fill = "white")
self.do_blink = False
start_button = Button(self, text="start blinking",
command=self.start_blinking)
stop_button = Button(self, text="stop blinking",
command=self.stop_blinking)
start_button.pack()
stop_button.pack()

def start_blinking(self):
self.do_blink = True
self.blink()

def stop_blinking(self):
self.do_blink = False

def blink(self):
if self.do_blink:
current_color = self.canvas.itemcget(self.rect, "fill")
new_color = "red" if current_color == "white" else "white"
self.canvas.itemconfigure(self.rect, fill=new_color)
self.after(1000, self.blink)


if __name__ == "__main__":
root = MyApp()
root.mainloop()

Running a function multiple time and displaying output on tkinter

When GUIs (tkitner, PyQt, other GUIs, other languages) run your function then they does't update widgets at once but they wait for end of your code and they update all widgets only once. This way they have less work and they redraw window only once so it doesn't blink.


You can use after(milliseconds, function_name, [arg1, ...]) to execute function periodically without blocking mainloop

Something like this

def runGrid(i=10):
updateGrid()
if i > 0:
root.after(4000, runGrid, i-1) # 4000ms = 4s

Eventually you can use root.update() to force mainloop to redraw window

def runGrid():
for i in range(0, 10):
updateGrid()
root.update()
time.sleep(4)

tkinter executes one more time the function after using after_cancel()

So, I've found out a sort of workaround killing the serial once the release button callback is called and opening again with a try except once the function button_hold_callback is called for the last time

def button_stop_callback(self, event):
self.ser.reset_output_buffer()
self.ser.write(b'\x02\x56\xff\xff\xff\xff\xff\xff\x32\x35\x04')
logging.debug("Written stop on the serial")
self.ser.close()
self.after_cancel(repeat)

# ---- CALLBACK MANTIENE L'ESECUZIONE DELLA FUNZIONE CON TASTO PREMUTO
def button_hold_callback(self, *args):
global repeat

try:
repeat = self.after(250, self.button_hold_callback, args[0], args[1])
except IndexError:
pass

try:
self.ser.reset_input_buffer()
self.ser.reset_output_buffer()

self.ser.write(self.message_to_send(args[0]))
logging.info('Sent message ' + str(self.message_to_send(args[0])))

# logging.info("write on serial")
self.ser.reset_input_buffer()
self.ser.reset_output_buffer()
# ---- SE FALLISCE PORTA SERIALE CHIUSA
except:
self.ser.open()

I am trying to create a new window and execute a function after that new window creation in tkinter

Tkinter has two universal widget methods that will help do what you want. One is called wait_visibility() which will pause the program until the new Toplevel is visible. The second is called after() which allows the scheduling of call to a function after a delay specified in milliseconds. If the called function does the same thing, effectively it creates a loop.

Here's how to use them:

import tkinter as tk
import time

def loop(limit, i):
if i < limit:
print(i)
window.after(1000, loop, limit, i+1)

def new():
new = tk.Toplevel(window)
new.geometry("450x250")
new.title('new window')
tk.Label(new, text="new one").place(x=150, y=40)
window.wait_visibility(new)
loop(5, 0)

window = tk.Tk()
window.geometry("450x250")
window.title('main window')

button = tk.Button(window, text='button', width=20, command=new)
button.place(x=180,y=200)
window.mainloop()

Functions all running when class is called (tkinter)

When you need to provide arguments to a Button command, you must use lambda or partial to prevent it from executing immediately.

    self.Submit_Button = Button(window, text="SUBMIT", width=8, command=lambda: self.change_home(window))

Although in this case you don't need to provide arguments at all, since the 'window' argument is the same as 'self.master'.



Related Topics



Leave a reply



Submit