¿Cómo ejecuta su propio código junto con el bucle de eventos de Tkinter?
Mi hermano pequeño recién se está iniciando en la programación y, para su proyecto de feria de ciencias, está haciendo una simulación de una bandada de pájaros en el cielo. Ha escrito la mayor parte de su código y funciona muy bien, pero los pájaros necesitan moverse en todo momento .
Tkinter, sin embargo, acapara el tiempo de su propio bucle de eventos, por lo que su código no se ejecuta. Se root.mainloop()
ejecuta, se ejecuta y sigue ejecutándose, y lo único que ejecuta son los controladores de eventos.
¿Hay alguna manera de ejecutar su código junto con el bucle principal (sin subprocesos múltiples, es confuso y debe mantenerse simple) y, de ser así, cuál es?
En este momento, se le ocurrió un truco feo, vinculando su move()
función a <b1-motion>
, de modo que mientras mantenga presionado el botón y mueva el mouse, funcione. Pero tiene que haber una manera mejor.
Utilice el after
método en el Tk
objeto:
from tkinter import *
root = Tk()
def task():
print("hello")
root.after(2000, task) # reschedule event in 2 seconds
root.after(2000, task)
root.mainloop()
Aquí está la declaración y la documentación del after
método:
def after(self, ms, func=None, *args):
"""Call function once after given time.
MS specifies the time in milliseconds. FUNC gives the
function which shall be called. Additional parameters
are given as parameters to the function call. Return
identifier to cancel scheduling with after_cancel."""
La solución publicada por Bjorn genera un mensaje "RuntimeError: llamando a Tcl desde otro apartamento" en mi computadora (RedHat Enterprise 5, Python 2.6.1). Es posible que Bjorn no haya recibido este mensaje ya que, según un lugar que revisé , el mal manejo de los subprocesos con Tkinter es impredecible y depende de la plataforma.
El problema parece ser que app.start()
cuenta como una referencia a Tk, ya que la aplicación contiene elementos Tk. Solucioné esto reemplazándolo app.start()
con un self.start()
interior __init__
. También lo hice para que todas las referencias de Tk estén dentro de la función que llamamainloop()
o dentro de funciones que son llamadas por la función que llama mainloop()
(esto aparentemente es crítico para evitar el error de "apartamento diferente").
Finalmente, agregué un controlador de protocolo con una devolución de llamada, ya que sin esto el programa sale con un error cuando el usuario cierra la ventana Tk.
El código revisado es el siguiente:
# Run tkinter code in another thread
import tkinter as tk
import threading
class App(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def callback(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.callback)
label = tk.Label(self.root, text="Hello World")
label.pack()
self.root.mainloop()
app = App()
print('Now we can continue running code while mainloop runs!')
for i in range(100000):
print(i)