I am using .after to invoke a function which moves an image inside my GUI. When i run the code and call the function "myFunc" the image does move, but it happens instantaneously. It should gradually move across the screen. I am unsure as to why this is happening.
def movRight():
global img
global imgx
canvas.move(imgx,20,0)
return
def myFunc():
moveController(1,20)
return
def moveController(extruder, position):
global e1current
global e2current
global e3current
global e4current
if extruder == 1:
while position > e1current:
print("moving")
e1current+=1
main.after(500,movRight)
return
.after does not block execution of the loop. It's more like it spawns a seperate thread which begins execution after 500 (in your case) milliseconds. In other words, .after does not block the loop for 500ms, call movRight and then continue the loop. Take a look at this example:
import tkinter as tk
def print_hi():
print("hi")
root = tk.Tk()
for i in range(5):
root.after(1000, print_hi)
print("loop done")
root.mainloop()
This outputs "loop done", and then, approximately one second later, it prints "hi" five times.
Consider this code:
while position > e1current:
main.after(500,movRight)
It is the same as if you had written it this way:
main.after(500,movRight)
main.after(500,movRight)
main.after(500,movRight)
...
Since calling main.after(500,movRight) probably executes in a millisecond or so, you are queueing up hundreds or thousands of calls that will all execute 500ms in the future.
In other words, it's no differen than if you had done this:
def something():
movRight()
movRight()
movRight()
...
main.after(500,something)
The proper way to do an animation is to create a function that does one frame of animation, and then reschedules itself.
For example:
def moveController():
movRight()
e1current+=1
if position > e1current:
main.after(500, moveController)
Related
I am working on a python tkinter program that monitoring computer temperature, and I want it to update the temperature value after a fixed time.
the following function is what I used to do that:
def update():
get_temp()#the function that get the computer temperature value, like cpu temperature.
...
def upd():
update()
time.sleep(0.3)
upd()#recursive call function.
upd()
but this way will hit the recursive limit, so the program will stops after a period of time.
I want it to keep updating the value, what should I do?
I don't know if I change it to after() it will be better or not.
but if I use after(), the tkinter window will freeze a while, so I don't want to use it.
Thank you.
It needs loop.
It should be:
def update():
get_temp()#the function that get the computer temperature value, like cpu temperature.
...
def upd():
while True:#needs a while true here and don't call upd() in this function.
update()
time.sleep(0.3)
upd()#this upd() is outside the function upd(),it used to start the function.
Thanks to everyone who helped me.
Recursion is inadequate in this use-case, use a loop instead.
Tkinter in particular has got a method which allows you to execute a function in an interval without disrupting the GUI's event loop.
Quick example:
from tkinter import *
root = Tk()
INTERVAL = 1000 # in milliseconds
def get_temp()
# ...
root.after(INTERVAL, get_temp)
get_temp()
root.mainloop()
I am making a GUI application in tkinter and I need the code in a function to run every 60 seconds. I have tried using the root.after(root, function) pause, but using this, my GUI window froze and the code in the function continued to run as I was interrupting the root.mainloop(). Here is a bit of the code with which I am having problems:
def start():
global go
go=1
while go == 1:
getclass()
root.after(1000,func=None)
def stop():
global go
go=0
def getclass():
#my code here
print("Hello World")
I just want getclass() to run every 60 seconds when the start button is pushed. Then I want this 60 second loop to stop when the stop button is pushed.
Thank you in advance,
Sean
You are overthinking this: What is needed is very simple, maybe like this:
def start():
getclass()
root.after(60000, start) # <-- 60,000 milliseconds = 60 seconds
When start() gets called, it executes a call to getClass(), then sets a callback that will call itself again in 60 seconds.
Minimal app demonstrating the use:
import tkinter as tk
def getclass():
print('hello')
def start():
getclass()
root.after(60000, start) # <-- 60,000 milliseconds = 60 seconds
root = tk.Tk()
start()
root.mainloop()
How come when I run my code, it will sleep for 3 seconds first, then execute the 'label' .lift() and change the text? This is just one function of many in the program. I want the label to read "Starting in 3...2...1..." and the numbers changing when a second has passed.
def predraw(self):
self.lost=False
self.lossmessage.lower()
self.countdown.lift()
self.dx=20
self.dy=0
self.delay=200
self.x=300
self.y=300
self.foodx=self.list[random.randint(0,29)]
self.foody=self.list[random.randint(0,29)]
self.fillcol='blue'
self.canvas['bg']='white'
self.lossmessage['text']='You lost! :('
self.score['text']=0
self.countdown['text']='Starting in...3'
time.sleep(1)
self.countdown['text']='Starting in...2'
time.sleep(1)
self.countdown['text']='Starting in...1'
time.sleep(1)
self.countdown.lower()
self.drawsnake()
It does this because changes in widgets only become visible when the UI enters the event loop. You aren't allowing the screen to update after calling sleep each time, so it appears that it's sleeping three seconds before changing anything.
A simple fix is to call self.update() immediately before calling time.sleep(1), though the better solution is to not call sleep at all. You could do something like this, for example:
self.after(1000, lambda: self.countdown.configure(text="Starting in...3"))
self.after(2000, lambda: self.countdown.configure(text="Starting in...2"))
self.after(3000, lambda: self.countdown.configure(text="Starting in...1"))
self.after(4000, self.drawsnake)
By using after in this manner, your GUI remains responsive during the wait time and you don't have to sprinkle in calls to update.
I writing my first pyglet app to visualize some calculation another script makes.
Unfortunately I'm stuck and can't figure out where to go next.
I'm trying to move a sprite from its position to another position using a for loop,
so at the end of the each loop it changes the sprite position relative to the old position a specific distance calculated by the loop.
This is a simplified version of the code I'm writing (I hope this will help others with a similar problem, then copy my own specific project).
#window.event
def on_draw():
window.clear()
batch.draw()
def update(yd):
Ball.y += yd
distance = [2,1,3,-2,-3,2,4,-5,1,2,3,-4,3,5,2,-2,3,2,3,-2,]
### test update in loop ###
for i in distance:
time.sleep(0.1)
update(i)
### Run app ##
pyglet.app.run()
I don't want to use the pyglet.clock.schedule_interval(update()) because the loop can take longer to calculate then the clock.schudule, the movement will not be smooth but that's fine.
The problem I have is nothing happens until the loop is finished, only after python runs through the loop it calls pyglet.app.run(), but if I call pyglet.app.run() before the loop, it will only run the loop after I close the pyglet windows.
When using pyglet, you're not in control of the main loop of the program.
The main loop is started by pyglet.app.run(), and you can hook into events with the window.event decorator.
You run your loop before the main loop is started. That's why it seems that nothing happens.
If you want to call update every 0.1 seconds, you should use clock.schedule_interval, which takes a function to call and an interval parameter. If you want to move your sprite once for each value in distance, you can make use of an iterator and a nested function, like:
def update():
g = iter(distance)
def inner():
try:
update(next(g))
except StopIteration:
pass
return inner
pyglet.clock.schedule_interval(update(), .1)
Edit in response to your comment:
Given your loop:
for i in distance:
time.sleep(0.1)
update(i)
you can turn it into an iterator:
def calculate():
for i in distance:
time.sleep(0.1) # just some expensive calculation
yield i
and use it inside on_draw():
calc = calculate()
#window.event
def on_draw():
window.clear()
batch.draw()
try: update(next(calc))
except StopIteration: pass
I am new to Tkinter, so I apologize if this is easy, but I have search for a couple of hours and can't figure it out. What I want to do is after the mainloop is idle, I always want to call the function checkForGroupUpdates(). When I run the code below, it only runs once. I can't figure out to have it run every time the mainloop is idle. I appreciate the help.
from Tkinter import *
import random
class Network(Frame):
""" Implements a stop watch frame widget. """
def __init__(self, parent=None, **kw):
Frame.__init__(self, parent, kw)
self.makeWidgets()
def makeWidgets(self):
""" Make the time label. """
self._canvas = Canvas(self, width=600, height=400)
self._canvas.pack()
def checkForGroupUpdates(self):
print "checking"
h=0
this=10
while this>.0001:
this=random.random()
print h
h=h+1
print "checked"
def main():
root = Tk()
nw = Network(root)
nw.pack(side=TOP)
root.after_idle(nw.checkForGroupUpdates)
root.mainloop()
if __name__ == '__main__':
main()
#user1763510, notice that in Bryan Oakley's answer, he has checkForGroupUpdates call self.after again. This is because self.after only does a single call, so getting repeated calls requires having it call itself within the function that gets called by the first call. This way, it keeps repeatedly calling itself.
The same goes for the after_idle() function. You have to have checkForGroupUpdates call after_idle() again at the bottom.
Here is the documentation for after, after_idle, etc. There is even a little example in the after description, which makes it all clear.
Documentation: http://effbot.org/tkinterbook/widget.htm
Example from link above, under the afterdescription:
#Method 1
class App:
def __init__(self, master):
self.master = master
self.poll() # start polling
def poll(self):
... do something ...
self.master.after(100, self.poll)
To use after_idle instead, it would look like this:
#Method 2
class App:
def __init__(self, master):
self.master = master
self.poll() # start polling
def poll(self):
... do something ...
self.master.update_idletasks()
self.master.after_idle(self.poll)
Notice the addition of the self.master.update_idletasks() line. This draws the GUI and handles button presses and things. Otherwise, after_idle() will suck up all resources and not let the GUI self-update properly in the mainloop().
An alternative to using
self.master.update_idletasks()
self.master.after_idle(self.poll)
is to use:
#Method 3
self.master.update_idletasks()
self.master.after(0, self.poll)
Using self.master.after(0, self.poll) is my preferred technique, as it allows me to easily change the 0 to something else if I decide I don't need to run self.poll constantly. By increasing the delay time to at least 1 ms, you no longer need to call self.master.update_idletasks() at all. So, this works too:
#Method 4
self.master.after(1, self.poll)
Also notice that for all examples above, calling self.poll() in the __init__ function is what kicks it all off, and storing master into self.master is necessary simply so that inside poll you can call the after or after_idle function via self.master.after_idle, for example.
Q: Is this stable/does it work?
A: I ran a test code using Method 3 just above for ~21 hrs, and it ran stably the whole time, allowing the GUI to be usable and all.
Q: What is the speed comparison for each method above?
A:
Method 1: (I didn't speed test it)
Method 2: ~0.44 ms/iteration
Method 3: ~0.44 ms/iteration
Method 4: ~1.61 ms/iteration
Q: Which is my preferred method?
A: Method 3 or 4.
Instead of calling the function all the time when the app is idle, you should just call it once every fraction of a second. For example, if you want to check 10 times every second you would do something like this:
def checkForGroupUpdates(self):
<do whatever you want>
self.after(100, self.checkForGroupUpdates)
Once you call that function once, it will arrange for itself to be called again in 100ms. This will continue until the program exits. If the program goes "non-idle" (ie: while responding to a button click), this function will pause since tkinter is single-threaded. Once the program goes idle again, the check will continue.