Mastering the ScrolledFrame in ttkbootstrap


Learn how to create scrollable containers for your widgets without the complexity of manual Canvas and Scrollbar linking.


One of the biggest challenges in standard Tkinter is creating a scrollable area. Normally, you would have to embed a Frame inside a Canvas and write custom logic to update the scroll region.

The ttkbootstrap.scrolled module provides the ScrolledFrame, a high-level widget that handles all of this automatically. It is perfect for settings pages, long forms, or dashboards where the content exceeds the window size.

import ttkbootstrap as ttk
from ttkbootstrap.scrolled import ScrolledFrame

root = ttk.Window(themename="cosmo")
root.title("plus2net ScrolledFrame Tutorial")
root.geometry("400x300")

# 1. Create the ScrolledFrame
sf = ScrolledFrame(root, autohide=True)
sf.pack(fill="both", expand=True, padx=10, pady=10)

# 2. Add widgets to the ScrolledFrame (not the root!)
for i in range(1, 21):
    btn = ttk.Button(sf, text=f"Item {i}")
    btn.pack(fill="x", pady=5)

root.mainloop()

Key Features of ScrolledFrame

The ScrolledFrame widget is designed to solve the common layout limitations of standard Tkinter. Here are the professional features that make it essential for modern GUIs:

  • Automatic Scrollbars: By using the autohide=True parameter, scrollbars only appear when the content exceeds the visible area, keeping your interface clean.
  • Built-in Mousewheel Support: Unlike standard Tkinter frames, the ScrolledFrame comes with pre-configured event bindings for mousewheel scrolling on Windows, macOS, and Linux.
  • Flexible Padding: It supports internal padding, ensuring your scrollable content doesn't "touch" the edges of the scrollbar for a more breathable, modern look.
  • Simplified Hierarchy: It acts as a single container. You simply pack or grid your widgets directly into the ScrolledFrame object instead of managing a complex Canvas/Frame stack.
Developer Note: Always remember to set the fill and expand parameters to True when packing the ScrolledFrame to ensure it responds to window resizing.

Practical Use Case: Dynamic Row Management

In real-world applications, you often need to add items to a list dynamically—such as adding tasks to a To-Do list or rows to a data entry form. The ScrolledFrame is perfect for this because it re-calculates the scrollable area every time a new widget is added.

import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from ttkbootstrap.scrolled import ScrolledFrame

def delete_row(frame_to_remove):
    # 1. Destroy the widget
    frame_to_remove.destroy()
    # 2. Force the ScrolledFrame to refresh its scrollable area
    sf.update_idletasks()

def add_row():
    row_frame = ttk.Frame(sf)
    row_frame.pack(fill="x", pady=2)
    
    lbl = ttk.Label(row_frame, text=f"User Record #{var_count.get()}")
    lbl.pack(side=LEFT, padx=10)
    
    # Pass the frame itself into the delete function
    btn_del = ttk.Button(
        row_frame, 
        text="Delete", 
        bootstyle="danger-outline", 
        command=lambda f=row_frame: delete_row(f)
    )
    btn_del.pack(side=RIGHT, padx=20)
    
    var_count.set(var_count.get() + 1)

root = ttk.Window(themename="flatly")
root.title("plus2net Dynamic ScrolledFrame")
root.geometry("500x400")

var_count = ttk.IntVar(value=1)

# Control Frame at the top
top_bar = ttk.Frame(root, padding=10)
top_bar.pack(fill="x")

ttk.Button(top_bar, text="Add New User", command=add_row, bootstyle="success").pack()

# ScrolledFrame for the list content
sf = ScrolledFrame(root, autohide=True)
sf.pack(fill="both", expand=True, padx=10, pady=10)

root.mainloop()
Authority Note: Notice how row_frame.destroy() works seamlessly. When a widget is removed, the ScrolledFrame immediately updates the scrollbar position to reflect the new total height.

Troubleshooting: Scrollbar Not Updating on Delete?

When building interactive applications, you often need to remove items from a list. While standard Tkinter allows you to destroy a widget, the ScrolledFrame needs to be explicitly notified that its internal "height" has changed so it can adjust the scrollbar accordingly.

The following function ensures that when a row is deleted, the scrollbar updates its length and position instantly, without waiting for the user to move the mouse.

