0

Im trying to implement a zoomable and scrollable canvas (from Tkinter canvas zoom + move/pan) into my existing ttkbootstrap application. When zooming outwards the program sometimes results in a bad screen distance error when the scrollregion is connfigured. I found out that this error only occurs when ttkbootstrap is imported, even if not used once in the program. The error dosnt occur when the line self.canvas.configure(scrollregion=bbox) is commented out. Has anyone encountered a similar problem and has an idea how to fix it?

This is the code that reproduces the error when ttkbootstrap is imported:

import tkinter as tk
from tkinter import ttk
#import ttkbootstrap as tb           # uncomment and scroll out to see error
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)

class Zoom_Advanced(tk.Tk):
    def __init__(self, path):
        super().__init__()
        self.title('Zoom with mouse wheel')

        #self.image = Image.open(path)  # open image
        self.image = Image.new('RGB', (200, 200))     # EDIT
        self.width, self.height = self.image.size
        self.imscale = 1.0  # scale for the canvas image
        self.delta = 1.3  # zoom magnitude

        self.setup_scrollbars()
        self.setup_canvas()
        self.bind_events()
        self.show_image()

    def setup_scrollbars(self):
        vbar = AutoScrollbar(self, orient='vertical')
        hbar = AutoScrollbar(self, orient='horizontal')
        vbar.grid(row=0, column=1, sticky='ns')
        hbar.grid(row=1, column=0, sticky='we')

        self.canvas = tk.Canvas(self, highlightthickness=0,
                                xscrollcommand=hbar.set, yscrollcommand=vbar.set)
        self.canvas.grid(row=0, column=0, sticky='nswe')
        self.canvas.update()

        vbar.configure(command=self.scroll_y)
        hbar.configure(command=self.scroll_x)

        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

    def setup_canvas(self):
        self.container = self.canvas.create_rectangle(0, 0, self.width, self.height, outline="red", width=2)

    def bind_events(self):
        self.canvas.bind('<Configure>', self.show_image)
        self.canvas.bind('<ButtonPress-1>', self.move_from)
        self.canvas.bind('<B1-Motion>', self.move_to)
        self.canvas.bind('<MouseWheel>', self.wheel)
        self.canvas.bind('<Button-5>', self.wheel)
        self.canvas.bind('<Button-4>', self.wheel)

    def scroll_y(self, *args, **kwargs):
        self.canvas.yview(*args, **kwargs)
        self.show_image()

    def scroll_x(self, *args, **kwargs):
        self.canvas.xview(*args, **kwargs)
        self.show_image()

    def move_from(self, event):
        self.canvas.scan_mark(event.x, event.y)

    def move_to(self, event):
        self.canvas.scan_dragto(event.x, event.y, gain=1)
        self.show_image()

    def wheel(self, event):
        x = self.canvas.canvasx(event.x)
        y = self.canvas.canvasy(event.y)
        bbox = self.canvas.bbox(self.container)
        if bbox[0] < x < bbox[2] and bbox[1] < y < bbox[3]:
            pass
        else:
            return

        scale = 1.0
        if event.num == 5 or event.delta == -120:
            i = min(self.width, self.height)
            if int(i * self.imscale) < 30:
                return
            self.imscale /= self.delta
            scale /= self.delta
        if event.num == 4 or event.delta == 120:
            i = min(self.canvas.winfo_width(), self.canvas.winfo_height())
            if i < self.imscale:
                return
            self.imscale *= self.delta
            scale *= self.delta

        self.canvas.scale('all', x, y, scale, scale)
        self.show_image()

    def show_image(self, event=None):
        bbox1 = self.canvas.bbox(self.container)
        bbox1 = (bbox1[0] + 1, bbox1[1] + 1, bbox1[2] - 1, bbox1[3] - 1)
        bbox2 = (self.canvas.canvasx(0),
                 self.canvas.canvasy(0),
                 self.canvas.canvasx(self.canvas.winfo_width()),
                 self.canvas.canvasy(self.canvas.winfo_height()))
        bbox = [
            min(bbox1[0], bbox2[0]),
            min(bbox1[1], bbox2[1]),
            max(bbox1[2], bbox2[2]),
            max(bbox1[3], bbox2[3])
        ]

        if bbox[0] == bbox2[0] and bbox[2] == bbox2[2]:
            bbox[0] = bbox1[0]
            bbox[2] = bbox1[2]
        if bbox[1] == bbox2[1] and bbox[3] == bbox2[3]:
            bbox[1] = bbox1[1]
            bbox[3] = bbox1[3]

        self.canvas.configure(scrollregion=bbox)      # !! error occurs here !!
        x1 = max(bbox2[0] - bbox1[0], 0)
        y1 = max(bbox2[1] - bbox1[1], 0)
        x2 = min(bbox2[2], bbox1[2]) - bbox1[0]
        y2 = min(bbox2[3], bbox1[3]) - bbox1[1]

        if int(x2 - x1) > 0 and int(y2 - y1) > 0:
            x = min(int(x2 / self.imscale), self.width)
            y = min(int(y2 / self.imscale), self.height)
            image = self.image.crop((int(x1 / self.imscale), int(y1 / self.imscale), x, y))
            imagetk = ImageTk.PhotoImage(image.resize((int(x2 - x1), int(y2 - y1))))
            imageid = self.canvas.create_image(
                max(bbox2[0], bbox1[0]),
                max(bbox2[1], bbox1[1]),
                anchor='nw',
                image=imagetk
            )
            self.canvas.lower(imageid)
            self.canvas.imagetk = imagetk

