The answer is, as you have been told, using threads using threading . What happens is that it is not so simple, in principle Tkinter
is not thread- safe which forces us to run all the code of our graphical interface from the main thread.
In your example we might think that we simply execute the callback()
function in a secondary thread and solved problem. No, that would be if you do not need to use data created in the function in your GUI or if you do not need to update or create widgets (as you do with tkMessageBox
) from this secondary thread.
The prefrontal is And now what do we do?
Well, the solution is to use queues ( Queue
) that are thread-safe and allow sharing information between threads. The idea is simple, we create a queue and the information that we have to pass to the main thread where we execute Tkinter
we put it in the queue. As is logical, we need a function in the main thread that periodically looks if there is something in the queue and if there is something it processes.
This function needs to be executed periodically, for this we could use the classic while True
accompanied by a waiting time between cycles to avoid pissing off the processor, for this we would use time.sleep()
. But there is a problem, with this we block the graphical interface and we are in the same problem as at the beginning, to solve it we simply throw away the after()
method from Tkinter.
Using your example would be something like this:
import datetime
import time
import threading
from Tkinter import *
import tkMessageBox
import Queue
cola = Queue.Queue()
def validateDate(a,b):
try:
datetime.datetime.strptime(a, '%H:%M')
datetime.datetime.strptime(b, '%H:%M')
return True
except ValueError:
return False
def callback():
def run():
var=OpenHour.get()
var1=CloseHour.get()
actual = 0
if not validateDate(var, var1):
cola.put((tkMessageBox.showinfo, ("Error", "Datos o formato incorrecto, deberia ser hh:mm"), {} ))
else:
while var != actual:
actual = datetime.datetime.now().time().strftime('%H:%M')
time.sleep(1)
cola.put((tkMessageBox.showinfo, ("Exito", "La persiana se ha abierto"), {}))
while var1 != actual:
actual = datetime.datetime.now().time().strftime('%H:%M')
time.sleep(1)
cola.put((tkMessageBox.showinfo, ("Exito", "La persiana se ha cerrado"), {}))
t=threading.Thread(target=run)
t.start()
def tkloop():
try:
while True:
funcion, args, kwargs = cola.get_nowait()
funcion(*args, **kwargs)
except:
pass
VentanaPersiana.after(100, tkloop)
VentanaPersiana = Tk()
l1=Label(VentanaPersiana, text='Hora de apertura:')
l1.pack()
OpenHour = Entry()
OpenHour.pack()
l2=Label(VentanaPersiana, text='Hora de cierre:')
l2.pack()
CloseHour = Entry()
CloseHour.pack()
b = Button(VentanaPersiana, text="Programar", width=10, command=callback)
b.pack()
tkloop()
VentanaPersiana.mainloop()
There really is another possibility that is to use Callbacks by using after()
or after_idle()
and without using threads, this has some problems and for my taste it becomes heavy in moderately complex codes, I leave you a simplified example ( your code would have to be modified enough to work in this way) of a timer in which a tkMessageBox
jumps when it is the time indicated in Entry
:
import datetime
from Tkinter import *
import tkMessageBox
def iniciar():
def callback():
actual = datetime.datetime.now().time()
var = hour.get()
var = datetime.datetime.strptime(var, '%H:%M')
if var.time() < actual:
tkMessageBox.showinfo("Aviso", "Hora de hacer algo...")
else:
root.after_idle(callback)
root.after_idle(callback)
root = Tk()
l=Label(root, text='Introducir hora de la forma HH:MM:')
l.pack()
hour = Entry()
hour.pack()
b = Button(root, text="Temporizador", width=10, command=iniciar)
b.pack()
root.mainloop()
Note : In the first example I added things that are not in your example to make a code that is minimally functional and executable for third parties. They work in Python 2.x
since your code is for this branch.
Greetings.