I've been trying to create a scrollable frame in with Tkinter / ttk using the many answers on here and guides scattered around. The frame is to hold a table which can have rows added and deleted. One issue I'm having that I can't find elsewhere is that if there is space for more content within the frame, I can scroll the contents of the table to the bottom of the frame. When the frame is full, it behaves as expected.
I can't convert to a gif at the moment, so I've added some screenshots to try and illustrate.
Here is my scrollable frame class, it's pretty standard from examples on the web;
class ScrollableFrame(ttk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.canvas = tk.Canvas(self)
self.scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.scrollableFrame = ttk.Frame(self.canvas)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.scrollableFrame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
self.bind("<Enter>", self.bind_to_mousewheel)
self.bind("<Leave>", self.unbind_from_mousewheel)
self.scrollbar.pack(side='right', fill='y')
self.canvas.pack(side='top', expand=0, fill='x')
self.canvas.create_window((0, 0), window=self.scrollableFrame)
def bind_to_mousewheel(self, event):
self.canvas.bind_all("<MouseWheel>", self.on_mousewheel)
def unbind_from_mousewheel(self, event):
self.canvas.unbind_all("<MouseWheel>")
def on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
The table rows are added to the ScrollableFrame.scrollableFrame
widget by the parent GUI, which I figured was necessary to trigger the <Configure>
bound to the ScrollableFrame.canvas
.
Does anyone have any suggestions?
I found an answer thanks to the hint from @acw1668;
I adjusted the <Configure>
command to check the size of the scrollregion
and compare it to the parent ScrollableFrame
class window. If it is smaller, I change the scroll region to be the size of the ScrollableFrame
.
Here are the adjustments I made to my class, including changing the window anchor to sit at the top left of the frame;
class ScrollableFrame(ttk.Frame):
"""
"""
def __init__(self, parent):
"""
"""
...
self.scrollableFrame.bind("<Configure>", self.update_scroll_region)
...
self.canvas.create_window((0, 0), window=self.scrollableFrame, anchor='nw')
def update_scroll_region(self, event):
bbox = self.canvas.bbox('all')
sfHeight = self.winfo_height()
if (bbox[3] - bbox[1]) < sfHeight:
newBbox = (bbox[0], 0, bbox[2], sfHeight)
self.canvas.configure(scrollregion=newBbox)
else:
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
As pointed out, it would be possible and more Pythonic to use bbox[-1] = max(bbox[-1], sfHeight)
, then call self.canvas.configure(scrollregion=bbox)
however I found due to my canvas' placement I had to set the first Y coordinate to 0.