With tkinter, how can I use the after method to make a function run periodically?
For instance, I have a speak function that only prints something in the console:
def speak():
print("Hello, world!")
How can I use the after method to call the speak function every second?
Note: the following code is written and tested in Python 3.5. Minor changes might be needed, for instance, when calling super.
The documentation describes the Widget.after method as follows:
after(delay_ms, callback=None, *args)
Registers an alarm callback that is called after a given time.
Scheduling a function
The after method is primarily used to schedule a function call after a given delay. For instance, the following code schedules a call to a function after one second:
import tkinter as tk
def speak():
print("Hello world!")
root = tk.Tk()
root.after(1000, speak)
# Output
Hello world!
Making a function run periodically
In order to make a function run periodically, one can make it call itself at the end of its own body. However, after is a method from the Widget class, so a widget is needed. Therefore, the best choice is generally to put the scheduled function inside of a class extending Widget.
The following code prints "Hello world!" every other second in the console.
import tkinter as tk
class Foo(tk.Tk):
def periodically_speak(self):
print("Hello world!")
self.after(2000, self.periodically_speak)
foo = Foo()
foo.periodically_speak()
Using parameters
One might want to pass parameters to a method that runs periodically. For this purpose, the after method unpacks every parameter after the callback as the parameters to pass to the callback. For instance, root.after(1000, foo, a, b, c) will schedule a call to foo(a, b, c). The following example shows a use of this feature to determine the behaviour of the function.
import tkinter as tk
class Foo(tk.Tk):
def periodically_speak(self, text):
print(text)
self.after(2000, self.periodically_speak, text)
foo = Foo()
foo.periodically_speak("Good night world!")
Canceling a call
The after methods returns a string, that corresponds to the call's id. It can be passed to the after_cancel method, in order to cancel a call that was scheduled.
The following example will start printing "Hello world!" every second, but will stop when pressing the button.
import tkinter as tk
class Foo(tk.Tk):
def __init__(self):
super().__init__()
self.callId = None
self.button = tk.Button(self, text="Stop", command=self.stop)
self.button.pack()
def periodically_speak(self):
print("Hello world!")
self.callId = self.after(2000, self.periodically_speak)
def stop(self):
if self.callId is not None:
self.after_cancel(self.callId)
foo = Foo()
foo.periodically_speak()
Side notes
The following points should be kept in mind.
The after method does not guarantee that the callback will be called *exactly* after the given delay, but *at least* after it. As a consequence, after should not be used where precision is required.
It might be tempting to use time.sleep in order to schedule or periodically run a function. This must be avoided when working on a GUI, because `sleep` will pause the current thread, which most of the time is the main thread. For example, this could halt the refresh of the widgets, the program would stop responding.
Related
When I use the Tkinter function "root.after()" it only runs the function specified once. In my case this is the "move" function, in the line second from the bottom of my code. Overall, I'm trying to just do a basic animation to an oval.
I'm running Python 3.7.1.
from tkinter import *
class shape:
def __init__(self, canvas):
self.canvas = canvas
self.EdgeThickness = 1
self.color="#ffffff"
def animation(self,xposgiven):
self.shape = self.canvas.create_oval(xposgiven,250,250,400,fill=self.color,width=self.EdgeThickness)
print('runninganimation')
root=Tk()
c=Canvas(root, width=1000, height=500)
c.pack()
c.configure(background="#000000")
s=shape(c)
s.xpos=5
def move():
print('runningmove')
s.xpos+=5
s.animation(s.xpos)
root.after(100,move)
root.mainloop()
I expected that the function move() would run every 100 ms but it only runs once. I thought the function root.after(time,func), ran the function 'func' every 'time' ms. But it doesn't appear to be doing this in my code. It only runs once.
after schedules a job to run exactly once. If you want it to run every 100ms, a common strategy is to have your function call after with itself as an argument before returning.
> import tkinter
> help(tkinter.Tk.after)
after(self, ms, func=None, *args)
Call function once after given time.
MS specifies the time in milliseconds. FUNC gives the
function which shall be called. Additional parameters
are given as parameters to the function call. Return
identifier to cancel scheduling with after_cancel.
I am new at python. I am using python 2.7. I want to have messagebox to notify me on every 50 seconds.
when I write this:
import sys
import time
import threading
if sys.version_info < (3,0):
import Tkinter as tkinter
import tkMessageBox as mbox
else:
import tkinter
import tkinter.messagebox as mbox
window = tkinter.Tk()
window.wm_withdraw()
def loop():
threading.Timer(20.0, loop).start()
mbox.showinfo('my app',time.ctime())
loop()
But when I press OK application freeze. What I am doing wrong?
You forgot to call window.mainloop().
This method triggers the main loop of the widget, which processes the events, and allows interaction with the widget and its children.
In addition, you should use the after widget method rather than other timers.
This method allows you to schedule a call to a method.
You might wan to have a look at this post for a more complete explanation of the after method.
The following code implements the loop function with the after method, and runs the main loop by calling window.mainloop().
def loop(root):
mbox.showinfo('my app',time.ctime())
root.after(50000, lambda: loop(root))
window = tkinter.Tk()
window.wm_withdraw()
loop(window)
window.mainloop()
Note that the loop function takes a widget as parameters, that will be your window.
This is needed, because the after method needs to be called on a widget.
Additionally, the after method takes a callback as second parameter, and I found it easier to pass it as a lambda function.
The latter is tantamount to calling root.after(50000, f) where f has been defined by def f(): return loop(root).
I am trying to learn python, Tkinter and oop. Below is the code that I wrote after following tutorial on effbot.org
from Tkinter import Tk, Frame, Label
class Nexus(object):
"""Top level object which represents entire app"""
def __init__(self, main_window):
self.nexus_frame = Frame(main_window)
self.nexus_frame.pack()
self.label = Label(main_window, text="Tkinter")
self.label.pack()
def main():
main_window = Tk()
nexus_app = Nexus(main_window)
main_window.wm_title("Hello World Window")
width = main_window.winfo_screenwidth()
height = main_window.winfo_screenheight()
main_window.wm_minsize(width=width-100, height=height-100)
main_window.mainloop()
if __name__ == "__main__":
main()
Here a top level window is created first and it is passed as argument to Nexus class where I am adding a frame and a label to the frame. Then I am setting the size of top level window relative to current screen size back in the main function.
My question is why was the top level window create in main function?
Could it not be created inside __init__ of Nexus class itself?
What difference would it make if main_window was create inside __init__ of Nexus class and mainloop() was started therein?
Once Tk.mainloop is entered, no further code will be executed. Instead, the Tk event loop will take over (hence the name).
What that means is that if you, eg, did something like this:
def main():
...
main_window.mainloop()
print 'Hello world!'
then that print statement would never be executed (or, at least, not while the GUI is running).
So, with that in mind, why is there a problem with creating the root window and executing main loop within the constructor (the __init__ statement)? Well, two basic reasons:
It would mean that the constructor never returns, which is unexpected. If a programmer sees this:
def main():
Nexus()
print 'Hello world!'
then he or she will expect that print statement to be executed. As a rule, you don't expect creating an instance of a class to be the kind of thing which will cause an infinite loop (as the event loop is).
Related to that is the second reason: it would not be possible to create more than one instance of Nexus, because as soon as you create one, Tk.mainloop will take over. Again, that's unexpected: a class is a description of a type of object, and you would normally expect to be able to instantiate more than one object like that.
At the moment, if you write:
def main():
...
Nexus(main_window)
Nexus(main_window)
then you'll get two copies of your Nexus window on the screen. That's expected, and sensible. The alternative would not be.
So what's the take-away message?
When you're dealing with GUI programs, entering the event loop is the last thing you want to do. Your setup might involve creating one object (as now), or it might involve creating many objects (eg, a complex GUI app might have two or three windows).
Because we want to be able to write similar code in both cases, the usual approach is to create the root window (the Tk object) once, and then pass it in as a reference to any classes that need to know about it.
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.
I'm new to GUI and classes and I'm a just a bit confused, when I use a button in tkinter for python it's suppose to repeat it's command when pressed. but in my program it doesn't do that. is there something wrong with me codes that might counter it? I'm trying to make a simple program that echos whatever is typed.
-Thanks
from Tkinter import *
from PIL import Image, ImageTk
import tkMessageBox
class appsMain(Frame):
def __init__(self,parent):
Frame.__init__(self,parent)
self.parent=parent
self.initUI()
def initUI(self):
self.parent.title("OrganizedWindows")
self.send=Text(self,bg="white",height=3,width=35)
self.send.place(x=17,y=235)
self.msg=Text(self,width=35,height=12,state="disable")
self.msg.place(x=17,y=20)
sendbtn=Button(self,text=" Listen ",command=self.accept)
sendbtn.place(x=305,y=240)
self.pack(fill=BOTH, expand=1)
def accept(self,msg):
self.msg.configure(state="normal")
self.msg.insert(INSERT,msg+"\n")
self.msg.insert(INSERT,"BYE")
self.msg.configure(state="disable")
root=Tk()
root.geometry("350x300+300+300")
app=appsMain(root)
root.mainloop()
Your code has a few problems. The first is solved easily:
sendbtn=Button(self,text=" Listen ",command=self.accept)
doesn't work because when the button is clicked, self.accept is called with no additional arguments (accept expects 2 arguments, [self and msg], but it is only getting 1 [self]).
You can work around this with lambda:
sendbtn=Button(self,text=" Listen ",command=lambda : self.accept("some message here"))
(This is equivalent to):
def func:
self.accept("some message here")
sendbtn=Button(self,text=" Listen ",command=func)
But, I don't know if you want to constantly add different messages ... or where they come from, so it is difficult to give a general solution at this point.
Tkinter applications happily continue to run even after exceptions are raised. It is a good idea to watch the terminal for exceptions when you're developing a Tkinter application (In this case, it pointed me right to the source of the problem).
This is to better answer your Lambda comment question. Lambda is a quick, one-liner way to write a function. The variable you set it to is the same as the name of your function for def myFunction. Then you say the keyword lambda and the letter(s)/word(s) you put after the keyword lambda are just the parameters of your function. Next you put a colon (just like you would for a normal function-> def myFunction:). After that you write whatever you want the function to return. So if you wanted a function to square a given number, n, then you could write it normally like:
def square_num(n):
return n**2
OR as a cool Lambda:
square_num = lambda n: n**2
You can also have as many parameters as you wish, just like in a normal function, so for a given number raised to the x power you could write:
raise_num = lambda n, x: n**x