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