8

This is a very basic program with which I want to make two moving balls, but only one of them actually moves.

I have tried some variations as well but can't get the second ball moving; another related question - some people use the move(object) method to achieve this, while others do a delete(object) and then redraw it. Which one should I use and why?

This is my code that is only animating/moving one ball:

from Tkinter import *

class Ball:
    def __init__(self, canvas, x1, y1, x2, y2):
    self.x1 = x1
    self.y1 = y1
    self.x2 = x2
    self.y2 = y2
    self.canvas = canvas
    self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red")

    def move_ball(self):
        while True:
            self.canvas.move(self.ball, 2, 1)
            self.canvas.after(20)
            self.canvas.update()

# initialize root Window and canvas
root = Tk()
root.title("Balls")
root.resizable(False,False)
canvas = Canvas(root, width = 300, height = 300)
canvas.pack()

# create two ball objects and animate them
ball1 = Ball(canvas, 10, 10, 30, 30)
ball2 = Ball(canvas, 60, 60, 80, 80)

ball1.move_ball()
ball2.move_ball()

root.mainloop()
nbro
  • 15,395
  • 32
  • 113
  • 196
dlohse
  • 81
  • 1
  • 1
  • 2

4 Answers4

15

You should never put an infinite loop inside a GUI program -- there's already an infinite loop running. If you want your balls to move independently, simply take out the loop and have the move_ball method put a new call to itself on the event loop. With that, your balls will continue to move until the application has been destroyed.

I've modified your program slightly by removing the infinite loop, slowing down the animation a bit, and also using random values for the direction they move. All of those changes are inside the move_ball method.

from Tkinter import *
from random import randint

class Ball:
    def __init__(self, canvas, x1, y1, x2, y2):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2
        self.canvas = canvas
        self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red")

    def move_ball(self):
        deltax = randint(0,5)
        deltay = randint(0,5)
        self.canvas.move(self.ball, deltax, deltay)
        self.canvas.after(50, self.move_ball)

# initialize root Window and canvas
root = Tk()
root.title("Balls")
root.resizable(False,False)
canvas = Canvas(root, width = 300, height = 300)
canvas.pack()

# create two ball objects and animate them
ball1 = Ball(canvas, 10, 10, 30, 30)
ball2 = Ball(canvas, 60, 60, 80, 80)

ball1.move_ball()
ball2.move_ball()

root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Ah - much thanks Bryan, that works perfectly (and more importantly I understand it)! I don't think it will be difficult to add actual functionality to it like bouncing and acceleration. – dlohse Aug 21 '14 at 17:16
  • what if I don't want anything graphic ? does Tk include a kind of dummy widget, or anything else with an `.after()` method to place a loop alongside the others – yota Mar 21 '16 at 15:54
  • @yota: If you don't want anything graphic, use your own loop. The use of `after` is a solution specific to tkinter -- though, the concepts can be applied to any toolkit with an event loop. – Bryan Oakley Mar 21 '16 at 15:58
  • Sorry it was vague :) Labjack U12 board offers a python framework and show a Tkinter based solution for keyboard and mouse capture, but there is no display, so I though I could nonetheless use the Tk machinery for the main loop and make seamless keyboard handling (with tk) alongside with other app related logic (which may be not triggered by events). The answer may be written here: stackoverflow.com/q/459083 :) – yota Mar 22 '16 at 07:17
3

This function seems to be the culprit

def move_ball(self):
    while True:
        self.canvas.move(self.ball, 2, 1)
        self.canvas.after(20)
        self.canvas.update()

You deliberately put yourself in an infinite loop when you call it.

ball1.move_ball()    # gets called, enters infinite loop
ball2.move_ball()    # never gets called, because code is stuck one line above
Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
2

Its only moving one because the program reads only one variable at a time. If you set the program to read when the ball gets to a certain spot, say the end of the canvas, you could then code the program to read the next line and trigger the second ball to move. But, this will only move one at a time.

Your program is literally stuck on the line:

ball1.move_ball()

And it will never get to line:

ball2.move_ball()

Because there isn't a limit to where the loop should end.

Otherwise, the answer by "sundar nataraj" will do it.

Donald
  • 633
  • 5
  • 16
0

try instead of self.canvas.move(self.ball, 2, 1) use

self.canvas.move(ALL, 2, 1)

All this used to move all the objects in canvas

sundar nataraj
  • 8,524
  • 2
  • 34
  • 46
  • Thanks for the quick responses. That does animate both if I use "ALL" in the move method, but I want to have them move independently (for any number of balls), using a .move(ALL, x, y) will move all of the objects in the canvas in the same x,y direction. Maybe I shouldn't be using a while loop to do this in the first place? – dlohse Aug 21 '14 at 16:34
  • @Kazark All is used to move all the objects in canvas.but OP need to try different . i think he understood the point – sundar nataraj Aug 21 '14 at 16:52
  • The reason I left that comment was because your post show up in the low-quality review queue. Generally "Try..." is better as a comment than an answer. "Do..." is a good answer. – Keith Pinson Aug 21 '14 at 16:54