2

We have some code in an event callback that looks like:

...
self.position.side = -self.position.side
self.update_with_board() # displays self.position graphically
ai_move = self.brain.get_move(self.position)
...

update() is called immediately but does not affect the GUI until the ai_move line.

However, when I do:

...
self.position.side = -self.position.side
self.update_with_board() # displays self.position graphically
raw_input()
ai_move = self.brain.get_move(self.position)
...

it updates graphically immediately as it asks for the input. I don't know what to make of this: maybe funky lazy evaluation or a tkinter scheduling thing I don't know about? How do I get the GUI to update in the order specified instead of delayed?

EDIT: Sorry, I wasn't using the built-in update() method, but rather one I defined to draw. I renamed it update_with_board(), and see the same behavior.

def update_with_board(self):
    for i in range(8):
        for j in range(8):
            color = "gray" if (i+j) % 2 else "white"
            self.canvas.create_rectangle(self.square * i, self.square * j, self.square * (i+1), self.square * (j+1), fill=color)
            if self.position.board[8 * j + i] in self.ims.keys():
                self.canvas.create_image(self.square * i + self.square/2,
                self.square * j + self.square/2, image = self.ims[self.position.board[8 * j + i]])  
nbro
  • 15,395
  • 32
  • 113
  • 196
nair.ashvin
  • 791
  • 3
  • 11
  • Quoting the docs on `update`: "It should never be called from an event callback or a function that is called from an event callback." I believe TkInter is trying to protect you from infinite recursion here; otherwise, there's nothing to prevent `update` from re-entering the main event loop which renters your handler which… If you use `update_idletasks`, does that work? (It may not be what you _want_ here, but it's a useful thing to check for debugging.) – abarnert Dec 10 '12 at 05:29
  • 2
    It looks like every time you call `update_with_board` you are creating 64 new images and 64 new rectangles, and not deleting any old ones. Am I reading that right? This is what is called a memory leak. If you're creating a board game of some sort, there's really no reason to create new objects every time you want to update the board -- you can just change the attributes of the existing items. – Bryan Oakley Dec 10 '12 at 12:03

1 Answers1

3

Doing a bit more research, I'm pretty sure my comment is correct.

The UI updates all widgets as needed every time through the event loop. Calling update basically just forces it to run the event loop now. So, if you're in the middle of an event callback, you're recursively entering the event loop, which is a very bad thing, and can lead to infinite recursion.

As The TkInter Book says, update:

Processes all pending events, calls event callbacks, completes any pending geometry management, redraws widgets as necessary, and calls all pending idle tasks. This method should be used with care, since it may lead to really nasty race conditions if called from the wrong place (from within an event callback, for example, or from a function that can in any way be called from an event callback, etc.). When in doubt, use update_idletasks instead.

Similarly, the TkInter reference says:

This method forces the updating of the display. It should be used only if you know what you're doing, since it can lead to unpredictable behavior or looping. It should never be called from an event callback or a function that is called from an event callback.

Testing things out, it seems like at least in some cases, TkInter just ignores you when you call update from inside the event loop. Presumably to protect you from infinite recursion, but I haven't looked at the code to verify that.

At any rate, this isn't the function you want, so it doesn't really matter why exactly it isn't doing what it wasn't documented to do.

If you need to trigger an update from within an event callback, call update_idletasks. This actually calls all pending "idle" tasks, including redraws, without calling any event callbacks. (Not "update next time we're in the event loop and have nothing else to do".)

Meanwhile, calling raw_input inside an event callback is even worse. You're blocking the main event loop on terminal input. Unless this was just something you did for debugging purposes, it's a very bad idea. And even for debugging purposes, it's a very odd thing to test, and it's entirely plausible that whatever happens will have no relevance to normal behavior.

For more background, see the question TkInter: How do widgets update, or search update_idletasks on this site, or look at some of the Related links on the right side (at least two of which are relevant).

Based on your edit, as it turns out, it sounds like you had kind of the opposite problem—you were just changing things without telling TkInter to do anything about it, so the changes wouldn't show up until the next time through the event loop (which means not until after you return from this function). But the answer is basically the same—one way it's "replace your call to update with update_idletasks", the other way it's "add a call to update_idletasks".

Community
  • 1
  • 1
abarnert
  • 354,177
  • 51
  • 601
  • 671