Understanding the Logic

  • destroy(): Removes the row frame and all widgets inside it (labels, buttons, etc.) from the memory and the screen.
  • update_idletasks(): Tells Python to pause for a microsecond and finish the "task" of deleting the widget before moving to the next line of code.
  • sf.container.event_generate("<Configure>"): This is the "secret sauce." It manually triggers the internal resize logic of the ScrolledFrame, forcing the scrollbar to recalculate its size based on the new, shorter list of items.
def delete_row(frame_to_remove):
    # 1. Destroy the widget
    frame_to_remove.destroy()
    
    # 2. Force the window to process the destruction
    sf.update_idletasks()
    
    # 3. Trigger a configuration event on the internal container
    # This is the "Pro" way to force a scrollbar refresh
    sf.container.event_generate("<Configure>")
Full code is here
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from ttkbootstrap.scrolled import ScrolledFrame

def delete_row(frame_to_remove):
    # 1. Destroy the widget
    frame_to_remove.destroy()
    # 2. Force the window to process the destruction
    sf.update_idletasks()
    # 3. Trigger a configuration event to refresh scrollbar
    sf.container.event_generate("<Configure>")

def add_row():
    row_id = var_count.get()
    row_frame = ttk.Frame(sf)
    row_frame.pack(fill="x", pady=5)
    
    lbl = ttk.Label(row_frame, text=f"User Record #{row_id}")
    lbl.pack(side=LEFT, padx=10)
    
    btn_del = ttk.Button(
        row_frame, text="Delete", 
        bootstyle="danger-outline", 
        command=lambda f=row_frame: delete_row(f)
    )
    btn_del.pack(side=RIGHT, padx=20)
    var_count.set(row_id + 1)

root = ttk.Window(themename="flatly")
root.title("plus2net Dynamic ScrolledFrame")
root.geometry("500x400")

var_count = ttk.IntVar(value=1)

top_bar = ttk.Frame(root, padding=10)
top_bar.pack(fill="x")
ttk.Button(top_bar, text="Add New Record", command=add_row).pack()

sf = ScrolledFrame(root, autohide=True)
sf.pack(fill="both", expand=True, padx=10, pady=10)

root.mainloop()

Clearing All Rows at Once

When building dynamic lists, you often need a "Reset" or "Clear All" button. Instead of deleting each row individually, we can iterate through all widgets inside the ScrolledFrame and destroy them in a single loop.

After clearing the widgets, it is essential to reset your counters and refresh the scrollbar logic to ensure the interface returns to its original state.

def clear_all():
    # 1. Iterate through all child widgets of the ScrolledFrame
    for widget in sf.winfo_children():
        widget.destroy()
    
    # 2. Reset the record counter
    var_count.set(1)
    
    # 3. Force the scrollbar to recalculate (shrinking it to zero)
    sf.update_idletasks()
    sf.container.event_generate("<Configure>")
Pro-Tip: Using winfo_children() is safer and faster than maintaining a manual list of widgets. It ensures that even if you added complex sub-frames, every single element is removed from the screen and memory.

Adding the Clear Button

You can add this button to your top control bar alongside the "Add" button:

ttk.Button(
    top_bar, 
    text="Clear All Records", 
    command=clear_all, 
    bootstyle="danger"
).pack(side=RIGHT, padx=5)

Tkinter vs. ttkbootstrap: Scrolling Comparison

Feature Standard Tkinter ttkbootstrap ScrolledFrame
Setup Complexity High (Canvas + Frame + Scrollbar) Low (Single Widget)
Mousewheel Support Manual (Requires OS-specific code) Built-in (Native behavior)
Auto-Hide Requires custom logic Enabled via autohide=True
Resizing Content Manual scrollregion updates Automatic via Event Generation
Learn More on ttkbootstrap Custom Theme Saving style settings in a Json file
Subhendu Mohapatra — author at plus2net
Subhendu Mohapatra

Author

🎥 Join me live on YouTube

Passionate about coding and teaching, I publish practical tutorials on PHP, Python, JavaScript, SQL, and web development. My goal is to make learning simple, engaging, and project‑oriented with real examples and source code.



Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
©2000-2025   plus2net.com   All rights reserved worldwide Privacy Policy Disclaimer