Scrollable Frame with grid not sticking to the border and not working

Question:

I would like to make a scrollable Frame.

from tkinter import *
from tkinter import ttk

class MainWindow:
    def __init__(self):
        self.metrics = []
        self.content = ttk.Frame(root)
        self.v = Scrollbar(root, orient = "vertical")
        self.class_label = Label(self.content, text = "labels")

        self.A_label = Label(self.content, text = "A")
        self.A_entry = Entry(self.content, width = 5)
        self.A_input = self.A_entry.get()
        self.A_hint = Label(self.content, text = "A")

        self.B_label = Label(self.content, text = "B")
        self.B_entry = Entry(self.content, width = 5)
        self.B_input = self.B_entry.get()
        self.B_hint = Label(self.content, text = "B")

        self.C = Label(self.content, text = "C")

        self.space = Label(self.content, text = "")

        self.D_label = Label(self.content, text = "D")
        self.D_entry = Entry(self.content, width = 5)
        self.D_hint = Label(self.content, text = "D")

        self.E_label = Label(self.content, text = "E")
        self.E_entry = Entry(self.content, width = 5)
        self.E_hint = Label(self.content, text = "D")

        self.F_label = Label(self.content, text = "F")
        self.F_entry = Entry(self.content, width = 5)
        self.F_hint = Label(self.content, text = "F")

        self.G_label = Label(self.content, text = "G")
        self.G_entry = Entry(self.content, width = 5)
        self.G_hint = Label(self.content, text = "G")

        self.H_label = Label(self.content, text = "H")
        self.H_entry = Entry(self.content, width = 5)
        self.H_hint = Label(self.content, text = "H")

        self.I_label = Label(self.content, text = "I")
        self.I_entry = Entry(self.content, width = 5)
        self.I_hint = Label(self.content, text = "I")

        self.J_label = Label(self.content, text = "J")
        self.J_entry = Entry(self.content, width = 5)
        self.J_hint = Label(self.content, text = "J")

        self.K_label = Label(self.content, text = "K")
        self.K_entry = Entry(self.content, width = 5)
        self.K_hint = Label(self.content, text = "K")

        self.space2 = Label(self.content, text = "")

        self.L_label = Label(self.content, text = "L")
        self.L_entry = Entry(self.content, width = 5)

        self.content.grid(row = 0, column = 0)
        self.v.grid(row = 0, column = 1, sticky = NS)

        self.class_label.grid(row = 0, column = 0)

        self.A_label.grid(row = 1, column = 0)
        self.A_entry.grid(row = 1, column = 1)
        self.A_hint.grid(row = 1, column = 3, sticky = "w")

        self.B_label.grid(row = 2, column = 0)
        self.B_entry.grid(row = 2, column = 1)
        self.B_hint.grid(row = 2, column = 3, sticky = "w")

        self.C.grid(row = 3, column = 3, sticky = "w")

        self.space.grid(row = 4, column = 0)


        self.D_label.grid(row = 6, column = 0)
        self.D_entry.grid(row = 6, column = 1)
        self.D_hint.grid(row = 6, column = 3, sticky = "w")

        self.E_label.grid(row = 7, column = 0)
        self.E_entry.grid(row = 7, column = 1)
        self.E_hint.grid(row = 7, column = 3, sticky = "w")

        self.F_label.grid(row = 8, column = 0)
        self.F_entry.grid(row = 8, column = 1)
        self.F_hint.grid(row = 8, column = 3, sticky = "w")

        self.G_label.grid(row = 9, column = 0)
        self.G_entry.grid(row = 9, column = 1)
        self.G_hint.grid(row = 9, column = 3, sticky = "w")

        self.H_label.grid(row = 10, column = 0)
        self.H_entry.grid(row = 10, column = 1)
        self.H_hint.grid(row = 10, column = 3, sticky = "w", columnspan = 2)

        self.I_label.grid(row = 11, column = 0)
        self.I_entry.grid(row = 11, column = 1)
        self.I_hint.grid(row = 11, column = 3, sticky = "w", columnspan = 2)

        self.J_label.grid(row = 12, column = 0)
        self.J_entry.grid(row = 12, column = 1)
        self.J_hint.grid(row = 12, column = 3, sticky = "w", columnspan = 2)

        self.K_label.grid(row = 13, column = 0)
        self.K_entry.grid(row = 13, column = 1)
        self.K_hint.grid(row = 13, column = 3, sticky = "w", columnspan = 2)

        self.space2.grid(row = 14, column = 0)

        self.L_label.grid(row = 19, column = 0)
        self.L_entry.grid(row = 19, column = 1)

        root.mainloop()



