Tkinter: ejecutar funciones a lo largo del tiempo
Estoy intentando descubrir cómo funciona el flujo de control de tkinter.
Quiero mostrar un rectángulo y hacerlo parpadear tres veces. Escribí este código, pero no funciona. Supongo que es porque blink
se ejecuta antes mainloop
y en realidad no dibuja nada. Si es así, ¿cómo puedo intercambiar el flujo de control entre blink
y mainloop
para que funcione?
Mi código:
from tkinter import *
from time import *
def blink(rectangle, canvas):
for i in range(3):
canvas.itemconfigure(rectangle, fill = "red")
sleep(1)
canvas.itemconfigure(rectangle, fill = "white")
sleep(1)
root = Tk()
fr = Frame(root)
fr.pack()
canv = Canvas(fr, height = 100, width = 100)
canv.pack()
rect = canv.create_rectangle(25, 25, 75, 75, fill = "white")
blink(rect, canv)
root.mainloop()
La programación basada en eventos requiere una mentalidad diferente a la del código procesal. Su aplicación se ejecuta en un bucle infinito, sacando eventos de una cola y procesándolos. Para realizar una animación, todo lo que necesita hacer es colocar elementos en esa cola en el momento adecuado.
Los widgets de Tkinter tienen un método cuyo nombre le permite programar funciones para que se ejecuten después de un cierto período de tiempo. El primer paso es escribir una función que realice un "cuadro" de tu animación. En su caso, está definiendo la animación como el cambio entre dos colores. Todo lo que necesita es una función que verifica el color actual y luego cambia al otro color:
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)
Ahora, sólo necesitamos ejecutar esa función tres veces en intervalos de un segundo:
root.after(1000, blink, rect, canv)
root.after(2000, blink, rect, canv)
root.after(3000, blink, rect, canv)
Cuando inicie su bucle principal, después de un segundo el color cambiará, después de otro segundo cambiará nuevamente y después de un tercer segundo cambiará nuevamente.
Eso funciona para su necesidad muy específica, pero no es una muy buena solución general. Una solución más general es llamar blink
una vez y luego blink
volver a llamarse después de un período de tiempo. blink
Entonces hay que ser responsable de saber cuándo dejar de parpadear. Puedes configurar una bandera o contador de algún tipo para realizar un seguimiento de cuántas veces has parpadeado. Por ejemplo:
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)
Como último consejo, le recomiendo que defina su programa como una clase y luego cree una instancia de esa clase. Esto hace que no necesite funciones globales y no necesite pasar tantos argumentos. Realmente no importa para un programa de 20 líneas, pero empieza a importar cuando quieres escribir algo sustancial.
Por ejemplo:
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()
Cada widget tiene una función 'después', es decir, puede llamar a otra función después de un período de tiempo específico. Entonces, lo que querrías hacer es llamar a:
root.after( 1000, blink )
Si desea que sea una llamada repetida, simplemente llame 'después' nuevamente dentro de su función de parpadeo. El único problema que tendrá es pasar argumentos para parpadear; tal vez considere usar lamda dentro de 'después' para eso.