2

I'm trying to make two balls to move on the screen the position is being updated with thread and the main thread is updating the graphics here is my code:

from tkinter import *
from threading import *
import time

width = 500
height = 500


class Ball(Thread):
    def __init__(self, canvas, x1, y1, x2, y2, color):
        super().__init__()
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2
        self.x_velocity = 9
        self.y_velocity = 5
        self.canvas = canvas
        self.id = self.canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill=color)

    def update(self):
        self.canvas.move(self.id, self.x_velocity, self.y_velocity)
        pos = self.canvas.coords(self.id)
        if pos[0] <= 0 or pos[2] >= width:
            self.x_velocity *= -1
        if pos[1] <= 0 or pos[3] >= height:
            self.y_velocity *= -1

    def run(self):
        self.update()


def main():
    master = Tk()
    canvas = Canvas(master=master, bg='Grey', width=width, height=height)
    ball1 = Ball(canvas=canvas, x1=10, y1=10, x2=40, y2=40, color='Black')
    ball2 = Ball(canvas=canvas, x1=50, y1=50, x2=80, y2=80, color='Red')
    canvas.pack()
    ball1.start()
    ball2.start()
    while 1:
        master.update()
        time.sleep(0.04)


if __name__ == '__main__':
    main()

it seems that is not working what is wrong and how to handle it ? the error message is:

Exception in thread Thread-2: Traceback (most recent call last):   File "/home/muhammad_essam/anaconda3/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()   File "/mnt/sda6/CSE/Project/GUI/Learnning/GUI101/main.py", line 30, in run
    self.update()   File "/mnt/sda6/CSE/Project/GUI/Learnning/GUI101/main.py", line 22, in update
    self.canvas.move(self.id, self.x_velocity, self.y_velocity)   File "/home/muhammad_essam/anaconda3/lib/python3.6/tkinter/__init__.py", line 2585, in move
    self.tk.call((self._w, 'move') + args)
_tkinter.TclError: out of stack space (infinite loop?)

Exception in thread Thread-1: Traceback (most recent call last):   File "/home/muhammad_essam/anaconda3/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()   File "/mnt/sda6/CSE/Project/GUI/Learnning/GUI101/main.py", line 30, in run
    self.update()   File "/mnt/sda6/CSE/Project/GUI/Learnning/GUI101/main.py", line 22, in update
    self.canvas.move(self.id, self.x_velocity, self.y_velocity)   File "/home/muhammad_essam/anaconda3/lib/python3.6/tkinter/__init__.py", line 2585, in move
    self.tk.call((self._w, 'move') + args)
_tkinter.TclError: out of stack space (infinite loop?)
furas
  • 134,197
  • 12
  • 106
  • 148
  • 1
    https://stackoverflow.com/questions/22541693/tkinter-and-thread-out-of-stack-space-infinite-loop – jmoney Jan 17 '18 at 20:07
  • You aren't using threading the way you think you are. All of the movement is happening in the main thread since that's where your infinite loop is. Tkinter isn't designed to create widgets in one thread and then update them in another. – Bryan Oakley Jan 17 '18 at 20:08
  • so what i need is that i have a multi-agent each running on a thread, each thread no is updating their locations i need theme to be update in the GUI what should i used as a library @BryanOakley – Muhammad Essam Jan 17 '18 at 20:16
  • 1
    To be honest, you don't need threads at all. Assuming you will have no more than a few hundred balls, a single thread is more than powerful enough. – Bryan Oakley Jan 17 '18 at 20:51
  • See this example for a solution that doesn't use threads: http://stackoverflow.com/a/25431690/7432 – Bryan Oakley Jan 17 '18 at 21:00
  • I got that but the project requires that each agent operates on a single thread @Bryan Oakley – Muhammad Essam Jan 17 '18 at 21:03

1 Answers1

3

Your program is not very difficult to modify so that it uses the GUI main loop and after method calls. The code in the main function should probably be encapsulated in a class that inherits from tkinter.Frame, but the following example is complete and demonstrates one possible solution:

#! /usr/bin/env python3
import tkinter

FPS = 25
WIDTH, HEIGHT = 500, 500


def main():
    tkinter.NoDefaultRoot()
    root = tkinter.Tk()
    root.resizable(False, False)
    canvas = tkinter.Canvas(root, bg='grey', width=WIDTH, height=HEIGHT)
    canvas.grid()
    balls = (
        Ball(canvas, 10, 10, 40, 40, 'black'),
        Ball(canvas, 50, 50, 80, 80, 'red')
    )
    root.after(1000 // FPS, update_balls, root, balls)
    root.mainloop()


def update_balls(root, balls):
    root.after(1000 // FPS, update_balls, root, balls)
    for ball in balls:
        ball.update()


class Ball:
    def __init__(self, canvas, x1, y1, x2, y2, color):
        self.__canvas = canvas
        self.__x_velocity = 9
        self.__y_velocity = 5
        self.__id = canvas.create_oval(x1, y1, x2, y2, fill=color)

    def update(self):
        self.__canvas.move(self.__id, self.__x_velocity, self.__y_velocity)
        x1, y1, x2, y2 = self.__canvas.coords(self.__id)
        if x1 < 0 or x2 > WIDTH:
            self.__x_velocity *= -1
        if y1 < 0 or y2 > HEIGHT:
            self.__y_velocity *= -1


if __name__ == '__main__':
    main()
Noctis Skytower
  • 21,433
  • 16
  • 79
  • 117