Odd function behaviour with Tkinter - python

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]])

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

Related

How to queue a task AFTER all widget resizing operations?

I want to query a widget's size after changing its contents. Here's a demonstration:
import tkinter as tk
win = tk.Tk()
label = tk.Label(win)
label.pack()
def callback1():
label['text'] = 'hello world'
win.after_idle(callback2)
def callback2():
print('label width:', label.winfo_width())
win.after(0, callback1)
win.mainloop()
According to the documentation, callbacks queued with after_idle should only be executed when there's nothing else to do:
Registers a callback that is called when the system is idle. The callback will be called there are no more events to process in the mainloop.
And yet, callback2 is clearly executed before the label is resized, because the output of the program is this:
label width: 1
Even adding a call to update_idletasks() doesn't change this output. Only if win.update_idletasks() is called in both callback1 and callback2, the correct size is printed. I really don't understand why it's necessary to call it twice.
Question
Why is callback2 being executed before the label is resized? How can I ensure that label.winfo_width() returns the correct size?
Limitations
The main goal of this question is to understand how/when tkinter executes (idle) tasks. I want to find out how to correctly queue tasks so that they're executed only after the GUI has updated itself. I'm not really interested in workarounds such as these:
I'd prefer to avoid using update() because I don't understand how it causes race conditions or when it's safe to use. (In my real code, all of this would be executed inside an event handler, which the documentation explicitly states should be avoided.)
I also want to avoid using the <Configure> event. This is because there might be an arbitrary number of widgets changing size, and I cannot reasonably be expected to bind event handlers to all of their <Configure> events. I really just need a way to execute a callback function after all the resizing has taken place.

How is tkinter code executed?

I am writing a program using tkinter, but I do not understand how it works. Normally, code is executed top-down, but with tkinter it obviously does not.
For example, I have bound a function to the left mouse button, and this function is executed every time I click the button. But how is the other code around that treated? My problem is that I in the start of my program initialize a variable that is used as an argument in the bound function, and then it is changed in the function and returned. But every time the function is called, the variable seems to be reset to its initial value.
Does anyone know why this is?
I have it written like this:
var = "black"
var = c.bind("<Button-1>", lambda event: func(event, arg=var))
The function "func" changes var and returns it, but the next time I press the button the variable is always "black".
Thanks in advance!
Tkinter does indeed run top down. What makes tkinter different is what happens when it gets to the bottom.
Typically, the last executable statement in a tkinter program is a call to the mainloop method of the root window. Roughtly speaking, tkinter programs look like this:
# top of the program logic
root = tkinter.Tk()
...
def some_function(): ...
...
some_widget.bind("<1>", some_function)
...
# bottom of the program logic
root.mainloop()
mainloop is just a relatively simple infinite loop. You can think of it as having the following structure:
while the_window_has_not_been_destroyed():
event = wait_for_next_event()
process_event(event)
The program is in a constant state of waiting. It waits for an event such as a button click or key click, and then processes that event. Conceptually, it processes the event by scanning a table to find if that event has been associated with the widget that caught the event. If it finds a match, it runs the command that is bound to that widget+event combination.
When you set up a binding or associate a command with a button, you are adding something to that table. You are telling tkinter "if event X happens on widget Y, run function Z".
You can't use a return result because it's not your code that is calling this function. The code that calls the function is mainloop, and it doesn't care what the function returns. Anything that gets returned is simply ignored.

GUI wait until BooleanVar() changes

how can I make my Tkinter GUI wait for a change of a BooleanVar()? The BooleanVar is controlled by a distance sensor. The GUI should wait until the variable changes to False and the move on.
I tried to use a while True - loop, but as expected it disturbed the mainloop and the programm crashed.
I've also considered to use one if the methods to wait for user-input, but I can't figure out how.
Is there any way to solve this?
Thanks!
I don't understand what you mean by "wait" here, because a GUI is always in a constant state of "wait". It waits for events, and then it acts on events.
If you have a BooleanVar that is set somehow, you can set a trace on that variable. When the value changes, the trace will call a callback of your choice. In that callback your code can do whatever you want.
self.sensor = tk.BooleanVar()
self.sensor.trace("w", self.on_sensor_change)
...
def on_sensor_change(self, *args):
print "the sensor changed:", self.sensor.get()

Python 3.3 tkinter's Entry widget update on key