if __name__ == '__main__':
    root = Tk()

    root.title("title")
    new_window = MainWindow()

In this first example, the button in the scrollbar does appear but it can’t scroll and when I change the window’s size, the scrollbar doesn’t move.

If I’m not mistaken, I need to create a canvas within my frame and put everything inside this canvas. However, I read that I’m also supposed to put an inner frame inside the canvas. How am I supposed to do it?

So I tried this:

from tkinter import *
from tkinter import ttk

options = ["A", "B", "C", "D", "E", "F", "G", "H"]

root = Tk()

root.title("random software")

content = ttk.Frame(root)

vscrollbar = Scrollbar(content, orient=VERTICAL)
scrollable_canvas = Canvas(content)

inner_frame = Frame(scrollable_canvas)

A_label = Label(content, text = "A")
A_entry = Entry(content, width = 5)
A_hint = Label(content, text = "lorem")

B_label = Label(content, text = "B")
B_entry = Entry(content, width = 5)
B_hint = Label(content, text = "ipsum")

C_label = Label(content, text = "C")
C_entry = Entry(content, width = 5)
C_hint = Label(content, text = "dolor")

D_label = Label(content, text = "D")
D_entry = Entry(content, width = 5)
D_hint = Label(content, text = "sit")

E_label = Label(content, text = "E")
E_entry = Entry(content, width = 5)
E_hint = Label(content, text = "amet")

F_label = Label(content, text = "F")
F_entry = Entry(content, width = 5)
F_hint = Label(content, text = "consectetur")

G_label = Label(content, text = "G")
G_entry = Entry(content, width = 5)
G_hint = Label(content, text = "adipiscing")

H_label = Label(content, text = "H")
H_entry = Entry(content, width = 5)
H_hint = Label(content, text = "elit")

selected_option = StringVar()
selected_option.set(options[0])
option_label = OptionMenu(content, selected_option, *options)

content.grid(row = 1, column = 0)
vscrollbar.grid(row = 1, column = 1, sticky = NS)

option_label.grid(row = 5, column = 1)

A_label.grid(row = 6, column = 0)
A_entry.grid(row = 6, column = 1)
A_hint.grid(row = 6, column = 3, sticky = "w")

B_label.grid(row = 7, column = 0)
B_entry.grid(row = 7, column = 1)
B_hint.grid(row = 7, column = 3, sticky = "w")

C_label.grid(row = 8, column = 0)
C_entry.grid(row = 8, column = 1)
C_hint.grid(row = 8, column = 3, sticky = "w")

D_label.grid(row = 9, column = 0)
D_entry.grid(row = 9, column = 1)
D_hint.grid(row = 9, column = 3, sticky = "w")

E_label.grid(row = 10, column = 0)
E_entry.grid(row = 10, column = 1)
E_hint.grid(row = 10, column = 3, sticky = "w")

F_label.grid(row = 11, column = 0)
F_entry.grid(row = 11, column = 1)
F_hint.grid(row = 11, column = 3, sticky = "w")

G_label.grid(row = 12, column = 0)
G_entry.grid(row = 12, column = 1)
G_hint.grid(row = 12, column = 3, sticky = "w")

H_label.grid(row = 13, column = 0)
H_entry.grid(row = 13, column = 1)
H_hint.grid(row = 13, column = 3, sticky = "w")



