I'm using Tkinter for a small overlay on a screen that got to be update every 1 or 2 second. I search a lot about it and find the after() function that could be execute after the mainloop. But this one doesn't work quitely, the idea is to call a after() function witch contain another after() function and the main function that we want to execute in the loop, like that :
def my_functions():
print('task done')
ws.after(1000, my_functions)
ws.after(1000, my_functions())
But this one got a limit of "function calling itself" of 992, that means my window will refresh her value only for 16 min 32 sec (or 992 sec) then will crash.
I could maybe destroy and recreate all my window and loop before the limit but I doesn't love this solution and I would have to work a lot on it, I would prefer a easyest solution, but I'm searching for 3 days and doesn't find anything good.
With trying to reproduce the following error :
RecursionError: maximum recursion depth exceeded while calling a Python object
I find the solution, so for any other I will write it.
Like you said, you can't use the after() method if you pass an argument like that:
root.after(1000, task(arg))
If you want to pass an argument you have to do like that:
root = Tk()
x = 0
def task(x):
x += 1
print(x)
root.after(2000, task, x) # reschedule event in 2 seconds
root.after(2000, task, x)
root.mainloop()
If you use the first solution, it's only rise the error after the of the maximum recursion, tkinter doesn't find any mistake in the formulation and you can still execute your code.
Which is a bit weird for me, but I just doesn't understand quitely the documentation when I read it. And with re-reading it seems logic now.
Related
import tkinter as tk
window = tk.Tk()
def function_example():
a= 2
My question is what is the difference between these two following code executions, and why are they producing different results?
Scenario 1:
window.after(1000, function_example)
VS.
Scenario 2:
window.after(1000)
function_example()
I thought both should produce the same outcome, but they are producing different outcomes.
Perhaps easier to understand with a slightly different example.
import tkinter as tk
window = tk.Tk()
def function_example( note ):
print( note )
window.after( 5000, function_example, 'Called from after' )
function_example('1st call outside after.')
window.after( 1000 ) # Stops execution of the GUI for 1 second
function_example( '2nd call outside after.' )
window.mainloop()
Results are
1st call outside after. # Prints as the GUI is being created
2nd call outside after. # Prints 1 second after that
Called from after # 5 seconds after the GUI is created
No python code is processed for 1 second after the after method is called without a callback function being specified.
The GUI window opening as the '2nd call outside after.' is printed helps to demonstrate this.
after with a single argument arguments works exactly like time.sleep - it will cause the program to freeze for the given duration. Tkinter is not able to respond to any events during that time, including events that tell it to redraw the window. When the given time period has expired, the function will return and any following code can continue.
after called with two or more arguments will add the second argument on a queue to be run after a delay given as the first parameter. In this case, the call to after returns immediately and your application will continue executing. As soon as possible after the given delay has elapsed, the function will be run.
I'm a beginner programmer and I'm trying to learn how to make code run continuously in tkinter.
My aim for testing is just to get a label to change colour every 2 seconds regardless of input.
I understand I can use the following format (with lbl initialised as a Label)
def switch():
if lbl.cget('bg') == 'blue':
lbl.config(bg='black')
else:
lbl.config(bg='blue')
lbl.after(2000, switch)
This works fine. However, I want to be able to call switch for any label rather than just lbl specifically.
If I try the below I immediately get a recursion depth error
def switch(i):
if i.cget('bg') == 'blue':
i.config(bg='black')
else:
i.config(bg='blue')
i.after(2000, switch(i))
lbl.after(2000, switch(lbl))
I'm not sure why this is a the case, or what I could do to get round it so any help would be appreciated!!!
You can pass positional arguments to after. To run switch(i) after 2 seconds you can call after like this, adding the positional arguments after the function:
i.after(2000, switch, i)
Likewise, to run switch(lbl) do this:
lbl.after(2000, switch, lbl)
I am learning with tkinter, and that involves fluid motion of shapes. That's where I am stuck now.
I get inconsistent recursion errors. The code works no problem on Windows 1O, but on my macOS it crashes.
Furthemore, if the time between recursions is ≥ 17 milliseconds, it just stops making errors and runs as intended. I've simplified the code, just to contain the one problem.
from tkinter import *
master = Tk()
canvas = Canvas(bg = "gray", width = 1000, height = 800)
canvas.pack()
cara = canvas.create_line(100,100,900,100, width = 5, fill = "red")
def moveCara():
canvas.move(cara, 0,1)
canvas.after(16, moveCara) # Here is the time setting, change it to 17 and the thing
# does not crash.
canvas.update()
moveCara()
mainloop()
This code causes crash on my MacBook:
RecursionError: maximum recursion depth exceeded during compilation
However, if I change the time in canvas.after() to 17 or greater, everything works. why is that?
The problem is this line of code:
canvas.update()
You should definitely not be calling update inside a function called from an event handler (and running a function via after is considered an event handler). It's completely unnecessary to make your program work and needs to be removed.
What is happening is that your call to after adds some work to be done by mainloop after a given amount of time. When you call update, it performs that work if the given amount of time has elapsed, otherwise it might not do anything at all.
If your computer is slow enough, then by the time you call update it will be time for the next frame of your animation. That causes moveCora to be called again via canvas.update() before the first call returns. And that is why you get a recursion error.
I wasn't able to duplicate the problem on my machine at 16ms, but I was able to reproduce it when I moved the time to 5ms. You must have a machine that takes more than 16ms to move the line and then call after. When I remove the call to canvas.update() I am able to set the value down to 1ms without a problem.
So I have this very simple thing I wrote and it's killing me trying to figure out why it won't work. All it does it print a statement when you click.
So for the first example I had a button and assigned the function printName1 directly to it, which worked perfectly fine.
Then the next thing was to bind it using the .bind() function. So in this case we just have a frame that prints out certain things based on which button you press. But unfortunately whenever I use bind, it throws the error show above. References tkinter\__init__.py for the error, so it's not something directly in my code but maybe it needs to be done differently? Thanks guys.
from tkinter import *
root = Tk()
def printName1():
print('Jack')
def printName2():
print('John')
def printName3():
print('Jill')
frame = Frame(root, width=300, height=250)
frame.bind("<Button-1>", printName1)
frame.bind("<Button-2>", printName2)
frame.bind("<Button-3>", printName3)
frame.pack()
root.mainloop()
EDIT: The error is confusing because it made it seem like there was an extra argument when there should be 0. But actually I needed to add an argument to the functions and that was event. so it should be def printName1(event) and so on. Just figured I would let you guys know what worked for me in case anyone stumbles upon this.
If you refer to the documentation regarding tkinter events and bindings, you will see that when an event is triggered, the associated event object will be passed as the first (and only) argument to the bounded function (being printName1 and friends in your case).
So what you need to do is to modify those printName* functions to accept the event argument.
def printName1(event):
print('Jack')
Then what you desired to achieve should work.
Naturally, you could make the event argument optional as #TigerhawkT3 suggested.
Events, such as from the keyboard/mouse, are all sent to the application with information about the event: which key was it, where was the mouse when you clicked, that sort of thing. This means that any callback bound to such an event needs to take an argument. If you want to also bind it to a Tkinter Button, which doesn't take an event, you can handle that as well. Just define your functions with a default argument:
def printName1(event=None):
...
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".