Getting a TypeError when trying to increase speed of animation using tkinter
The .bind()
method returns an event and passes it to the given function.
Have a look here :
def increaseSpeed(self): if self.sleepTime>5:self.sleepTime-=20def decreaseSpeed(self): self.sleepTime+=20
Both these function don't take any arguments and so you see an error
So you need to do something like this :
def increaseSpeed(self,event = None): #or def increaseSpeed(self,*args): #[...]def decreaseSpeed(self,event = None): #or def decreaseSpeed(self,*args): #[...]
Here's the final working code -
from tkinter import *class controlAnimation: def __init__(self): window = Tk() window.title("Racing Car") self.width = 250 self.height = 100 self.canvas = Canvas(window, bg="white", width=self.width, height=self.height) self.canvas.pack() frame = Frame(window) frame.pack() btStop = Button(frame, text = "Stop", command = self.stop) btStop.pack(side=LEFT) btStart = Button(frame, text = "Start", command = self.start) btStart.pack(side=RIGHT) self.x=0 self.sleepTime = 100 self.canvas.create_rectangle(self.x,90,self.x+60,80,fill="black", tags = "car") self.canvas.create_oval(self.x+10, 90, self.x+20, 100, fill = "black",tags = "car") self.dx=3 self.isStopped = False self.animate() window.bind("<Up>", self.increaseSpeed) window.bind("<Down>", self.decreaseSpeed) window.mainloop() def stop(self): self.isStopped = True def start(self): self.isStopped = False self.animate() def increaseSpeed(self,event = None): #or def increaseSpeed(self,*args): if self.sleepTime>5:self.sleepTime-=20 def decreaseSpeed(self,event = None): #or def increaseSpeed(self,*args): self.sleepTime+=20 def animate(self): # Move the message while not self.isStopped: self.canvas.move("car", self.dx,0) self.canvas.after(self.sleepTime) self.canvas.update() if self.x < self.width: self.x += self.dx else: self.x = 0 self.canvas.create_rectangle(self.x-60,90,self.x,80,fill="black", tags = "car") self.canvas.create_oval(self.x-50, 90, self.x-40, 100, fill = "black",tags = "car")controlAnimation()
When using widget.bind("<Event>", func)
the function passes an extra event to the function and you can use it if you like but apparently you don't need it so you can replace your binds in init with this:
# You can either get the event using lambda if you don't want anything to do with it like this: window.bind("<Up>", lambda event: self.increaseSpeed) window.bind("<Down>", lambda event2: self.decreaseSpeed)
Or in your functions do this:
# Or you can pass it as an argument in your functiondef increaseSpeed(self, event): if self.sleepTime > 5: self.sleepTime -= 20def decreaseSpeed(self, event): self.sleepTime += 20
I believe this is what you are trying to achieve:
from tkinter import *class CarAnimation: def __init__(self): self.canvas_width = 550 self.canvas_height = 280 self.car_body_width = 100 self.car_body_height = 50 self.car_wheel_width = 20 self.car_wheel_height = 20 self.car_top_left = 0 self.move_by = 1 self.delay = 20 self.default_delay = 10 self.car_is_moving = False self.root = Tk() self.root.resizable(False, False) self.root.title("Car Animation!") self.car_canvas = Canvas(self.root, width=self.canvas_width, height=self.canvas_height, background="white", highlightthickness=0) # If you have more shapes in the canvas that you do not want to get affected by the movement store them as # below and call the move function for each of them, I just stored them in a variable for demonstration purposes self.car_body = self.car_canvas.create_rectangle(0, 0, self.car_body_width, self.car_body_height) self.car_wheel1 = self.car_canvas.create_oval(0, self.car_body_height, self.car_wheel_width, self.car_body_height+self.car_wheel_height) self.car_wheel2 = self.car_canvas.create_oval(self.car_body_width-self.car_wheel_width, self.car_body_height, self.car_body_width, self.car_body_height+self.car_wheel_height) self.start_btn = Button(self.root, text="Start Car", command=self.start_car) self.stop_btn = Button(self.root, text="Stop Car", command=self.stop_car) self.car_canvas.pack(side=TOP) self.start_btn.pack(side=BOTTOM, pady=5) self.stop_btn.pack(side=BOTTOM) self.root.bind("<Up>", self.decrease_delay) self.root.bind("<Down>", self.increase_delay) self.root.mainloop() def start_car(self): if not self.car_is_moving: self.car_is_moving = True self.move_car() def stop_car(self): if self.car_is_moving: self.car_is_moving = False def move_car(self): # Instead of storing the car's top left position and appending to it, you can also # 'self.car_canvas.coords(self.car_body)[0]' so that it would return the shape's coords if self.car_is_moving and self.car_top_left < self.canvas_width: self.car_top_left += self.move_by # You can store their layer numbers and move them one by one but since there were no more shapes # I decided to move all of them at once using 'ALL' self.car_canvas.move(ALL, self.move_by, 0) self.root.after(self.delay, self.move_car) elif self.car_is_moving and self.car_top_left >= self.canvas_width: self.car_top_left += -self.canvas_width - self.car_body_width self.car_canvas.move(ALL, -self.canvas_width - self.car_body_width, 0) self.root.after(self.delay, self.move_car) def increase_delay(self, event): # Added the if statement below so that the user cannot change the delay when the car is not moving if self.car_is_moving: self.delay += 10 def decrease_delay(self, event): if self.delay > self.default_delay and self.car_is_moving: self.delay -= 10 # You can just delete this elif statement, I just wanted to print out something to the terminal elif self.delay <= self.default_delay and self.car_is_moving: print("Delay cannot be lower than default!")if __name__ == '__main__': CarAnimation()
Note that as mentioned in the code you can use 'self.car_canvas.coords()' instead of storing the top left corner. If you have any questions regarding the code, please ask in the comments