Tkinter entendiendo mainloop

Hasta ahora, solía finalizar mis progtwigs de Tkiter con: tk.mainloop() , ¡o no aparecería nada! Ver ejemplo:

 from Tkinter import * import random import time tk = Tk() tk.title = "Game" tk.resizable(0,0) tk.wm_attributes("-topmost", 1) canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0) canvas.pack() class Ball: def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_oval(10, 10, 25, 25, fill=color) self.canvas.move(self.id, 245, 100) def draw(self): pass ball = Ball(canvas, "red") tk.mainloop() 

Sin embargo, cuando probé el siguiente paso en este progtwig (haciendo que la bola se mueva por tiempo), el libro del que estoy leyendo dice que haga lo siguiente. Cambia la función de dibujo a:

 def draw(self): self.canvas.move(self.id, 0, -1) 

y añadir el siguiente código a mi progtwig:

 while 1: ball.draw() tk.update_idletasks() tk.update() time.sleep(0.01) 

¡Pero me di cuenta de que al agregar este bloque de código, el uso de tk.mainloop() inútil, ya que todo se mostraría incluso sin él!

En este momento, debo mencionar que mi libro nunca habla sobre tk.mainloop() (tal vez porque usa Python 3), ¡pero me enteré de que estaba buscando en la web ya que mis progtwigs no funcionaban al copiar el código del libro!

Así que traté de hacer lo siguiente que no funcionaría!

 while 1: ball.draw() tk.mainloop() time.sleep(0.01) 

¿Que esta pasando? ¿Qué hace tk.mainloop() ? ¿Qué hace tk.update_idletasks() y tk.update() y en qué se diferencia de tk.mainloop() ? ¿Debo usar el bucle de arriba? tk.mainloop() ? o ambos en mis progtwigs?

tk.mainloop() bloquea . Lo que eso significa es que la ejecución de tu progtwig python se detiene allí. Puedes ver eso escribiendo:

 while 1: ball.draw() tk.mainloop() print "hello" #NEW CODE time.sleep(0.01) 

Nunca verá la salida de la statement de impresión. Porque no hay bucle, la bola no se mueve.

Por otro lado, los métodos update_idletasks() y update() aquí:

 while True: ball.draw() tk.update_idletasks() tk.update() 

…no bloquees; la ejecución continúa después de que esos métodos terminan, por lo que el bucle while se ejecuta una y otra vez, lo que hace que la bola se mueva.

Un bucle infinito que contiene las llamadas al método update_idletasks() y update() puede actuar como un sustituto para llamar a tk.mainloop() . Tenga en cuenta que se puede decir que el bucle while completo se bloquea como tk.mainloop() porque no se ejecutará nada después del bucle while.

Sin embargo, tk.mainloop() no es un sustituto de solo las líneas:

 tk.update_idletasks() tk.update() 

Más bien, tk.mainloop() es un sustituto para el ciclo while entero:

 while True: tk.update_idletasks() tk.update() 

Respuesta al comentario:

Esto es lo que dicen los documentos tcl :

actualizar idletasks

Este subcomando de actualización elimina todos los eventos inactivos progtwigdos actualmente de la cola de eventos de Tcl. Los eventos de inactividad se utilizan para posponer el procesamiento hasta que “no haya nada más que hacer”, y el caso de uso típico para ellos es el rediseño de Tk y los nuevos cálculos de geometría. Al posponer esto hasta que Tk esté inactivo, las operaciones de redibujo costosas no se realizan hasta que todo desde un conjunto de eventos (por ejemplo, liberación de botón, cambio de ventana actual, etc.) se procesa a nivel de script. Esto hace que Tk parezca mucho más rápido, pero si está realizando un proceso de ejecución prolongada, también puede significar que no se procesen eventos inactivos durante mucho tiempo. Al llamar las idletasks de actualización, los redibujes debidos a cambios internos de estado se procesan de inmediato. (Los redibujes debidos a eventos del sistema, por ejemplo, al ser desiconificados por el usuario, necesitan una actualización completa para ser procesados).

