I'm building a GUI application using tkinter, and the app needs a CAD interface for modelling so I used the tkinter canvas widget for that.
I implemented methods to move and zoom the canvas with a mouse based on this thread. The problem I'm having is that zooming the canvas changes the coordinates of objects on it (sometimes the coordinates change from positive to negative). So if I plot rectangle on the canvas using;
self.canvas.create_rectangle(0, 5, 75, 100, outline="black", fill="blue")
self.canvas.create_text(100,125, anchor="nw", text="Click and drag to move the canvas\nScroll to zoom.")
Then zoom or move and zoom the canvas, and plot another rectangle using;
self.canvas.create_rectangle(75, 5, 200, 100, outline="black", fill="red")
I expected to see something like this;
But I get this instead;
Is there anyway to fix this in tkinter?
EDIT
I've updated my code with Bryan's answer. I was able to track the scale factor in the self.scale
attribute but the offset (tracked in the self.offset
attribute) is still wrong. I'm not able to calculate the cumulative offset from repeated zooming. New rectangles scale properly (size wise) but the offset/location is still off.
Here is my current code;
import Tkinter as tk
class CAD(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.scale = 1
self.offset = [0, 0]
self.current_scale = [self.offset[0], self.offset[1], self.scale, self.scale]
self.canvas = tk.Canvas(self, width=400, height=350, background="bisque")
self.canvas.pack()
#Plot on the canvas
self.rect = self.canvas.create_rectangle(0, 5, 75, 100, outline="black", fill="blue")
self.canvas.create_text(100,125, anchor="nw", text="""
Left click and drag to move the canvas
Scroll to zoom
Right click to draw rectangle""")
# Mouse bindings to the canvas
self.canvas.bind("<ButtonPress-1>", self.move_start)
self.canvas.bind("<B1-Motion>", self.move_move)
self.bind_all("<MouseWheel>", self.zoom)
self.canvas.bind("<ButtonPress-3>", self.draw)
# move
def move_start(self, event):
self.canvas.scan_mark(event.x, event.y)
def move_move(self, event):
self.canvas.scan_dragto(event.x, event.y, gain=1)
# zoom
def zoom(self, event):
true_x = self.canvas.canvasx(event.x)
true_y = self.canvas.canvasy(event.y)
if (event.delta > 0):
sc = 1.1
elif (event.delta < 0):
sc = 0.9
self.canvas.scale("all", true_x, true_y, sc, sc)
self.scale *= sc
self.offset = [sum(x) for x in zip(self.offset, [true_x, true_y])]
self.current_scale = [self.offset[0], self.offset[1], self.scale, self.scale]
def draw(self, event):
new_item = self.canvas.create_rectangle(75, 5, 200, 100, outline="black", fill="red")
self.canvas.scale(new_item, *self.current_scale)
if __name__ == "__main__":
root = tk.Tk()
CAD(root).pack(fill="both", expand=True)
root.mainloop()
END EDIT