path = "image/path"
app = Zoom_Advanced(path=path)
app.mainloop()

Thanks for any suggestions

Edit: The Error traceback is the following

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\ole-b\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1921, in __call__
    return self.func(*args)
  File "C:\Users\ole-b\OneDrive\PCB dim tool\zoom.py", line 110, in wheel
    self.show_image()
  File "C:\Users\ole-b\OneDrive\PCB dim tool\zoom.py", line 133, in show_image
    self.canvas.configure(scrollregion=bbox)
  File "C:\Users\ole-b\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1675, in configure
    return self._configure('configure', cnf, kw)
  File "C:\Users\ole-b\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1665, in _configure
    self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: bad screen distance "378.0"

In case the specific Image is responsible i changed it to an empty image. I also added an outline to the rectangle container. When zooming outwards the container looks as expected but the image doesnt fill the container. (This however could be a result of the error occurring in the first place)

The error only seems to happen if only one of two opposing lines of the container would result outside of the visible area (eg. the left line but not the right one or the top one but not the bottom one).

Ole Bleß
  • 1
  • 2
  • 1
    Cannot reproduce the issue even `ttkbootstrap` is imported. It is better to post the full error traceback. – acw1668 Jul 11 '23 at 10:03
  • It worked by using ttkbootstrap. – toyota Supra Jul 11 '23 at 10:52
  • `self.canvas.configure` is Ok. Nothing wrong. – toyota Supra Jul 11 '23 at 11:17
  • Thanks for your responses. Were you able to zoom out in such a way, that only one of each of the two opposing lines of the outline is outside of the visible area? I changed the code to have a dummy image, which for me results in an error if i scroll out once. – Ole Bleß Jul 11 '23 at 12:16
  • Yes, everything is working when resizing. Both scrollbars visible. Btw. I'm using win10 Python 3.12.0b3 – toyota Supra Jul 11 '23 at 12:38
  • Hmm i was using python 3.9 but tried it with python 3.12.0b3 aswell now. Im also using windows 10 and reinstalled ttkbootrap. The problem is still there. I also tried it on my surface tablet and my pc at home... To be sure, did you use the mousewheel to scroll out or did you just resize the screen? Thank you – Ole Bleß Jul 11 '23 at 13:32
  • Still cannot reproduce the issue with or without importing `ttkbootstrap` and zoom out with only one of the opposite sides out of visible area. I am using Windows 11 with Python 3.11.4. – acw1668 Jul 11 '23 at 15:35
  • @Ole Bleß. The mousewheel worked fine. No issue so far. – toyota Supra Jul 11 '23 at 17:36
  • Okay thank you all. Now i really dont know what to do next.. also tried it with in a new virtual conda environment and still get the error. Is the bad screen distance error somehow related to my system settings? (The two PCs of mine do use the same windows account) – Ole Bleß Jul 12 '23 at 12:54
  • Try casting the values of `bbox2` to integers. – acw1668 Jul 13 '23 at 02:13
  • @acw1668 That worked! Thank you so much, didnt think of that. – Ole Bleß Jul 13 '23 at 15:14

0 Answers0