2009-01-19 49 views
83

Mi hermano pequeño acaba de ingresar a la programación, y para su proyecto de la Feria de Ciencias, está haciendo una simulación de una bandada de pájaros en el cielo. Obtuvo la mayor parte de su código escrito, y funciona bien, pero las aves deben moverse en cada momento.¿Cómo se ejecuta su propio código junto con el bucle de eventos de Tkinter?

Sin embargo, Tkinter ahorra tiempo para su propio bucle de eventos, por lo que su código no se ejecutará. Haciendo root.mainloop() se ejecuta, se ejecuta y sigue ejecutándose, y lo único que ejecuta son los controladores de eventos.

¿Hay alguna manera de hacer que su código se ejecute junto con el mainloop (sin multihilo, es confuso y esto debería mantenerse simple), y si es así, ¿qué es?

En este momento, se le ocurrió un truco feo, que ata su función move()-<b1-motion>, de manera que todo el tiempo que mantiene presionado el botón del ratón y menea el, funciona. Pero tiene que haber una mejor manera.

Respuesta

105

utilizar el método de after en el objeto Tk:

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 para el método after:

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.""" 
+19

si especifica que el tiempo de espera sea 0, la tarea se volverá a colocar en el bucle de evento inmediatamente después de finalizar. esto no bloqueará otros eventos, mientras sigue ejecutando su código tan a menudo como sea posible. – Nathan

+0

Después de sacarme el pelo durante horas tratando de hacer que opencv y tkinter trabajen juntos y se cierren limpiamente cuando se hizo clic en el botón [X], esto junto con win32gui.FindWindow (Ninguno, 'título de ventana') hicieron el truco. Soy tan novato ;-) – JxAxMxIxN

2

Otra opción es dejar que tkinter ejecutar en un subproceso independiente. Una forma de hacerlo es la siguiente:

import Tkinter 
import threading 

class MyTkApp(threading.Thread): 
    def __init__(self): 
     self.root=Tkinter.Tk() 
     self.s = Tkinter.StringVar() 
     self.s.set('Foo') 
     l = Tkinter.Label(self.root,textvariable=self.s) 
     l.pack() 
     threading.Thread.__init__(self) 

    def run(self): 
     self.root.mainloop() 


app = MyTkApp() 
app.start() 

# Now the app should be running and the value shown on the label 
# can be changed by changing the member variable s. 
# Like this: 
# app.s.set('Bar') 

tener cuidado sin embargo, la programación multihilo es difícil y es muy fácil de disparar a su auto en el pie. Por ejemplo, debe tener cuidado cuando cambie las variables de miembro de la clase de ejemplo anterior para que no interrumpa con el bucle de evento de Tkinter.

+2

Solo usa una cola para comunicarte con el hilo. – jldupont

+2

No estoy seguro de que esto pueda funcionar. Acabo de probar algo similar y obtengo "RuntimeError: el hilo principal no está en el lazo principal". – jldupont

+4

jldupont: Obtuve "RuntimeError: Llamar a Tcl desde un apartamento diferente" (posiblemente el mismo error en una versión diferente). La solución era inicializar Tk en run(), no en __init __(). Esto significa que está inicializando Tk en el mismo hilo que llama mainloop() en. – mgiuca

35

La solución publicada por Bjorn da como resultado un mensaje "RuntimeError: Calling Tcl from different appartment" en mi computadora (RedHat Enterprise 5, python 2.6.1). Es posible que Bjorn no haya recibido este mensaje, ya que, de acuerdo con one place I checked, el manejo incorrecto de la conexión 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. Lo arreglé reemplazando app.start() con self.start() dentro de __init__. También lo hice de manera que todas las referencias a dichos conocimientos, ya sea dentro de la función que llama mainloop() o están en el interior funciones que son llamadas por la función que llama a mainloop() (esto es al parecer fundamental para evitar el error "diferente apartamento").

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) 
+0

¿Cómo pasaría argumentos al método 'run'? Parece que no puedo entender cómo ... – TheDoctor

+1

normalmente pasaría argumentos a '__init __ (..)', almacénelos en 'self' y úselos en' run (..) ' –

15

Al escribir su propio bucle, como en la simulación (supongo), es necesario llamar a la función update el que hace lo que el mainloop hace: actualiza la ventana con sus cambios, pero lo hace en su ciclo.

def task(): 
    # do something 
    root.update() 

while 1: 
    task() 
+8

Tiene que ser _muy_ cuidadoso con este tipo de programación. Si algún evento hace que se llame a 'task', terminarás con bucles de eventos anidados, y eso es malo. A menos que comprenda completamente cómo funcionan los bucles de eventos, debe evitar llamar 'update' a toda costa. –

+0

Utilicé esta técnica una vez: funciona bien, pero dependiendo de cómo lo haga, es posible que tenga algunos tambaleos en la interfaz de usuario. – jldupont

2

Esta es la primera versión funcional de lo que será un lector de GPS y un presentador de datos. tkinter es algo muy frágil con muy pocos mensajes de error. No pone cosas y no dice por qué gran parte del tiempo. Muy difícil viniendo de un buen desarrollador de formularios WYSIWYG. De todos modos, esto ejecuta una pequeña rutina 10 veces por segundo y presenta la información en un formulario. Tomó un tiempo para hacerlo realidad. Cuando probé un valor de temporizador de 0, nunca apareció el formulario. ¡Ahora me duele la cabeza! 10 o más veces por segundo es lo suficientemente bueno para mí. Espero que ayude a alguien más. Mike Morrow

import tkinter as tk 
import time 

def GetDateTime(): 
    # Get current date and time in ISO8601 
    # https://en.wikipedia.org/wiki/ISO_8601 
    # https://xkcd.com/1179/ 
    return (time.strftime("%Y%m%d", time.gmtime()), 
      time.strftime("%H%M%S", time.gmtime()), 
      time.strftime("%Y%m%d", time.localtime()), 
      time.strftime("%H%M%S", time.localtime())) 

class Application(tk.Frame): 

    def __init__(self, master): 

    fontsize = 12 
    textwidth = 9 

    tk.Frame.__init__(self, master) 
    self.pack() 

    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth, 
      text='Local Time').grid(row=0, column=0) 
    self.LocalDate = tk.StringVar() 
    self.LocalDate.set('waiting...') 
    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth, 
      textvariable=self.LocalDate).grid(row=0, column=1) 

    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth, 
      text='Local Date').grid(row=1, column=0) 
    self.LocalTime = tk.StringVar() 
    self.LocalTime.set('waiting...') 
    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth, 
      textvariable=self.LocalTime).grid(row=1, column=1) 

    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth, 
      text='GMT Time').grid(row=2, column=0) 
    self.nowGdate = tk.StringVar() 
    self.nowGdate.set('waiting...') 
    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth, 
      textvariable=self.nowGdate).grid(row=2, column=1) 

    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth, 
      text='GMT Date').grid(row=3, column=0) 
    self.nowGtime = tk.StringVar() 
    self.nowGtime.set('waiting...') 
    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth, 
      textvariable=self.nowGtime).grid(row=3, column=1) 

    tk.Button(self, text='Exit', width = 10, bg = '#FF8080', command=root.destroy).grid(row=4, columnspan=2) 

    self.gettime() 
    pass 

    def gettime(self): 
    gdt, gtm, ldt, ltm = GetDateTime() 
    gdt = gdt[0:4] + '/' + gdt[4:6] + '/' + gdt[6:8] 
    gtm = gtm[0:2] + ':' + gtm[2:4] + ':' + gtm[4:6] + ' Z' 
    ldt = ldt[0:4] + '/' + ldt[4:6] + '/' + ldt[6:8] 
    ltm = ltm[0:2] + ':' + ltm[2:4] + ':' + ltm[4:6] 
    self.nowGtime.set(gdt) 
    self.nowGdate.set(gtm) 
    self.LocalTime.set(ldt) 
    self.LocalDate.set(ltm) 

    self.after(100, self.gettime) 
    #print (ltm) # Prove it is running this and the external code, too. 
    pass 

root = tk.Tk() 
root.wm_title('Temp Converter') 
app = Application(master=root) 

w = 200 # width for the Tk root 
h = 125 # height for the Tk root 

# get display screen width and height 
ws = root.winfo_screenwidth() # width of the screen 
hs = root.winfo_screenheight() # height of the screen 

# calculate x and y coordinates for positioning the Tk root window 

#centered 
#x = (ws/2) - (w/2) 
#y = (hs/2) - (h/2) 

#right bottom corner (misfires in Win10 putting it too low. OK in Ubuntu) 
x = ws - w 
y = hs - h - 35 # -35 fixes it, more or less, for Win10 

#set the dimensions of the screen and where it is placed 
root.geometry('%dx%d+%d+%d' % (w, h, x, y)) 

root.mainloop() 
Cuestiones relacionadas