root.mainloop()

In this case, the scrollbar isn’t extended from top to bottom, it doesn’t appear on the right side of the window and when I change the window’s size the scrollbar doesn’t move. Why isn’t it extended on the right side (like the first case) and how to make the scrollbar scrollable?

I saw a few answers with pack but I would like to continue using grid.

Edit:

Based on the answer from Ovski, I managed to get this:

from tkinter import *
from tkinter import ttk

options = ["A", "B", "C", "D", "E", "F", "G", "H"]

root = Tk()

root.title("random software")

content = ttk.Frame(root)

scrollable_canvas = Canvas(content)


vscrollbar = ttk.Scrollbar(content, orient=VERTICAL, command = scrollable_canvas.yview)


scrollable_canvas.configure(yscrollcommand=vscrollbar.set)
scrollable_canvas.bind('<Configure>',
    lambda e: scrollable_canvas.configure(scrollregion = scrollable_canvas.bbox("all")))

inner_frame = Frame(scrollable_canvas)
scrollable_canvas.create_window((0, 0), window = inner_frame, anchor = "nw")

A_label = Label(inner_frame, text = "A")
A_entry = Entry(inner_frame, width = 5)
A_hint = Label(inner_frame, text = "lorem")

B_label = Label(inner_frame, text = "B")
B_entry = Entry(inner_frame, width = 5)
B_hint = Label(inner_frame, text = "ipsum")

C_label = Label(inner_frame, text = "C")
C_entry = Entry(inner_frame, width = 5)
C_hint = Label(inner_frame, text = "dolor")

D_label = Label(inner_frame, text = "D")
D_entry = Entry(inner_frame, width = 5)
D_hint = Label(inner_frame, text = "sit")

E_label = Label(inner_frame, text = "E")
E_entry = Entry(inner_frame, width = 5)
E_hint = Label(inner_frame, text = "amet")

F_label = Label(inner_frame, text = "F")
F_entry = Entry(inner_frame, width = 5)
F_hint = Label(inner_frame, text = "consectetur")

G_label = Label(inner_frame, text = "G")
G_entry = Entry(inner_frame, width = 5)
G_hint = Label(inner_frame, text = "adipiscing")

H_label = Label(inner_frame, text = "H")
H_entry = Entry(inner_frame, width = 5)
H_hint = Label(inner_frame, text = "elit")

content.grid(row = 0, column = 1)
scrollable_canvas.grid(row = 0, column = 1)
vscrollbar.grid(row = 0, column = 2, sticky = "NS" + "E")

A_label.grid(row = 6, column = 0)
A_entry.grid(row = 6, column = 1)
A_hint.grid(row = 6, column = 3, sticky = "w")

B_label.grid(row = 7, column = 0)
B_entry.grid(row = 7, column = 1)
B_hint.grid(row = 7, column = 3, sticky = "w")

C_label.grid(row = 8, column = 0)
C_entry.grid(row = 8, column = 1)
C_hint.grid(row = 8, column = 3, sticky = "w")

D_label.grid(row = 9, column = 0)
D_entry.grid(row = 9, column = 1)
D_hint.grid(row = 9, column = 3, sticky = "w", columnspan = 2)

E_label.grid(row = 10, column = 0)
E_entry.grid(row = 10, column = 1)
E_hint.grid(row = 10, column = 3, sticky = "w")

F_label.grid(row = 11, column = 0)
F_entry.grid(row = 11, column = 1)
F_hint.grid(row = 11, column = 3, sticky = "w")

G_label.grid(row = 12, column = 0)
G_entry.grid(row = 12, column = 1)
G_hint.grid(row = 12, column = 3, sticky = "w")

H_label.grid(row = 13, column = 0)
H_entry.grid(row = 13, column = 1)
H_hint.grid(row = 13, column = 3, sticky = "w")



root.mainloop()

