Question

I am getting problems with Tkinter after() method. Actually, what I want to do is to change the background colour of some entry boxes as soon as times passes. Let's take this piece of code (which is different from the script I'm working on, but the situation described is the same):

import Tkinter as tk

root = tk.Tk()
root.option_add("*Entry.Font","Arial 32 bold")

emptyLabel=tk.Label()
emptyLabel.grid(row=4) #Empty label for geometry purpose
entryList=[]

for x in range(4):
    entryList.append([])
    for y in range(4):
        entryList[x].append('')
        entryList[x][y]=tk.Entry(root, bg="white",width=2,justify="center",
                                 takefocus=True,insertofftime=True)
        entryList[x][y].grid(row=x,column=y)

solvebt=tk.Button(root,text='Solve').grid(row=5,column=2)
newgamebt=tk.Button(root,text='New').grid(row=5,column=1)

#BROKEN PART STARTS HERE 

def changebg(x,y):
    entryList[x][y]['bg']='yellow'

for x in range(4):
    for y in range(4):
        entryList[x][y].after(300,changebg(x,y))
        #Same result with root.after(300,changebg(x,y))


root.mainloop()

The problem is that when I start the program, I would expect it to show me as it "paints", one at time, all of the entry boxes in yellow. What happens, instead, is that the program freezes for (300*16) milliseconds and then, all of a sudded, every entry boxes is yellow!

Was it helpful?

Solution

The problem is here:

def changebg(x,y):
    entryList[x][y]['bg']='yellow'

for x in range(4):
    for y in range(4):
        entryList[x][y].after(300,changebg(x,y))
        #Same result with root.after(300,changebg(x,y))

You're calling changebg to immediately in the double for loop -- You're then passing the return value (None) to root.after. This won't lead to the delay that you describe. Perhaps your actual code looks like:

for x in range(4):
    for y in range(4):
        entryList[x][y].after(300,lambda x=x,y=y : changebg(x,y))

That will lead to the behavior you actually describe. Ultimately, what you need is to flatten your list of widgets and then pass then one at a time -- registering the next one if it exists:

import itertools
all_entries = itertools.chain.from_iterable(entryList)
def changebg(ientries):
    ientries = iter(ientries) #allow passing a list in as well ...
    entry = next(ientries,None)
    if entry is not None:
        entry['bg'] = 'yellow' #change the color of this widget
        root.after(300,lambda : changebg(ientries)) #wait 300ms and change color of next one.

changebg(all_entries)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top