2

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;

Expected output image

But I get this instead;

Actual output

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

Community
  • 1
  • 1
Khristos
  • 973
  • 2
  • 11
  • 22

1 Answers1

3

One way to state your problem is this:

  • I created an object
  • I applied a transformation on the object
  • I created a new object without the transformation
  • the objects are not aligned as I expect

To get the objects to align you need to transform the coordinates of any new items based on the current set of transformations.

Assuming you know what the current scale factor is, you can just apply the scaling to any new items you create.

For example, the following code yields a result like you describe in the question, where the two rectangles are aligned even though the second one was created after the first one was scaled:

import tkinter as tk

root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=400, background="bisque")
canvas.pack(fill="both", expand=True)

# start with one rectangle
canvas.create_rectangle(0, 5, 75, 100, outline="black", fill="blue")

# scale it, and save the scale
current_scale = (0, 0, 1.5, 1.5)
canvas.scale("all", *current_scale)

# add a new item, and apply the same scale to it
new_item = canvas.create_rectangle(75, 5, 200, 100, outline="black", fill="red")
canvas.scale(new_item, *current_scale)

root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • I have updated the question with your answer and the current code, but still have problems with calculating the cumulative offset. – Khristos Jan 04 '17 at 01:38