Tkinter Center Item on Canvas once moved

Viewed 86

I am working on a app which loads floor plans into a image viewer so they can be zoomed for electrical workers to evaluate. I am having a slight issue with centering the image.

I have 2 questions I would love help with.

  1. I have made a reset button which when loaded and clicked centers the image. This works as I would like. However, once the image is moved on the canvas and the reset button is pressed the image instantly slides to the side and doesn't center anymore. So firstly how would I overcome this? I have tried messing with canvas.move, canvas.moveto and canvas.coords with no success.

  2. The second issue is how would I center the image when the app is first loaded. I tried adding self.canvas.scan_mark and self.canvas.scan_dragto to the class init however this doesnt seem to work. I am guessing because the order of how the GUI is loaded.

Reference image is here:

enter image description here

To replicate open app then press reset button. Image will center then move the canvas anywhere with your mouse randomly and press the reset button again, now the image moves the left hand side and doesn't center anymore.

example of code below. Thanks

import random
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk

class AutoScrollbar(ttk.Scrollbar):
    ''' A scrollbar that hides itself if it's not needed.
        Works only if you use the grid geometry manager '''
    def set(self, lo, hi):
        if float(lo) <= 0.0 and float(hi) >= 1.0:
            self.grid_remove()
        else:
            self.grid()
        ttk.Scrollbar.set(self, lo, hi)

    def pack(self, **kw):
        raise tk.TclError('Cannot use pack with this widget')

    def place(self, **kw):
        raise tk.TclError('Cannot use place with this widget')

class Zoom(ttk.Frame):
    ''' Simple zoom with mouse wheel '''
    def __init__(self, mainframe, path):
        ''' Initialize the main Frame '''
        ttk.Frame.__init__(self, master=mainframe)
        self.master.title('zoom')
        self.master.geometry("1200x800")

        # Vertical and horizontal scrollbars for canvas
        vbar = AutoScrollbar(self.master, orient='vertical')
        hbar = AutoScrollbar(self.master, orient='horizontal')
        vbar.grid(row=0, column=1, sticky='ns')
        hbar.grid(row=1, column=0, sticky='we')
        # Open image
        self.image = Image.open(path)
        # Create canvas and put image on it
        self.canvas = tk.Canvas(self.master, highlightthickness=0,
                                xscrollcommand=hbar.set, yscrollcommand=vbar.set)
        self.button = tk.Button(self.master,text="Reset Canvas to center",command=self.reset_canvas)
        self.button.grid()
        self.canvas.grid(row=0, column=0, sticky='nswe')
        #self.canvas.scan_mark(0, 0)
        #self.canvas.scan_dragto(200, 0, gain=1)

        vbar.configure(command=self.canvas.yview)  # bind scrollbars to the canvas
        hbar.configure(command=self.canvas.xview)
        # Make the canvas expandable
        self.master.rowconfigure(0, weight=1)
        self.master.columnconfigure(0, weight=1)
        # Bind events to the Canvas
        self.canvas.bind('<ButtonPress-1>', self.move_from)
        self.canvas.bind('<B1-Motion>',     self.move_to)
        self.canvas.bind('<MouseWheel>', self.wheel)  # with Windows and MacOS, but not Linux
        self.canvas.bind('<Button-5>',   self.wheel)  # only with Linux, wheel scroll down
        self.canvas.bind('<Button-4>',   self.wheel)  # only with Linux, wheel scroll up
        # Show image and plot some random test rectangles on the canvas
        self.imscale = 1.0
        self.imageid = None
        self.delta = 0.75
        width, height = self.image.size
        minsize, maxsize = 5, 20
        for n in range(10):
            x0 = random.randint(0, width - maxsize)
            y0 = random.randint(0, height - maxsize)
            x1 = x0 + random.randint(minsize, maxsize)
            y1 = y0 + random.randint(minsize, maxsize)
            color = ('red', 'orange', 'yellow', 'green', 'blue')[random.randint(0, 4)]
            self.canvas.create_rectangle(x0, y0, x1, y1, outline='black', fill=color,
                                         activefill='black', tags=n)
        # Text is used to set proper coordinates to the image. You can make it invisible.
        self.text = self.canvas.create_text(0, 0, anchor='nw', text='Scroll to zoom')
        self.show_image()
        self.canvas.configure(scrollregion=self.canvas.bbox('all'))

    def reset_canvas(self):
        print("reset pressed")
        self.canvas.scan_dragto(200, 0,gain=1)

    def move_from(self, event):
        ''' Remember previous coordinates for scrolling with the mouse '''
        self.canvas.scan_mark(event.x, event.y)

    def move_to(self, event):
        ''' Drag (move) canvas to the new position '''
        self.canvas.scan_dragto(event.x, event.y, gain=1)

    def wheel(self, event):
        ''' Zoom with mouse wheel '''
        scale = 1.0
        # Respond to Linux (event.num) or Windows (event.delta) wheel event
        if event.num == 5 or event.delta == -120:
            scale        *= self.delta
            self.imscale *= self.delta
        if event.num == 4 or event.delta == 120:
            scale        /= self.delta
            self.imscale /= self.delta
        # Rescale all canvas objects
        x = self.canvas.canvasx(event.x)
        y = self.canvas.canvasy(event.y)
        self.canvas.scale('all', x, y, scale, scale)
        self.show_image()
        self.canvas.configure(scrollregion=self.canvas.bbox('all'))

    def show_image(self):
        ''' Show image on the Canvas '''
        if self.imageid:
            self.canvas.delete(self.imageid)
            self.imageid = None
            self.canvas.imagetk = None  # delete previous image from the canvas
        width, height = self.image.size
        new_size = int(self.imscale * width), int(self.imscale * height)
        imagetk = ImageTk.PhotoImage(self.image.resize(new_size))
        # Use self.text object to set proper coordinates
        self.imageid = self.canvas.create_image(self.canvas.coords(self.text),
                                                anchor='nw', image=imagetk)
        self.canvas.lower(self.imageid)  # set it into background
        self.canvas.imagetk = imagetk  # keep an extra reference to prevent garbage-collection

path = 'floor_plan.jpg'  # place path to your image here
root = tk.Tk()
app = Zoom(root, path=path)
root.mainloop()
1 Answers

In show_image(self) change width and height to

self.imagewidth, self.imageheight = self.image.size
new_size = int(self.imscale * self.imagewidth), int(self.imscale * self.imageheight)

Then change reset_canvas(self) to

    def reset_canvas(self):
        print("reset pressed")
        w, h = self.canvas.winfo_width()//2, self.canvas.winfo_height()//2
        ww, hh = self.imagewidth//2, self.imageheight//2
        
        self.canvas.scan_dragto(w-ww, h-hh,gain=1)

This will center the image based on canvas size and image size.

Related