The idea behind this is that grid and pack should be combined to get the best result. In this case, using grid with a scrollbar wouldn’t really work because the scrollbar would be stuck in its case. By putting every widget in a canvas, the canvas can be aligned using pack and the scrollbar will stick to the border.

Asked By: Nocxy

||

Answers:

Here is a working example of a frame, containing a canvas, containing an "inner_frame" holding a grid of labels.

Included a function that handles scroll events, but this only works if you have a single scrollable area as it locks the scrollwheel to the application, not the frame.

There is also a part (read the comments) to handle resizing of the window.

import tkinter as tk


# mouse wheel scrolling with reduced speed
def on_mouse_wheel(event):
    canvas.yview('scroll', int(-1 * event.delta / 120), 'units')

root = tk.Tk()
root.bind('<MouseWheel>', on_mouse_wheel)  # bind mousewheel to root, this only works if you have a single scroll area
window_width = 400
window_height = 200
table_columns = 4
table_rows = 30

root.geometry(f'{window_width}x{window_height}')

main_frame = tk.Frame(root)
main_frame.pack(fill=tk.BOTH, expand=1)  # frame goes to the left

canvas = tk.Canvas(main_frame)
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)

v_scroll = tk.Scrollbar(main_frame, orient=tk.VERTICAL, command=canvas.yview)
v_scroll.pack(side=tk.RIGHT, fill=tk.Y)  # scrollbar goes to the right

canvas.configure(yscrollcommand=v_scroll.set)
canvas.bind(
    '<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox(tk.ALL))
)  # adjust scrolling area on resize

inside_frame = tk.Frame(canvas)  # frame where you put your actual content
canvas.create_window((0, 0), window=inside_frame, anchor=tk.N)  # adding the inside_frame to the canvas

# an example grid with some data
for y in range(table_rows):
    for x in range(table_columns):
        tk.Label(inside_frame, text=f'{y}:{x}', borderwidth=1, relief=tk.SOLID, width=10).grid(column=x, row=y)
        
root.mainloop()

This is the way I handle scrollbars for all my GUI applications and it seems to be the only way if you want to scroll widgets other then a textbox or a treeview.

Conclusion: Use the pack manager to arrange the main frame, canvas and inner frame and put all your content inside the inner frame with whatever window manager you like.

UPDATE: Your modified code from the first example
I removed ttk as it is not needed and refactored the coded a bit.

import tkinter as tk


