Search code examples
pythonpython-3.xtkintertkinter-canvas

Tkinter - Frame can scroll horizontally, but will not vertically size itself properly


I have a frame that is scrollable horizontally. That is working as desired. I desire the frame to vertically size itself such that all widgets are displayed. Currently, the frame will not grow as additional widgets are added, and widgets created below a certain point are not visible. I know that I could use a vertical scrollbar, but I greatly prefer not to. The frame will contain a variable amount of widgets, but never so many that it needs to be scrollable in the vertical direction. So I would prefer that it just naturally sizes itself in that direction. Here is a minimal code example:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()

outer_frm = tk.Frame(root)
outer_frm.grid(column=0, row=0)

canv = tk.Canvas(outer_frm)
canv.grid(column=0, row=0)

inner_frm = tk.Frame(canv)
inner_frm.grid(column=0, row=0)

sbar = ttk.Scrollbar(outer_frm, orient='horizontal', command=canv.xview)

canv.configure(xscrollcommand=sbar.set)

sbar.grid(column=0, row=1, sticky='EW')

inner_frm.bind(
    "<Configure>",
    lambda e: canv.configure(
        scrollregion=canv.bbox("all")
    )
)

canv.create_window(
    (0,0), window=inner_frm, anchor='nw'
)

for i in range(100):
    for j in range(30):
        tk.Label(inner_frm, text=f'-----{i}-----{j}-----').grid(column=i, row=j)

root.mainloop()

I have tried several different approaches with no success. The closest I have come is

root.update_idletasks()
width = root.winfo_width()
required_height = inner_frm.winfo_reqheight()
root.geometry(f"{width}x{required_height}")

but that only sizes the entire window, leaving the frame the same size and a window that is half empty at the bottom.

How do I go about making the frame size itself properly in the vertical direction?


Solution

  • Adding widgets into inner_frm will not expand the canvas vertically. You need to expand the canvas manually when inner_frm is resized:

    def on_configure(event):
        canv.configure(
            scrollregion=canv.bbox('all'),
            height=event.height  # set height of canvas to the same as that of the frame
        )
    
    inner_frm.bind('<Configure>', on_configure)