Keeping track of each point of each polygon on a canvas with tkinter

Viewed 231

I'm making and UI for a Tangram (puzzle with different polygons) using tkinter in Python and I'd like to keep track of the coordinates of each point of each polygon when they move around my canvas.

To do so, I created this class:

class Polygon:
    def __init__(self, coords, color, canvas):
        self.coords = coords
        self.color = color
        self.move = False

        canvas.bind('<Button-1>', self.start_movement)
        canvas.bind('<Motion>', self.movement)
        canvas.bind('<ButtonRelease-1>', self.stopMovement)
        canvas.bind('<Button-3>', self.rotate)

        canvas.create_polygon(self.coords, fill=self.color)

Each polygon is created this way :

medium_triangle = Polygon((0,0, 100*math.sqrt(2),0, 0,100*math.sqrt(2)),
                          'red', drawing_place)
small_triangle_1 = Polygon((0,0 ,100,0, 0,100), 'purple', drawing_place)
[...]
big_triangle_2 = Polygon((0,0, 200,0, 0,200), 'green', drawing_place)

My main problem is that it looks like I can only modify the coords attribute of the last Polygon created. I use the mouse to drag the pieces on my canvas and I use these methods to make my Polygons move:

def start_movement(self, event):
    self.move = True

    # Translate mouse coordinates to canvas coordinate
    self.initi_x = drawing_place.canvasx(event.x)
    self.initi_y = drawing_place.canvasy(event.y)

    self.movingimage = drawing_place.find_closest(self.initi_x, self.initi_y,
                                                  halo=1)  # get canvas object
                                                           # ID of where mouse
                                                           # pointer is.

def movement(self, event):
    if self.move:

        end_x = drawing_place.canvasx(event.x)  # Translate mouse x screen
                                                # coordinate to canvas coordinate.
        end_y = drawing_place.canvasy(event.y)  # Translate mouse y screen
                                                # coordinate to canvas coordinate.

        deltax = end_x - self.initi_x  # Find the difference
        deltay = end_y - self.initi_y  # Find the difference

        self.newPosition(deltax, deltay)

        self.initi_x = end_x  # Update previous current with new location
        self.initi_y = end_y
        drawing_place.move(self.movingimage, deltax, deltay)  # Move object

def stopMovement(self, event):
    self.move = False
    affichage(self)

I manage to add to my initial coordinates, the displacement which was carried out thanks to my new_position method :

def newPosition(self, deltax, deltay):
    coord = self.coords  # Retrieve object points coordinates
    old_coord = list(coord)  # Tuple to List
    c = []  # New coords
    i = 0  # Cursor on old_coord
    for coordinates in old_coord:

        # check if index of coordinates in range of i and len(old_coord)
        # in old_coord is pair (x coord).
        if (old_coord.index(coordinates, i, len(old_coord)) % 2) == 0:
            c.append(coordinates + deltax)
        else:  # index's impair => y-coord
            c.append(coordinates + deltay)
        i += 1

    coord2 = tuple(c)  # List to Tuple
    self.set_coords(coord2)

 def set_coords(self, coords):
    self.coords = coords

But as you can see right here in my console

just after medium_triangle declaration:
(0, 0, 141.4213562373095, 0, 0, 141.4213562373095)
(0, 0, 200, 0, 0, 200)
(0, 0, 200, 0, 0, 200)
(0, 0, 200, 0, 0, 200)
(0, 0, 200, 0, 0, 200)
(297.0, 61.0, 497.0, 61.0, 297.0, 261.0)
(551.0, 166.0, 751.0, 166.0, 551.0, 366.0)
(951.0, 250.0, 1151.0, 250.0, 951.0, 450.0)

During the declarations of my polygons, I seem to be able to print their coordinates with medium_triangle.coords but after, when I click on my canvas, it directly displays the coordinates of the last one declared. And when I move another piece on my canvas it just adds to the same Polygon.

I'm not quite comfortable with classes and methods etc but I thought I understood that each of my polygons was a different instance of my class but despite this it looks like I can only access one instance of the Polygon.

I hope my problem is clear, have I really created different polygons and if yes why can't I modify them separately?

2 Answers
canvas.bind('<Button-1>', self.start_movement)
canvas.bind('<Motion>', self.movement)
canvas.bind('<ButtonRelease-1>', self.stopMovement)
canvas.bind('<Button-3>', self.rotate)

Your canvas (of type Canvas, or so I assume) can only be bound to one action per key. Try this code:

canvas.bind('<Button-1>', self.start_movement)
canvas.bind('<Button-1>', lambda e: print("ok"))

...and you'll see that it won't call start_movement() anymore, because the lambda will be called instead. Here, the only functions bound to the canvas are the ones you last called: so the ones in the initialization of the last Polygon you created. By binding new methods, you erased the previous bindings to the same keys.

You should not use canvas.bind(...) inside Polygon class. Use canvas.tag_bind(...) instead:

class Polygon:
    def __init__(self, coords, color, canvas):
        self.coords = coords
        self.color = color
        self.move = False
        self.canvas = canvas

        self.id = canvas.create_polygon(self.coords, fill=self.color)

        canvas.tag_bind(self.id, '<Button-1>', self.start_movement)
        canvas.tag_bind(self.id, '<Motion>', self.movement)
        canvas.tag_bind(self.id, '<ButtonRelease-1>', self.stopMovement)
        canvas.tag_bind(self.id, '<Button-3>', self.rotate)

Note that I have saved the passed canvas to an instance variable self.canvas. You should replace all drawing_place by self.canvas inside other class methods.

Also you don't need to call the following line inside start_movement():

    self.movingimage = drawing_place.find_closest(self.initi_x, self.initi_y,
                                                  halo=1)  # get canvas object
                                                           # ID of where mouse
                                                           # pointer is.

As self.id can be used instead of the result of find_closest() inside movement():

    def movement(self, event):
        if self.move:

            end_x = self.canvas.canvasx(event.x)  # Translate mouse x screen
                                                    # coordinate to canvas coordinate.
            end_y = self.canvas.canvasy(event.y)  # Translate mouse y screen
                                                    # coordinate to canvas coordinate.

            deltax = end_x - self.initi_x  # Find the difference
            deltay = end_y - self.initi_y  # Find the difference

            self.newPosition(deltax, deltay)

            self.initi_x = end_x  # Update previous current with new location
            self.initi_y = end_y
            self.canvas.move(self.id, deltax, deltay)  # Move object
Related