I am using tkinter for a GUI. I bound an event to an entry like so:
EntryFilePath.bind("<Key>", updateAmountOfPeople)
It works, but the problem is that it only updates when a key other than typing input is being pressed. Backspace triggers it, arrows trigger it, just not letters or numbers. I am looking for this functionality.
Other info that might be important:
PathFileName = StringVar()
EntryFilePath = Entry(topLeftMidFrame, textvariable = PathFileName, width=45)
EntryFilePath.pack(side=TOP, pady=32, padx=10)
How to make it trigger on any key?
EDIT: I found out that this only happens when it just got selected. It needs one of the "other" non [a-Z0-9] keys once, after that it is good to go. This is problematic though, in case people start immediately writing.
EDIT2: It might have to do with it having update delay.
The binding should work for every keypress -- if it's not, you're doing something somewhere else in your code to prevent it from working, or your testing is flawed.
If you want a function to be called whenever the value changes, you might want to consider setting a trace on the variable associated with the entry widget. The trace will fire whenever the value changes, no matter whether it's through keyboard input, pasting with the mouse, etc. It will not call your callback when the user uses the arrow keys or the return key, or any other key that doesn't affect the value.
For example:
def var_callback(*args):
print "the variable has changed:", PathFileName.get()
PathFileName.trace("w", var_callback)
It can be solved by changing
EntryFilePath.bind("<Key>", updateAmountOfPeople)
to
EntryFilePath.bind("<KeyRelease>", updateAmountOfPeople)

Python Tkinter use to emulate blinking with interaction of multiple buttons

I am looking for a solution to emulate the behavior of the UI of an electronic component and the user interaction (which should be pushing buttons) with LEDs reporting an internal state of the electronic component.
I am using python and the tKinter module to do so.
My code runs and my GUI window displays correctly. However, when I push several times on buttons the behavior is not as expected.
I have 4 possible state for each LED (OFF, ON, (Blinking) SLOW, (Blinking) FAST).
I have 4 buttons which can have an impact on the state. Each button has an interaction function defined in the widget class I have defined, and each of this function, once called, redefines the internal state of the widget.
In order to control the blinking of the LED, I use a single loop and the self.after( ..) function. This function is the following:
def toggleLeds(self):
for led in [self.ledTxIP, self.ledRxIP, self.ledTxRS, self.ledRxRS, self.ledPower, self.ledRun, self.ledStatus, self.ledConfig]:
if (((led[1] == "SLOW") and (self._FastBlinking == 0)) or (led[1] =="FAST")):
bg = led[0].cget("background")
bg = "green" if bg == "black" else "black"
led[0].configure(background=bg)
elif((led[1] == "OFF") and (self._update == 1)):
led[0].configure(background="black")
self._update = 0
elif (self._update == 1):
led[0].configure(background="green")
self._update = 0
self._FastBlinking = (self._FastBlinking + 1)%2
self.update_idletasks()
self.after(self._FastBlinkTime, self.toggleLeds)
This one is called recursively through the self.after function, and at the end of the interaction function I have defined for each button.
Here is how I have defined a single LED:
self.ledTxIP = [tk.Label(self, width=1, borderwidth=2, relief="groove"),"OFF"]
And here is an example of the button interaction function:
def pushMode(self):
if (re.search("Reset",self.state) == None):
if (self.clickModCnt == 0):
self.state = "Status"
self._stateTimer = int(time.gmtime()[5])
elif (self.clickModCnt == 1):
if(int(time.gmtime()[5]) - self._stateTimer < 3):
self.state = "Config"
else:
self.state = "RunMode"
else:
self.state = "RunMode"
self.clickModCnt = (self.clickModCnt + 1)%3
self._update = 1
self.updateLedState()
If anybody has an advice on this, it would be more than welcome.
I don't know why this didn't jump out at me sooner, but I think the problem is listed in your own question text, referring to the toggleLeds method:
This one is called recursively through the self.after function, and at the end of the interaction function I have defined for each button.
When the program initially runs, I'm assuming that you call toggleLeds somewhere to kick off the initial pattern for the LEDs. That sets up a single recursive loop via the self.after call at the end of the method. However, if you also call that same method every time you click a button to change state, you're setting up a new loop with every button click, and each new loop may or may not be in sync with your initial loop.
There are a couple ways that I can think of to handle this possible conflict. One is to avoid making new calls to toggleLeds, but that way there could be a delay between the button click and the new LED pattern. If you don't mind that delay, that's probably the best solution.
If you want the light/blink pattern to change immediately, you need to interrupt the current loop and start a new one with the new light/blink states. According to the Tkinter reference produced by New Mexico Tech, the after method:
...returns an integer “after identifier” that can be passed to the .after_cancel() method if you want to cancel the callback.
Here's how you could take advantage of that. First make sure that you're storing that identifier when calling the after method:
self.after_id = self.after(self._FastBlinkTime, self.toggleLeds)
Then change your toggleLeds method definition to accept an optional "interrupt" argument, and to cancel the existing after loop if that argument is True:
def toggleLeds(self, interrupt=False):
if interrupt:
self.after_cancel(self.after_id)
# Existing code follows
Finally, pass True to that argument when calling the method after a button has been clicked:
# Existing button processing code here
self.toggleLeds(interrupt=True)
With these changes in place, each button click would cancel the current after cycle and start a new one, preventing more than one cycle from running at once, which should keep the LEDs in sync.

Categories