Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tkinter after_cancel in python

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" method, bu it continues to draw..

Here is the code.

#! /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:
            print("clicked2")    
            tk.after_cancel(task)
    canvas.bind("<Button-1>",callback)        
    tk.after(1000,task)           
tk.after(1000,task)

tk.mainloop()   
like image 534
user3657258 Avatar asked Sep 06 '14 16:09

user3657258


People also ask

What is config () in tkinter?

config is used to access an object's attributes after its initialisation. For example, here, you define. l = Label(root, bg="ivory", fg="darkgreen") but then you want to set its text attribute, so you use config : l.

What is Tk Mainloop ()?

window.mainloop() tells Python to run the Tkinter event loop. This method listens for events, such as button clicks or keypresses, and blocks any code that comes after it from running until you close the window where you called the method.

Does tkinter have drag and drop?

The tkinter. dnd module provides drag-and-drop support for objects within a single application, within the same window or between windows. To enable an object to be dragged, you must create an event binding for it that starts the drag-and-drop process.


2 Answers

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.

like image 121
W1ll1amvl Avatar answered Sep 20 '22 07:09

W1ll1amvl


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.

like image 29
Gary02127 Avatar answered Sep 23 '22 07:09

Gary02127