Tkinter: ejecución de funciones a lo largo del tiempo

4 minutos de lectura

Tkinter ejecucion de funciones a lo largo del tiempo
kwadr4tic

Estoy tratando de averiguar 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 cambiar 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()

1646966946 599 Tkinter ejecucion de funciones a lo largo del tiempo
Bryan Oakley

La programación basada en eventos requiere una mentalidad diferente del código de procedimiento. Su aplicación se ejecuta en un bucle infinito, sacando eventos de una cola y procesándolos. Para hacer una animación, todo lo que necesita hacer es colocar elementos en esa cola en el momento apropiado.

Los widgets de Tkinter tienen un método llamado después que 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 haga un “cuadro” de su animación. En su caso, está definiendo la animación como cambiar entre dos colores. Todo lo que necesita es una función que verifique el color actual y luego cambie 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, solo necesitamos que esa función se ejecute 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 solución general muy buena. Una solución más general es llamar blink una vez, y luego tener blink llamarse a sí mismo de nuevo después de un período de tiempo. blink entonces debe ser responsable de saber cuándo dejar de parpadear. Puede configurar una bandera o un contador de algún tipo para realizar un seguimiento de cuántas veces ha 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 consejo final, 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 comienza 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()

  • ¡Gracias! ¡Eso es aclarar mucho!

    – kwadr4tic

    18 de febrero de 2012 a las 18:08

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:

root.after( 1000, blink )

Si desea que sea una llamada repetitiva, 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.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad