Reputation: 3882
I am working with tkinter in python2.7. All I want to do is draw a canvas in one class, then class another class which will move a square around the canvas. But for some reason I get
Exception in Tkinter callback
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1410, in __call__
return self.func(*args)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 495, in callit
func(*args)
AttributeError: Animation instance has no __call__ method
From what I have seen, people get this error when they have over ridden a value, or a name shadows another. But I can't see what I might be over riding. Can anyone else see the problem?
driver.py:
from Tkinter import *
import animation
class Alien(object):
def __init__(self):
#Set up canvas
self.root = Tk()
self.canvas = Canvas(self.root, width=400, height=400)
self.canvas.pack()
#Vars
self.map = [[1, 0, 0, 1, 0], [0, 1, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 1, 0, 0], [1, 0, 0, 1, 0]]
self.x = 0
self.y = 0
r = 50
land = {}
#Draw Init
for i, row in enumerate(self.map):
for j, cell in enumerate(row):
color = "black" if cell else "green"
land[(i, j)] = self.canvas.create_rectangle(r * i, r * j , r * (i + 1), r * (j + 1),
outline=color, fill=color)
self.creature = self.canvas.create_rectangle(r * self.x, r * self.y, r * (self.x + 1), r * (self.y + 1),
outline="red", fill="red")
self.canvas.pack(fill=BOTH, expand=1)
#Action
self.root.after(0, animation.Animation(self.root, self.canvas, self.creature))
#Clost TK
self.root.mainloop()
a = Alien()
animation.py:
from random import randrange
class Animation():
def __init__(self, root, canvas, creature):
self.x = self.y = 0
i = randrange(1, 5)
if i == 1:
self.y = -1
elif i == 2:
self.y = 1
elif i == 3:
self.x = -1
elif i == 4:
self.x = 1
self.canvas = canvas
self.creature = creature
for i in range(10):
root.after(250, self.animate())
def animate(self):
#root.after(250, self.animate(canvas, creature))
"""Moves creature around canvas"""
self.canvas.move(self.creature, self.x * 50, self.y * 50)
#self.canvas.update() no longer needed
Upvotes: 1
Views: 7552
Reputation: 940
You're passing in an Animation instance to after, which then tries to call it. I suspect you meant to create an instance and then pass in its animate method, right?
EDIT: So to elaborate a bit now that I'm back on the Internet; the problem is with this line:
self.root.after(0, animation.Animation(self.root, self.canvas, self.creature))
This creates an Animation
instance and passes it to the after
method, which then bombs out when it cannot figure out how to call it. I'd do something like:
animator = animation.Animation(self.root, self.canvas, self.creature)
self.root.after(0, animator.animate) # get things going
Also, as pointed out elsewhere, the loop inside the Animation constructor is broken in at least two ways:
for i in range(10):
root.after(250, self.animate())
sets up ten callbacks, all relative to the current time -- i.e. after a quarter of a second, you'll trigger ten callbacks at once. And they will all fail, since you're calling the animate
method and passing in its return value (which is None
) to after
, instead of passing in the method itself. Or in other words,
after(ms, function())
is the same as
value = function()
after(ms, value)
which isn't really what you want. One way to fix this is to add self.root = root
to the constructor, and then do:
self.root.after(250, self.animate)
inside animate
to trigger another call a quarter of a second later.
Alternatively, you can let Tkinter keep track of things for you; the after
method lets you pass in an argument to the callback, and you can use that to pass in the after
function itself (!) so the animate
method doesn't have to look for it:
def animate(self, after):
... do animation ...
after(250, self.animate, after)
and then set it off with a single call to root.after, passing in that method:
root.after(0, animator.animate, root.after)
Upvotes: 3
Reputation: 91017
Besides the already mentioned faults, you as well have
for i in range(10):
root.after(250, self.animate())
in your animation.py. Here applies the same: you want to give root.after()
a callable, self.animate
, instead of the result of calling this callable once (self.animate()
).
The ()
do the call of the "thing" mentioned before. If you want to call it now, use ()
. If you don't (and you don't in this case), don't put ()
here.
Upvotes: 2
Reputation: 59426
You give an Animation
instance as callback to self.root.after()
. Callback objects are called after the given time. Calling them means that their __call__()
method gets called. Your Animation
class does not have such a method. I propose to add one which then does whatever you want it to do. For instance:
class Animation():
#
# ... your stuff from above ...
#
def __call__(self):
self.animate()
Another option would be to give the to-be-called method to after()
directly:
self.root.after(0, animation.Animation(self.root, self.canvas, self.creature).animate)
Upvotes: 2