How to make a Scrollable Frame in Tkinter

Why would anyone want to make a Scrollable Frame in Tkinter? Let’s briefly discuss some benefits and situations in which something like this would be useful.

  1. It allows you to display more content than would fit within the dimensions of the frame, without having to make the frame larger and potentially clutter up the user interface.
  2. It allows the user to easily view the full content of the frame by using the scrollbars, without having to manually resize the frame or use other workarounds.

So basically if you are spaced constrained, and have alot of widgets to be showing, then making a Scrollable Frame is a good idea.


Create a Scrollable Frame

Here we have our VerticalScrolledFrame Class. As you can see, our approach has been to create a separate Class (just like the widgets that we use), which we can reuse over and over again. In-fact, you can just copy paste this into your own program and use it like a regular frame!

class VerticalScrolledFrame(ttk.Frame):
    def __init__(self, parent, *args, **kw):
        ttk.Frame.__init__(self, parent, *args, **kw)

        # Create a canvas object and a vertical scrollbar for scrolling it.
        vscrollbar = ttk.Scrollbar(self, orient=VERTICAL)
        vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
        self.canvas = tk.Canvas(self, bd=0, highlightthickness=0, 
                                width = 200, height = 300,
                                yscrollcommand=vscrollbar.set)
        self.canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
        vscrollbar.config(command = self.canvas.yview)


        # Create a frame inside the canvas which will be scrolled with it.
        self.interior = ttk.Frame(self.canvas)
        self.interior.bind('<Configure>', self._configure_interior)
        self.interior_id = self.canvas.create_window(0, 0, window=self.interior, anchor=NW)
        self.canvas.bind('<Configure>', self._configure_canvas)

Let’s do a quick breakdown of what is in the above code.

  1. We initialized our Class as a Frame.
  2. Created a Scrollbar and a Canvas (and connected both of them). Creating a Canvas is necessary for the scrolling feature, as a Frame cannot do this natively.
  3. We created another frame inside this Class, called “interior”, which we also added to the Canvas using the create_window() method. This is the actual frame in which we will be placing our widgets.
  4. We binded this “interior” frame and the “canvas” to the “Configure” event, which triggers whenever the window is resized. The functions called by the “Configure” event will be defined by us in the next step of this tutorial.

Two important things to note here.

First of all, the “Configure” event is being used here in the event that window is resized. Because by default, if you create a Frame inside a Canvas, and then resize the Canvas, the Frame will not resize along with it. (This rule applies to any object drawn inside a canvas, so we need to do this part manually).

If you do not want your Frame/Window to be resizable (e.g. you have disabled resizing on it), then you can probably just skip this step.

Secondly, you need to remember we are placing widgets inside the interior frame. So when we create an object for the VerticalScrolledFrame Class, with the name “scrolledframe” for example, instead of this:

frame = VerticalScrolledFrame(root)
button = tk.Button(frame, text="Click Me")

we need to do this:

frame = VerticalScrolledFrame(root)
button = tk.Button(frame.interior, text="Click Me")

Configuring the Frame

Here is the function which resizes the frame whenever the window/frame is resized. We basically update the frame to the size of the Canvas (because in reality, the canvas is the one being resized when we drag the window, not the frame).

    def _configure_interior(self, event):
        # Update the scrollbars to match the size of the inner frame.
        size = (self.interior.winfo_reqwidth(), self.interior.winfo_reqheight())
        self.canvas.config(scrollregion=(0, 0, size[0], size[1]))
        if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
            # Update the canvas's width to fit the inner frame.
            self.canvas.config(width = self.interior.winfo_reqwidth())

Here we update the canvas too.

    def _configure_canvas(self, event):
        if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
            # Update the inner frame's width to fill the canvas.
            self.canvas.itemconfigure(self.interior_id, width=self.canvas.winfo_width())

Here is the full working code that you can try out!

import tkinter as tk
import tkinter.ttk as ttk
from tkinter.constants import *

class VerticalScrolledFrame(ttk.Frame):
    def __init__(self, parent, *args, **kw):
        ttk.Frame.__init__(self, parent, *args, **kw)

        # Create a canvas object and a vertical scrollbar for scrolling it.
        vscrollbar = ttk.Scrollbar(self, orient=VERTICAL)
        vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
        self.canvas = tk.Canvas(self, bd=0, highlightthickness=0, 
                                width = 200, height = 300,
                                yscrollcommand=vscrollbar.set)
        self.canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
        vscrollbar.config(command = self.canvas.yview)

        # Reset the view
        self.canvas.xview_moveto(0)
        self.canvas.yview_moveto(0)

        # Create a frame inside the canvas which will be scrolled with it.
        self.interior = ttk.Frame(self.canvas)
        self.interior.bind('<Configure>', self._configure_interior)
        self.canvas.bind('<Configure>', self._configure_canvas)
        self.interior_id = self.canvas.create_window(0, 0, window=self.interior, anchor=NW)


    def _configure_interior(self, event):
        # Update the scrollbars to match the size of the inner frame.
        size = (self.interior.winfo_reqwidth(), self.interior.winfo_reqheight())
        self.canvas.config(scrollregion=(0, 0, size[0], size[1]))
        if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
            # Update the canvas's width to fit the inner frame.
            self.canvas.config(width = self.interior.winfo_reqwidth())
        
    def _configure_canvas(self, event):
        if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
            # Update the inner frame's width to fill the canvas.
            self.canvas.itemconfigure(self.interior_id, width=self.canvas.winfo_width())
        