APN Como se describe en la Actualización considerada dañina, el uso de la actualización para manejar los redibujos no manejados por las idletasks de actualización tiene muchos problemas. Joe English en una publicación comp.lang.tcl describe una alternativa:

Por update_idletasks() tanto, update_idletasks() hace que se procesen algunos subconjuntos de eventos, y que update() hace que se procesen.

De los documentos de actualización :

actualizar? idletasks?

El comando de actualización se usa para actualizar la aplicación ingresando el bucle de eventos Tcl repetidamente hasta que se hayan procesado todos los eventos pendientes (incluidas las devoluciones de llamadas inactivas).

Si la palabra clave idletasks se especifica como un argumento para el comando, entonces no se procesan nuevos eventos o errores; solo se invocan devoluciones de llamada inactivas. Esto hace que las operaciones que normalmente se aplazan, como actualizaciones de pantalla y cálculos de diseño de ventanas, se realicen inmediatamente.

KBK (12 de febrero de 2000) – Mi opinión personal es que el comando [actualizar] no es una de las mejores prácticas, y se recomienda a los progtwigdores que lo eviten. Raras veces, si alguna vez, he visto un uso de [actualización] que no podría ser progtwigdo de manera más efectiva por otro medio, generalmente el uso apropiado de devoluciones de llamada de eventos. Por cierto, esta precaución se aplica a todos los comandos Tcl (vwait y tkwait son los otros culpables comunes) que ingresan al bucle de eventos de forma recursiva, con la excepción de usar un solo [vwait] a nivel global para iniciar el bucle de eventos dentro de un shell Eso no lo lanza automáticamente.

Los propósitos más comunes para los que he visto [actualización] recomendados son: 1) Mantener viva la GUI mientras se está ejecutando un cálculo de larga ejecución. Ver progtwig de cuenta regresiva para una alternativa. 2) Esperar a que se configure una ventana antes de hacer cosas como la administración de geometría en ella. La alternativa es vincular eventos como los que notifican el proceso de la geometría de una ventana. Consulte Centrar una ventana para una alternativa.

¿Qué pasa con la actualización? Hay varias respuestas. Primero, tiende a complicar el código de la GUI circundante. Si realiza los ejercicios en el progtwig Cuenta atrás, obtendrá una idea de lo fácil que puede ser cuando cada evento se procesa en su propia callback. En segundo lugar, es una fuente de insectos insidiosos. El problema general es que la ejecución de [actualización] tiene efectos secundarios casi sin restricciones; a la vuelta de [actualización], un script puede descubrir fácilmente que la alfombra se ha retirado de debajo de ella. Hay una discusión más a fondo de este fenómeno en Update que se considera dañino.

…..

¿Hay alguna posibilidad de que mi progtwig funcione sin el ciclo while?

Sí, pero las cosas se ponen un poco complicadas. Podría pensar que algo como lo siguiente funcionaría:

 class Ball: def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_oval(10, 10, 25, 25, fill=color) self.canvas.move(self.id, 245, 100) def draw(self): while True: self.canvas.move(self.id, 0, -1) ball = Ball(canvas, "red") ball.draw() tk.mainloop() 

El problema es que ball.draw () hará que la ejecución entre en un bucle infinito en el método draw (), por lo que tk.mainloop () nunca se ejecutará, y tus widgets nunca se mostrarán. En la progtwigción de la interfaz gráfica de usuario, los bucles infinitos deben evitarse a toda costa para que los widgets respondan a la entrada del usuario, por ejemplo, los clics del mouse.

Entonces, la pregunta es: ¿cómo ejecutar algo una y otra vez sin crear realmente un bucle infinito? Tkinter tiene una respuesta para ese problema: el método after() un widget:

 from Tkinter import * import random import time tk = Tk() tk.title = "Game" tk.resizable(0,0) tk.wm_attributes("-topmost", 1) canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0) canvas.pack() class Ball: def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_oval(10, 10, 25, 25, fill=color) self.canvas.move(self.id, 245, 100) def draw(self): self.canvas.move(self.id, 0, -1) self.canvas.after(1, self.draw) #(time_delay, method_to_execute) ball = Ball(canvas, "red") ball.draw() #Changed per Bryan Oakley's comment tk.mainloop() 

