The attached code randomly draws squares on the canvas using the \"after\" method. I expected to stop the drawing when I clicked the mouse button using the \"after_cancel\"
Alright, so you were close to getting it to work, what you need to do is set tk.after(...)
to a variable, not the recurring function you want to stop, then when you want to use after_cancel(...)
, you call that variable and it will stop all after methods with that name
source: How do I stop tkinter after function?
This will do it:
from Tkinter import *
import time
import random
tk = Tk()
canvas = Canvas(tk, width=1920, height=1080, background="grey")
canvas.pack()
def xy(event):
xm, ym = event.x, event.y
def task():
w=random.randint(1,1000)
h=random.randint(1,1000)
canvas.create_rectangle(w,h,w+150,h+150)
def callback(event):
if True:
print("clicked2")
# 'solve' is used here to stop the after... methods.
tk.after_cancel(solve)
canvas.bind("<Button-1>",callback)
solve = tk.after(1000,task)
# above and below tk.after is set to 'solve' a variable.
solve = tk.after(1000,task)
tk.mainloop()
This will stop the after methods and leave a tk window running.
Another solution this would be to use tk.destroy()
, or tk.quit()
or sys.exit()
or similar in the callback function... These will kill the tk window though.
I think this answers the question, if not just say, cheers.
When you schedule an after()
or an after_idle()
task, the call returns an ID to identify the pending action, so that you can cancel it. So grab the return value and then use it later.
Minimum changes to your code (with comments) look like this:
#! /usr/bin/env python
from tkinter import *
import time
import random
tk = Tk()
canvas = Canvas(tk, width=1920, height=1080, background="grey")
canvas.pack()
def xy(event):
xm, ym = event.x, event.y
def task():
w=random.randint(1,1000)
h=random.randint(1,1000)
canvas.create_rectangle(w,h,w+150,h+150)
def callback(event):
if True: # not sure why this is here...
print("clicked2")
tk.after_cancel(tk.after_id) # cancel it
canvas.bind("<Button-1>",callback)
tk.after_id = tk.after(1000,task)
# ^^^^^^^^^^^ this captures the after ID for later canceling.
tk.after(1000,task)
# no need to capture this ID since there's no way to cancel it yet
tk.mainloop()
There are some things you can do to improve your coding, if you're open to suggestions... First, it's usually cleanest to keep all funcs together, up at the top, rather than mixing them in with variable declarations, etc.
Also, you have the callback()
func nestled inside the task()
call, which causes the canvas.bind()
call to happen over and over again needlessly. If you pull that func out and make it at the same level as the other funcs, the code is cleaner and more efficient.
There are a few other things, too, that I would recommend. But here's an alternate version of your code, which you may or may not find helpful:
#! /usr/bin/env python
from tkinter import *
import time
import random
def xy(event): # unused...
xm, ym = event.x, event.y
def task():
w=random.randint(1,1000)
h=random.randint(1,1000)
canvas.create_rectangle(w,h,w+150,h+150)
tk.after_id = tk.after(1000,task)
def callback(event):
print("clicked2")
if tk.after_id:
tk.after_cancel(tk.after_id)
tk.after_id = None
if __name__ == '__main__':
tk = Tk()
tk.after_id = None
canvas = Canvas(tk, width=1920, height=1080, background="grey")
canvas.pack()
canvas.bind("<Button-1>", callback)
task() # tk.after(1000,task)
tk.mainloop()
Also, as a side note, the after(0
and after_idle()
calls allow you to pass arguments to be passed to the called function. So, for example, you could have a function:
def my_after_function(one, two, three='None'):
print(one, two, three)
and you could call:
tk.after(100, my_after_function, 'first', 'second')
which would give you:
first second None
You can't pass kwargs, but you can pass positional args.
Happy coding!