class MainWindow:
    def __init__(self, root: tk.Tk):
        self.root = root
        self.root.title("title")
        self.root.geometry('100x200')

        self.main_frame = tk.Frame(root)
        self.main_frame.pack(fill=tk.Y, expand=1)

        self.canvas = tk.Canvas(self.main_frame, width=100)
        self.canvas.pack(side=tk.LEFT, fill=tk.Y)

        self.v = tk.Scrollbar(self.main_frame, orient=tk.VERTICAL, command=self.canvas.yview)
        self.v.pack(side=tk.RIGHT, fill=tk.Y)

        self.canvas.configure(yscrollcommand=self.v.set)
        self.canvas.bind('<Configure>', lambda e: self.canvas.configure(scrollregion=self.canvas.bbox(tk.ALL)))

        self.inner_frame = tk.Frame(self.canvas)
        self.canvas.create_window((0, 0), window=self.inner_frame, anchor=tk.NW)

        self.class_label = tk.Label(self.inner_frame, text="labels")

        self.A_label = tk.Label(self.inner_frame, text="A")
        self.A_entry = tk.Entry(self.inner_frame, width=5)
        self.A_input = self.A_entry.get()
        self.A_hint = tk.Label(self.inner_frame, text="A")

        self.B_label = tk.Label(self.inner_frame, text="B")
        self.B_entry = tk.Entry(self.inner_frame, width=5)
        self.B_input = self.B_entry.get()
        self.B_hint = tk.Label(self.inner_frame, text="B")

        self.C = tk.Label(self.inner_frame, text="C")

        self.space = tk.Label(self.inner_frame, text="")

        self.D_label = tk.Label(self.inner_frame, text="D")
        self.D_entry = tk.Entry(self.inner_frame, width=5)
        self.D_hint = tk.Label(self.inner_frame, text="D")

        self.E_label = tk.Label(self.inner_frame, text="E")
        self.E_entry = tk.Entry(self.inner_frame, width=5)
        self.E_hint = tk.Label(self.inner_frame, text="D")

        self.F_label = tk.Label(self.inner_frame, text="F")
        self.F_entry = tk.Entry(self.inner_frame, width=5)
        self.F_hint = tk.Label(self.inner_frame, text="F")

        self.G_label = tk.Label(self.inner_frame, text="G")
        self.G_entry = tk.Entry(self.inner_frame, width=5)
        self.G_hint = tk.Label(self.inner_frame, text="G")

        self.H_label = tk.Label(self.inner_frame, text="H")
        self.H_entry = tk.Entry(self.inner_frame, width=5)
        self.H_hint = tk.Label(self.inner_frame, text="H")

        self.I_label = tk.Label(self.inner_frame, text="I")
        self.I_entry = tk.Entry(self.inner_frame, width=5)
        self.I_hint = tk.Label(self.inner_frame, text="I")

        self.J_label = tk.Label(self.inner_frame, text="J")
        self.J_entry = tk.Entry(self.inner_frame, width=5)
        self.J_hint = tk.Label(self.inner_frame, text="J")

        self.K_label = tk.Label(self.inner_frame, text="K")
        self.K_entry = tk.Entry(self.inner_frame, width=5)
        self.K_hint = tk.Label(self.inner_frame, text="K")

        self.space2 = tk.Label(self.inner_frame, text="")

        self.L_label = tk.Label(self.inner_frame, text="L")
        self.L_entry = tk.Entry(self.inner_frame, width=5)

        self.class_label.grid(row=0, column=0)

        self.A_label.grid(row=1, column=0)
        self.A_entry.grid(row=1, column=1)
        self.A_hint.grid(row=1, column=3, sticky="w")

        self.B_label.grid(row=2, column=0)
        self.B_entry.grid(row=2, column=1)
        self.B_hint.grid(row=2, column=3, sticky="w")

        self.C.grid(row=3, column=3, sticky="w")

        self.space.grid(row=4, column=0)

        self.D_label.grid(row=6, column=0)
        self.D_entry.grid(row=6, column=1)
        self.D_hint.grid(row=6, column=3, sticky="w")

        self.E_label.grid(row=7, column=0)
        self.E_entry.grid(row=7, column=1)
        self.E_hint.grid(row=7, column=3, sticky="w")

        self.F_label.grid(row=8, column=0)
        self.F_entry.grid(row=8, column=1)
        self.F_hint.grid(row=8, column=3, sticky="w")

        self.G_label.grid(row=9, column=0)
        self.G_entry.grid(row=9, column=1)
        self.G_hint.grid(row=9, column=3, sticky="w")

        self.H_label.grid(row=10, column=0)
        self.H_entry.grid(row=10, column=1)
        self.H_hint.grid(row=10, column=3, sticky="w", columnspan=2)

        self.I_label.grid(row=11, column=0)
        self.I_entry.grid(row=11, column=1)
        self.I_hint.grid(row=11, column=3, sticky="w", columnspan=2)

        self.J_label.grid(row=12, column=0)
        self.J_entry.grid(row=12, column=1)
        self.J_hint.grid(row=12, column=3, sticky="w", columnspan=2)

        self.K_label.grid(row=13, column=0)
        self.K_entry.grid(row=13, column=1)
        self.K_hint.grid(row=13, column=3, sticky="w", columnspan=2)

        self.space2.grid(row=14, column=0)

        self.L_label.grid(row=19, column=0)
        self.L_entry.grid(row=19, column=1)

        self.root.mainloop()


if __name__ == '__main__':
    new_window = MainWindow(tk.Tk())
Answered By: Ovski
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.