Canceling events after the .after() method has been called on them?

Question:

I’m writing a little game with python tkinter module using a canvas widget.
There is the person, who is just a ball, and bombs that spawn every 10 seconds.
3 seconds after a bomb spawns, it begins a process of “exploding” which I call 4 more methods on the self.bomb object to “animate” it exploding.

The object of the game is to try to “disarm” the bombs. I’ve come up with some fancy collision detection which is working. Once a collision is detected, the bomb is cleared off of the canvas using:

self.canvas.delete(self.bomb)

However, the following animation of the bomb getting bigger and changing color (exploding) still happens, even if I’ve “disarmed” the bomb. This is because my original bomb calls:

self.parent.after(3000, self.explode_first)

… which I have chained 3 more “explode” functions, all bound and called with the .after() method.

I tried to circumvent the problem by inserting conditionals into each of the methods called in the .after(), like booleans;

if self.bomb # if there's a bomb on the canvas,
     self.parent.after(125, self.explode_second) # and explode third and so on

which is not working. I want to be able to “cancel” events the tkinter queue that have been created with the after method, but here is the kicker. I don’t(cant) kill ALL the events, just the ones on the canvas. I have other events going that I want to leave intact, if possible.

Is there a way to do this? If not, feedback and/or suggestions on how to do this without canceling after events are welcomed, and Thanks in advance!

Heres the relevant code, for reference:

def start_game(self):
    self.Bombs += 1
    self.bombsLabel.configure(text = "Total Bombs: %d" % self.Bombs)
    self.b1 = random.randint(1, int(self.canvas.winfo_width()))
    self.b2 = random.randint(1, int(self.canvas.winfo_height()))
    self.b3 = self.b1 + 20
    self.b4 = self.b2 + 20
    self.create_bomb()
    self.parent.after(10000, self.start_game)

and :

# continuously make and explode bombs
def create_bomb(self):
    self.bomb = self.canvas.create_oval(self.b1, 
                                        self.b2, 
                                        self.b3, 
                                        self.b4, 
                                        fill = "red")
    if self.bomb:
        self.parent.after(3000, self.explode_first)


def explode_first(self):
    if self.bomb:
        self.b1 -= 5
        self.b2 -= 5
        self.b3 += 5
        self.b4 += 5
        self.canvas.delete(self.bomb)
        self.bomb = self.canvas.create_oval(self.b1, 
                                        self.b2, 
                                        self.b3, 
                                        self.b4, 
                                        fill = "orange")
        self.parent.after(125, self.explode_second)


def explode_second(self):
    if self.bomb:
        self.b1 -= 5
        self.b2 -= 5
        self.b3 += 5
        self.b4 += 5
        self.canvas.delete(self.bomb)
        self.bomb = self.canvas.create_oval(self.b1, 
                                        self.b2, 
                                        self.b3, 
                                        self.b4, 
                                        fill = "yellow")
        self.parent.after(125, self.explode_third)


def explode_third(self):
    self.b1 -= 5
    self.b2 -= 5
    self.b3 += 5
    self.b4 += 5
    self.canvas.delete(self.bomb)
    self.bomb = self.canvas.create_oval(self.b1,
                                        self.b2,
                                        self.b3,
                                        self.b4, 
                                        fill = "white")
    self.parent.after(125, self.explode_fourth)


def explode_fourth(self):
    self.canvas.delete(self.bomb)

def bomb_disarmed(self):

    print "disarmed the bomb!"
    self.canvas.delete(self.bomb)
Asked By: ford

||

Answers:

You could add an attribute to the class that you’re working like this:

self.isDisarmed = False

Then add this line to bomb_disarmed:

self.isDisarmed = True

Then in explode_first, add a test to see if the bomb has been disarmed:

def explode_first(self):
    if self.bomb:
        if self.isDisarmed:
            self.parent.after(125, self.explode_fourth)   # If bomb is disarmed, skip to the last step, where the bomb is deleted.
        else:                                             # Otherwise, carry on with bomb explosion
            self.b1 -= 5
            self.b2 -= 5
            self.b3 += 5
            self.b4 += 5
            self.canvas.delete(self.bomb)
            self.bomb = self.canvas.create_oval(self.b1, 
                                            self.b2, 
                                            self.b3, 
                                            self.b4, 
                                            fill = "orange")
            self.parent.after(125, self.explode_second)

As a general principle, I would recommend making Bomb a class, which has its own internal attributes and methods. That will make it easier to keep track of what is happening to the bomb, and what the result should be, and it will make it possible to do things like have multiple bombs.

EDIT:

Re: drawing on an App‘s canvas from another class’s method, here’s one way:

class App:
    def __init__(self, ...):
        self.canvas = Tkinter.Canvas(...)
        self.thingy = SomethingElse()

class SomethingElse:
    def __init__(self, parentApp):
        self.parentApp = parentApp
    def drawSomething(self):
        self.parentApp.canvas.create_oval(...)

a = App(...)
s = SomethingElse(a)
s.drawSomething()
Answered By: Brionius

The after method returns an id that represents the scheduled event. If you save the id, you can call after_cancel to cancel the event:

self.after_id = self.parent.after(3000, self.explode_first)
...
self.parent.after_cancel(self.after_id)
Answered By: Bryan Oakley

use the after_cancel() method

from docs:

w.after_cancel(id)
Cancels a request for callback set up earlier .after(). The id argument is the result returned by the original .after() call.
Answered By: coolcoder613
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.