Aug-06-2021, 04:09 PM
(This post was last modified: Aug-06-2021, 07:16 PM by mandiatutti.)
I've been using this framework Multipage Framework for developing an app... I'm kinda new to tkinter and I just finished developing a beta version and I just noticed that frames are not native scrollable. I checked some tutorials online on how to make them scrollable creating canvas inside or using ScrolledFrame package...). So, I tried with the scrolled frame package but it doesn't work right (like a normal app should): basically the content doesn't resize with the window. So is here anyone kind to help me and improve the basic structure of the framework in the link to make frames scrollables? Here's how I managed to create it scrollable but loosing "content-auto-fitting" capabilities:
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
scrollable_frame = ScrollableFrame(container, bg="red")
scrollable_frame.pack(fill="both", expand=True)
scrollable_frame.grid_rowconfigure(0, weight=1)
scrollable_frame.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=scrollable_frame, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
frame.grid_rowconfigure(0, weight=1)
frame.grid_columnconfigure(0, weight=1)
frame.config(highlightbackground="green", highlightcolor="green", highlightthickness=1)
self.show_frame("StartPage")
def show_frame(self, page_name):
#Show a frame for the given page name
frame = self.frames[page_name]
frame.tkraise()
FIT_WIDTH = "fit_width"
FIT_HEIGHT = "fit_height"
class ScrollableFrame(tk.Frame):
"""
There is no way to scroll <tkinter.Frame> so we are
going to create a canvas and place the frame there.
Scrolling the canvas will give the illution of scrolling
the frame
Partly taken from:
https://blog.tecladocode.com/tkinter-scrollable-frames/
https://stackoverflow.com/a/17457843/11106801
master_frame---------------------------------------------------------
| dummy_canvas----------------------------------------- y_scroll-- |
| | self--------------------------------------------- | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | ------------------------------------------------ | | | |
| ---------------------------------------------------- | | |
| | | |
| x_scroll--------------------------------------------- | | |
| | | | | |
| ---------------------------------------------------- --------- |
--------------------------------------------------------------------
"""
def __init__(self, master=None, scroll_speed=2,
hscroll=False, vscroll=True, **kwargs):
assert isinstance(scroll_speed, int), "`scroll_speed` must be an int"
self.scroll_speed = scroll_speed
self.master_frame = tk.Frame(master)
self.dummy_canvas = tk.Canvas(self.master_frame, **kwargs)
super().__init__(self.dummy_canvas)
# Create the 2 scrollbars
if vscroll:
self.v_scrollbar = tk.Scrollbar(self.master_frame,
orient="vertical",
command=self.dummy_canvas.yview)
self.v_scrollbar.pack(side="right", fill="y")
self.dummy_canvas.configure(yscrollcommand=self.v_scrollbar.set)
if hscroll:
self.h_scrollbar = tk.Scrollbar(self.master_frame,
orient="horizontal",
command=self.dummy_canvas.xview)
self.h_scrollbar.pack(side="bottom", fill="x")
self.dummy_canvas.configure(xscrollcommand=self.h_scrollbar.set)
# Bind to the mousewheel scrolling
self.dummy_canvas.bind_all("<MouseWheel>", self.scrolling_windows,
add=True)
self.dummy_canvas.bind_all("<Button-4>", self.scrolling_linux, add=True)
self.dummy_canvas.bind_all("<Button-5>", self.scrolling_linux, add=True)
self.bind("<Configure>", self.scrollbar_scrolling, add=True)
# Place `self` inside `dummy_canvas`
self.dummy_canvas.create_window((0, 0), window=self, anchor="nw")
# Place `dummy_canvas` inside `master_frame`
self.dummy_canvas.pack(side="top", expand=True, fill="both")
self.pack = self.master_frame.pack
self.grid = self.master_frame.grid
self.place = self.master_frame.place
self.pack_forget = self.master_frame.pack_forget
self.grid_forget = self.master_frame.grid_forget
self.place_forget = self.master_frame.place_forget
def scrolling_windows(self, event):
assert event.delta != 0, "On Windows, `event.delta` should never be 0"
y_steps = int(-event.delta/abs(event.delta)*self.scroll_speed)
self.dummy_canvas.yview_scroll(y_steps, "units")
def scrolling_linux(self, event):
y_steps = self.scroll_speed
if event.num == 4:
y_steps *= -1
self.dummy_canvas.yview_scroll(y_steps, "units")
def scrollbar_scrolling(self, event):
region = list(self.dummy_canvas.bbox("all"))
region[2] = max(self.dummy_canvas.winfo_width(), region[2])
region[3] = max(self.dummy_canvas.winfo_height(), region[3])
self.dummy_canvas.configure(scrollregion=region)
def resize(self, fit=None, height=None, width=None):
if fit == FIT_WIDTH:
super().update()
self.dummy_canvas.config(width=super().winfo_width())
if fit == FIT_HEIGHT:
super().update()
self.dummy_canvas.config(height=super().winfo_height())
if height is not None:
self.dummy_canvas.config(height=height)
if width is not None:
self.dummy_canvas.config(width=width)
fit = resize
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
for i in range(0,99):
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