El método after () no se bloquea (en realidad crea otro hilo de ejecución), por lo que la ejecución continúa en su progtwig python después de que se llama after (), lo que significa que tk.mainloop () se ejecuta a continuación, por lo que sus widgets se configuran y desplegado. El método after () también permite que sus widgets sigan respondiendo a las aportaciones de otros usuarios. Intente ejecutar el siguiente progtwig y luego haga clic con el mouse en diferentes lugares del canvas:

 from Tkinter import * import random import time root = Tk() root.title = "Game" root.resizable(0,0) root.wm_attributes("-topmost", 1) canvas = Canvas(root, width=500, height=400, bd=0, highlightthickness=0) canvas.pack() class Ball: def __init__(self, canvas, color): self.canvas = canvas self.id = canvas.create_oval(10, 10, 25, 25, fill=color) self.canvas.move(self.id, 245, 100) self.canvas.bind("", self.canvas_onclick) self.text_id = self.canvas.create_text(300, 200, anchor='se') self.canvas.itemconfig(self.text_id, text='hello') def canvas_onclick(self, event): self.canvas.itemconfig( self.text_id, text="You clicked at ({}, {})".format(event.x, event.y) ) def draw(self): self.canvas.move(self.id, 0, -1) self.canvas.after(50, self.draw) ball = Ball(canvas, "red") ball.draw() #Changed per Bryan Oakley's comment. root.mainloop() 
 while 1: root.update() 

… es (¡muy!) aproximadamente similar a:

 root.mainloop() 

La diferencia es que mainloop es la forma correcta de codificar y el bucle infinito es sutilmente incorrecto. Sin embargo, sospecho que la gran mayoría de las veces, cualquiera de los dos funcionará. Es solo que mainloop es una solución mucho más limpia. Después de todo, llamar a mainloop es esencialmente esto debajo de las portadas:

 while the_window_has_not_been_destroyed(): wait_until_the_event_queue_is_not_empty() event = event_queue.pop() event.handle() 

… lo cual, como puedes ver, no es muy diferente al tuyo loop. Entonces, ¿por qué crear su propio bucle infinito cuando tkinter ya tiene uno que puede usar?

mainloop los términos más simples posibles: siempre llame a mainloop como la última línea lógica de código en su progtwig . Así es como Tkinter fue diseñado para ser utilizado.

Estoy usando un patrón de diseño MVC / MVA, con múltiples tipos de “vistas”. Un tipo es un “GuiView”, que es una ventana Tk. Paso una referencia de vista a mi objeto de ventana que hace cosas como botones de enlace para ver funciones (a las que también llama la clase de controlador / controlador).

Para hacer eso, el constructor del objeto de vista debía completarse antes de crear el objeto de ventana. Después de crear y mostrar la ventana, quería hacer algunas tareas iniciales con la vista automáticamente. Al principio intenté hacerlos después de mainloop (), pero eso no funcionó porque mainloop () estaba bloqueado.

Como tal, creé el objeto de ventana y usé tk.update () para dibujarlo. Luego, comencé mis tareas iniciales y finalmente comencé el mainloop.

 import Tkinter as tk class Window(tk.Frame): def __init__(self, master=None, view=None ): tk.Frame.__init__( self, master ) self.view_ = view """ Setup window linking it to the view... """ class GuiView( MyViewSuperClass ): def open( self ): self.tkRoot_ = tk.Tk() self.window_ = Window( master=None, view=self ) self.window_.pack() self.refresh() self.onOpen() self.tkRoot_.mainloop() def onOpen( self ): """ Do some initial tasks... """ def refresh( self ): self.tkRoot_.update()