Search code examples
pythontkinterscrollbarframe

How to access the frame outside the function in python


How can I access the scroll_frame outside the function so that the new_frame will be inside the scroll_frame in the code below.

def scroll():
    container = LabelFrame(root)

    my_canvas = Canvas(container)
    my_canvas.pack(side=LEFT, fill=BOTH, expand=1)

    scroll_y = Scrollbar(container, orient=VERTICAL, command=my_canvas.yview)
    scroll_y.pack(side=RIGHT, fill='y')

    my_canvas.configure(yscrollcommand=scroll_y.set)
    my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion=my_canvas.bbox('all')))

    scroll_frame = Frame(my_canvas)
    my_canvas.create_window((0, 0), window=scroll_frame, anchor=NW)

    container.pack(fill=BOTH, expand=1)


new_frame = Frame(scroll_frame)

for i in range(25):
    Button(new_frame, text='click'+str(i)).pack()

scroll()

root.mainloop()


Solution

  • If you want to access scroll_frame, you should return it from scroll().

    Suggest to return container as well and don't call container.pack(...) inside scroll() because it is more flexible.

    Also suggest to pass the parent to scroll() as well instead of hard-coded root inside it.

    from tkinter import *
    
    def scroll(parent):
        container = LabelFrame(parent)
    
        my_canvas = Canvas(container, highlightthickness=0)
        my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
    
        scroll_y = Scrollbar(container, orient=VERTICAL, command=my_canvas.yview)
        scroll_y.pack(side=RIGHT, fill=Y)
    
        my_canvas.configure(yscrollcommand=scroll_y.set)
    
        scroll_frame = Frame(my_canvas)
        my_canvas.create_window((0, 0), window=scroll_frame, anchor=NW)
    
        # bind <Configure> on scroll_frame instead of my_canvas
        scroll_frame.bind('<Configure>', lambda e: my_canvas.configure(scrollregion=my_canvas.bbox('all')))
    
        return container, scroll_frame
    
    root = Tk()
    
    container, scroll_frame = scroll(root)
    container.pack(fill=BOTH, expand=1)
    
    # Is new_frame necessary? Why not use scroll_frame directly?
    new_frame = Frame(scroll_frame)
    new_frame.pack()
    
    for i in range(25):
        Button(new_frame, text='click'+str(i)).pack(fill=X)
    
    root.mainloop()
    

    Note that if you want to use scroll() repeatedly, better use class instead of function.


    Update: using class implemenatation:

    import tkinter as tk
    
    class ScrollableFrame(tk.LabelFrame):
        def __init__(self, parent, **kw):
            super().__init__(parent, **kw)
    
            canvas = tk.Canvas(self, highlightthickness=0)
            canvas.pack(side="left", fill="both", expand=1)
    
            vscrollbar = tk.Scrollbar(self, orient="vertical", command=canvas.yview)
            vscrollbar.pack(side="right", fill="y")
            canvas.config(yscrollcommand=vscrollbar.set)
    
            self.scroll_frame = tk.Frame(canvas)
            self.scroll_frame.bind("<Configure>", lambda e: canvas.config(scrollregion=canvas.bbox("all")))
            canvas.create_window(0, 0, window=self.scroll_frame, anchor="nw")
    
    if __name__ == "__main__":
        root = tk.Tk()
        container = ScrollableFrame(root)
        container.pack(fill="both", expand=1)
        for i in range(25):
            tk.Button(container.scroll_frame, text=f"click {i}").pack(fill="x")
        root.mainloop()