Importing ttkbootstrap leads to bad screen distance error when setting up scrollregion

160 Views Asked by At

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).

0

There are 0 best solutions below