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