If you trigger a callback that takes 1 minute to run, you're not returning to the main loop for 1 minute, so the GUI can't respond to anything.
There are two common solutions to this.
The first is to use a background thread:
def function1():
time.sleep(60)
print 'function1'
def function1_background():
t = threading.Thread(target=function1)
t.start()
button1 = Button(frame, text='function 1', fg='black', command=function1_background)
This is simple, but it only works when your code is purely doing background work, not touching any of the tkinter widgets.
The only problem here is that you'd have to def
20 extra functions. You don't want to repeat yourself that much—that's 80 lines of repetitive boilerplate code that gets in the way of seeing the code that matters, and 20 chances to make a stupid bug in copy-pasting that's a pain to track down, and 20 places you have to change if you later decide you want, say, processes instead of threads so the work can parallelize better, or a pool of 4 threads with the background tasks queued up.
You can solve that in a few different ways. See this question for more in-depth explanation, but in short, you get Python to do some of the repetitive work for you.
You can def
a single helper function:
def background(func):
t = threading.Thread(target=func)
t.start()
… and then lambda
20 separate function:
button1 = Button(frame, text='function 1', fg='black', command=lambda: background(function1))
Alternatively, you can partially apply the function using partial
:
button1 = Button(frame, text='function 1', fg='black', command=functools.partial(background, function1))
Or, if you never want to call the functions except in the background, you can write a decorator and apply it to each function at def
time:
def background(func):
@functools.wraps(func)
def wrapper():
t = threading.Thread(target=func)
t.start()
return wrapper
@background
def function1():
time.sleep(60)
print 'function1'
If you can't use threads (e.g., because the background work involves fiddling with your tkinter widgets), the alternative is to restructure your code so that, instead of being one monolithic task that takes 1 minute, it's a bunch of separate tasks that each takes a fraction of a second and schedules the next part:
def function1(count=60):
if count > 0:
time.sleep(0.1)
frame.after(0, function1, count-0.1)
else:
print 'function1'
button1 = Button(frame, text='function 1', fg='black', command=function1)
This always works, if you can find a way to do it. Your real work may not be as easy to divide into 0.1-second chunks as a sleep(60)
is.