Search code examples
python-3.xtkinterscrollbar

Adjust tkinter scrollbar with window


I am making a scrollable GUI with tkinter but the scrollbar isn't adjusting with the window size. So, if I resize the root window by dragging from the corner with mouse, I want the scrollbar to dynamically resize with the window. So basically, if the window is large enough, the scrollbar isn't needed, but if I resize it to be smaller, the scrollbar is needed. Here is my code:

import tkinter as tk

def makelabelframe(root, text, row, col):
    label_frame = tk.LabelFrame(root, text=text)
    label_frame.grid(row=row, column=col, sticky="news", padx=15, pady=10)
    return label_frame

def makelabel(root, text, row, col):
    lab = tk.Label(root, text=text)
    lab.grid(row=row, column=col, sticky="news", padx=15, pady=10)
    return lab

def makebutton(root, text, command, row, col):
    button = tk.Button(root, text=text, command=command)
    button.grid(row=row, column=col, sticky="news", padx=15, pady=10)

def makeframe(root, row, col):
    frame = tk.Frame(root)
    frame.grid(row=row, column=col, sticky="news", padx=15, pady=10)
    return frame

def makecanvas(root, row, col):
    canvas = tk.Canvas(root)
    canvas.grid(row=row, column=col, sticky="news", padx=15, pady=10)
    return canvas

def makescrollbar(root, canv, row, col):
    scrollbar = tk.Scrollbar(root, orient=tk.VERTICAL, command=canv.yview)
    scrollbar.grid(row=row, column=col, sticky="news", padx=15, pady=10)
    return scrollbar

def main():
    root = tk.Tk()
    w, h = 500, 500
    root.geometry("{}x{}".format(w, h))
    
    mainframe = makeframe(root, 0, 0)
    mainframe.place(relx=0.5, rely=0.5, anchor="center")
    canv = makecanvas(mainframe, 0, 0)
    sb = makescrollbar(mainframe, canv, 0, 1)
    canv.configure(yscrollcommand=sb.set)
    frame = makeframe(canv, 0, 1)
    win = canv.create_window((0, 0), window=frame, anchor="center")
    canv.bind_all("<MouseWheel>", lambda e: canv.yview_scroll(int(-e.delta/120), "units"))
    frame.bind("<Configure>", lambda e: canv.configure(width=e.width, height=e.height*0.99, scrollregion=canv.bbox("all")))

    button_func = lambda: print("Click")
    
    labelframe1 = makelabelframe(frame, "Labelframe 1", 0, 0)
    makelabel(labelframe1, "1", 0, 0)
    makelabel(labelframe1, "Text in labelframe 1", 0, 1)
    makelabel(labelframe1, "Button in labelframe 1", 1, 0)
    makebutton(labelframe1, "Button 1", button_func, 1, 1)

    labelframe2 = makelabelframe(frame, "Labelframe 2", 1, 0)
    makelabel(labelframe2, "2", 0, 0)
    makelabel(labelframe2, "Text in labelframe 2", 0, 1)
    makelabel(labelframe2, "Button in labelframe 2", 1, 0)
    makebutton(labelframe2, "Button 2", button_func, 1, 1)

    labelframe3 = makelabelframe(frame, "Labelframe 3", 2, 0)
    makelabel(labelframe3, "3", 0, 0)
    makelabel(labelframe3, "Text in labelframe 3", 0, 1)
    makelabel(labelframe3, "Button in labelframe 3", 1, 0)
    makebutton(labelframe3, "Button 3", button_func, 1, 1)
    
    labelframe4 = makelabelframe(frame, "Labelframe 4", 3, 0)
    makelabel(labelframe4, "4", 0, 0)
    makelabel(labelframe4, "Text in labelframe 4", 0, 1)
    makelabel(labelframe4, "Button in labelframe 4", 1, 0)
    makebutton(labelframe4, "Button 4", button_func, 1, 1)

    root.mainloop()

main()

I have tried many things, such as doing this without mainframe.place() function, but that does not work. I also tried to make a resize function:

def resize(e):
    canv.itemconfig(win, height=e.height, width=e.width)
canv.bind("<Configure>", resize)

But something very weird happens with this function. I also tried to make the canv first and the mainframe after that, but it did not work. I also tried binding canv, win, mainframe and root to "<Configure>" instead of frame , but none of that worked.

How could I fix this so that the scrollbar adjusts with the root window size if I make it smaller?


Solution

  • Note that canv and sb are children of mainframe and mainframe will not be resized when the window is resized. So both canv and sb will not be resized as well.

    You need to:

    • config mainframe to resize whenever the window is resized by adding relheight=1 in mainframe.place(...)
    • allocate available space vertically to widgets in row 0 (canv and sb) of mainframe by calling mainframe.rowconfigure(0, weight=1)

    Note also that:

    • it is better to use anchor="nw" in canv.create_window(...)
    • don't set the height of canv inside the lambda of frame.bind("<Configure>", ...)

    Required changes:

    def main():
        ...
        mainframe.place(relx=0.5, rely=0.5, anchor="center", relheight=1) # added relheight=1
        mainframe.rowconfigure(0, weight=1) # allocate available space vertically to row 0
        ...
        win = canv.create_window((0, 0), window=frame, anchor="nw") # used anchor="nw" instead
        ...
        # remove setting height in canv.configure(...)
        frame.bind("<Configure>", lambda e: canv.configure(width=e.width, scrollregion=canv.bbox("all")))
        ...