class Window():
    def __init__(self, master, *args, **kwargs):
        self.frame = VerticalScrolledFrame(master)
        self.frame.pack(expand = True, fill = tk.BOTH)
        self.label = ttk.Label(master, text="Shrink the window to activate the scrollbar.")
        self.label.pack()

        for i in range(10):
            ttk.Button(self.frame.interior, text=f"Button {i}").pack(padx=10, pady=5)

root = tk.Tk()
window = Window(root)
root.mainloop()
How to make a Scrollable Frame in Tkinter

This marks the end of the How to make a Scrollable Frame in Tkinter Tutorial. Any suggestions or contributions for CodersLegacy are more than welcome. Questions about the tutorial content can be asked in the comments section below.

7 thoughts on “How to make a Scrollable Frame in Tkinter”

  1. The frame works great but I don’t know how to adjust the “visible size” in the vertical axis (i.e. the size of the part of the frame that one can see). I tried with the pack() method and the “fill = Y” argument but the frame still has a fixed length in the vertical direction. Thanks!

    Reply
  2. How can I proceed if I want to use another frame inside which uses grid instead of pack? If I do that, I just don’t see anything.

    Reply
    • Hello,
      you can use the ‘interior’ frame. I wanted to have a table (frame with grid). This is the way it works for me:
         vsf = VerticalScrolledFrame(root)      
         vsf.pack(expand = True, fill = tk.BOTH)  
         table = vsf.interior 
      Now you can add widgets to ‘table’ by grid().
      I was happy to find this solution, because I can use ‘table.columnconfigure()’ with this!

      Reply
  3. print(“Thanks a lot!”)

    def thanks(name):
    print(f”Thanks, {name}! You’re awesome!”)

    thanks(“CodersLegacy”)

    # Feeling extra grateful? Repeat the message:
    print(“THX” * 5)

    Reply
  4. Thanks for that – perfect

    This code handles grid:

    
    class VerticalScrolledFrame(ttk.Frame):
        def __init__(self, parent, *args, **kw):
            ttk.Frame.__init__(self, parent, *args, **kw)
            self.rowconfigure(0, weight=1)
            self.columnconfigure(0, weight=1)
    
    
            vscrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL)
            vscrollbar.grid(row=0, column=1, sticky=tk.NS)
    
    
            self.canvas = tk.Canvas(
                self,
                bd=0,
                width=200, height=300,
                yscrollcommand=vscrollbar.set)
            self.canvas.grid(row=0, column=0, sticky=tk.NSEW)
            vscrollbar.config(command=self.canvas.yview)
    
    
            # Reset the view
            self.canvas.xview_moveto(0)
            self.canvas.yview_moveto(0)
    
    
            self.interior = ttk.Frame(self.canvas)
            self.interior.bind('<Configure>', self._configure_interior)
            self.canvas.bind('<Configure>', self._configure_canvas)
            self.interior_id = self.canvas.create_window(
                0, 0, window=self.interior, anchor=tk.NW)
    
    
        def _configure_interior(self, event):
            # Update the scroll bars to match the size of the inner frame.
            size = (self.interior.winfo_reqwidth(),
                    self.interior.winfo_reqheight())
            self.canvas.config(scrollregion=(0, 0, size[0], size[1]))
            if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
                # Update the canvas's width to fit the inner frame.
                self.canvas.config(width=self.interior.winfo_reqwidth())
    
    
        def _configure_canvas(self, event):
            if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
                # Update the inner frame's width to fill the canvas.
                self.canvas.itemconfigure(
                    self.interior_id, width=self.canvas.winfo_width())
    
    

    To use:

        def _main_frame(self, master: tk.Frame) -> ttk.Frame:
            frame = VerticalScrolledFrame(master)
            frame.grid(row=0, column=0, sticky=tk.NS)
    
            for row, colour in enumerate(colours):
    
                label = ttk.Label(frame.interior, text=colour.name)
                label.grid(row=row, column=0, sticky=tk.W)
    
            return frame
    
    Reply

Leave a Comment