Python tkinter: Class for a scrollable frame, the widget inside the scrollable frame does not expand using sticky='nsew', why?

60 views Asked by At

I wrote the folliwing class (reading some examples on the web), it works almost perfectly, except for the fact that the widgets placed inside the scrollable Frame (child of Frame) does not expand when specifying the option sticky='nsew' in the grid() method. Why?

Is there any other simpler solution to my need ?

''' lib scrollable frame '''
from tkinter import Canvas
from tkinter.ttk import Frame, Scrollbar

# ----------------------------------------------------------------------------------
# This Class creates a Scrollable Frame to be used as a normal Frame
# It is only important to use the command scrollFrame.scrollable_frame for the
# Widgets contained inside it
# ----------------------------------------------------------------------------------
class ScrollableFrame(Frame):
    ''' class scrollable frame '''
    def __init__(self, container, *args, **kwargs):
        super().__init__(container, *args, **kwargs)
        self.canvas = Canvas(self)
        self.scrollbar = Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = Frame(self.canvas)
        self.scrollable_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
        # "create_window" method is a geometry manager like grid or pack, specific for canvas
        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        self.canvas.grid(row=0, column=0, sticky='nsew')
        self.scrollbar.grid(row=0, column=1, sticky='nsew')
        # Connect the scroll to all sub widget
        self.attach_scroll_to_widget_and_children(self)

    def attach_scroll_to_widget_and_children(self, widget):
        ''' disable a widget and all it's children '''
        for child in widget.winfo_children():
            child.bind("<MouseWheel>", self.on_mouse_wheel)
            self.attach_scroll_to_widget_and_children(child)

    def on_mouse_wheel(self, event):
        ''' Scroll the canvas '''
        self.canvas.yview("scroll", int(-event.delta/100), "units")

if __name__ == '__main__':
    from tkinter import Tk
    from tkinter.ttk import Label

    root = Tk()
    root.geometry('1100x800+100+100')
    root.columnconfigure(0, weight=1)
    root.rowconfigure(0, weight=1)

    scroll_frame = ScrollableFrame(root)
    scroll_frame.grid(row=0, column=0, sticky='nsew')
    scroll_frame.columnconfigure(0, weight=1)
    scroll_frame.rowconfigure(0, weight=1)

    labelList = []
    for i in range(100):
        label = (Label(scroll_frame.scrollable_frame, text='label'+str(i), background='green'))
        label.grid(row=i, column=0, padx=10, pady=10, sticky='nsew')
        labelList.append(label)

    root.mainloop()

I tried to specify rowconfigure(0, weight=1) and columnconfigure for all widgets inside the class, but it does not work.

1

There are 1 answers

4
acw1668 On

Since you did not specify the width and height of the internal frame self.scrollable_frame, so it will be resized to just fit all its children by default.

You can change the width of the frame to the same as that of the canvas whenever the canvas is resized and call scrollable_frame.columnconfigure(0, weight=1) to allocate all the horizontal available space to widget in column 0, then those labels will be expand to fill the frame horizontally.

class ScrollableFrame(Frame):
    def __init__(self, container, *args, **kwargs):
        ...
        # allocate all horizontal available space to column 0
        self.scrollable_frame.columnconfigure(0, weight=1)
        # "create_window" method is a geometry manager like grid or pack, specific for canvas
        self.canvas.create_window((0, 0), window=self.scrollable_frame,
                                  anchor="nw", tag="frame") ### added tag="frame"
        # resize internal frame when canvas is resized
        self.canvas.bind("<Configure>", lambda e: self.canvas.itemconfig("frame", width=e.width))
        ...

Result:

enter image description here