Search code examples
pythontkinter

tkinter canvas.coords() and canvas.moveto() having different coordinates


I tried the following script, in a canvas that fills the window:

import tkinter as tk
from PIL import Image, ImageTk
from time import *

root = tk.Tk()
root.geometry("500x500")
canvas = tk.Canvas(root)
canvas.pack()

def fill(event):
    canvas.config(width = root.winfo_width(), height = root.winfo_height())
root.bind("<Configure>", fill)

img = Image.open("CO2.png")
imgtk = ImageTk.PhotoImage(img)
item = canvas.create_image(15, 15, image = imgtk)
while True:
    canvas.moveto(item, canvas.coords(item)[0], canvas.coords(item)[1])
    root.update()
    sleep(0.5)

This should do nothing, because it's moving an item to where it already is. But in practice, this moves the image towards the positive x and y direction, which means the coordinates from canvas.coords() is slightly off and do not correspond to canvas.moveto().

Here's the used image:

enter image description here

What is happening here?


Solution

  • That behavior is documented and you observing the functionality as it is intended.
    canvas.coords(item) will return where the item currently is.
    canvas.moveto(item, *coord_list) will place the item so that the upper left corner is below and next to the coordinates in *coord_list.

    Compare the documentation of coords with moveto.

    If no coordinates are specified, this command returns a list whose elements are the coordinates of the item named by tagOrId.

    and

    Move the items given by tagOrId in the canvas coordinate space so that the first coordinate pair (the upper-left corner of the bounding box) of the first item (the lowest in the display list) with tag tagOrId is located at position (xPos,yPos)

    This small snippet code demonstrate your experience:

    import tkinter as tk
    
    def test(event):
        cnvs.moveto(rect, *cnvs.coords(rect)[0:-2])
    
    root = tk.Tk()
    cnvs = tk.Canvas(root, highlightthickness=0)
    cnvs.pack()
    rect = cnvs.create_rectangle(0,0,50,50, fill='red')
    print(cnvs.coords(rect))
    root.bind('<1>', test)
    root.mainloop()
    

    while changing the function test to the following will solve the issue:

    def test(event):
        delta = int(float(cnvs.itemcget(rect, 'width'))/2)
        coords = [coordinate-delta for coordinate in cnvs.coords(rect)[0:-2]]
        cnvs.moveto(rect, *coords)
    

    However, you would need to tweak it for different item-types with other coordinate properties as well. The usual way to update coordinates with tk and tkinter is to use canvas.coords(item, *new_coords) anyway.