'Make Frame occupy whole of its scrollable parent Canvas when it is smaller than the Canvas itself

Situation:

I have a created a scrollable frame as follows - a parent outer frame with two configured columns, canvas on column=0, scrollbar on column=1. The canvas contains the actual frame, placed in it using create_window. My case is like, there may be times when there won't be any content in the frame, and times where there is enough content (it's decided at runtime).

When there is no content, the frame shrinks to a mere 1x1 pixel size; when the content is present but not enough to scroll, the frame covers only the content. My requirement is to set the frame so that when its required size is lesser than the canvas itself, it should take the size of the canvas; ie: the frame's size shouldn't be smaller than the canvas itself.


What I've tried:

1. I have tried -

.itemconfig(<frame_itemID>, width=<the_canvas>.winfo_width(), height=<the_canvas>.winfo_height()) on the concerned canvas after I .grid()ed the outer frame onto the screen in the hopes of it setting the size for the frame, to no avail.

2. Tried binding whenever the canvas get configured - <the_canvas>.bind("<Configure>", lambda e: <the_canvas>.itemconfig(<frame_itemID>, width=e.width, height=e.height)). The problem with this - the frame is stuck to the on-screen size of the canvas , ie: even on content overflow, it doesn't resize (seems permanently hard-bound to canvas' size).

3. Tried <inner_frame>.configure(width=<the_canvas>.winfo_width(), height=<the_canvas>.winfo_height()) after I placed the outer frame onto the screen. Expected the frame to react and resize the same way it would have if another widget was placed in it, in vain.

The first two attempts were from various suggestions on a question on this site; as I said, don't seem to work.


Code:

Following is the code for the scrolling frame; the one causing the above concerns:

class ScrollFrame(tk.Frame):
    def __init__(self, master=None, **kwargs):
        super().__init__(master, bd=0)

        # creating and placing the widgets as required
        self.canvas = tk.Canvas(self, bd=0, bg="#5599FF")
        self.scrollbar = ttk.Scrollbar(self)
        self.frame = tk.Frame(self.canvas, **kwargs)
        # used self._frame_id to store the item_id for the frame for attempt 1 & 2 above
        self._frame_id = self.canvas.create_window((0,0), window=self.frame, anchor="nw")
        self.canvas.grid(row=0, column=0, sticky="nwes")
        self.scrollbar.grid(row=0, column=1, sticky="nwes")
        # configuring the grid
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=0, minsize=16)
        # configurations to connect the canvas, frame and scrollbar
        self.frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        self.scrollbar.configure(orient='vertical', command=self.canvas.yview)

# NOTE: Any keyword arguments provided when the class is called is sent directly to the inner frame, not the outer (parent) frame.

Required imports -

import tkinter as tk
from tkinter import ttk

Sample code to test the class -

# with no content
def no_content(master):
    ScrollFrame(master, bg="#30303B", bd=0).place(x=20, y=20, width=240, height=400)

# some smaller-than-canvas content, not enough to enable scrolling
def some_content(master):
    Frame = ScrollFrame(master,  bg="#30303B", bd=0)
    tk.Label(Frame.frame, text="Hello,\nWorld!!!", bg="#FFCCAA", font=["Calibri", 25]).pack(padx=25, pady=25)
    Frame.place(x=280, y=20, width=240, height=400)

# enough content to enable scrolling
def overflow_content(master):
    Frame = ScrollFrame(master, bg="#30303B", bd=0)
    tk.Label(Frame.frame, text="Enough\n\ncontent\n\nto\n\nmake\n\nme\n\noverflow", bg="#FFCCAA", font=["Calibri", 30]).pack(padx=25, pady=25)
    Frame.place(x=540, y=20, width=240, height=400)

root = tk.Tk()
root.geometry("800x440")
root.resizable(0, 0)
no_content(root)
some_content(root)
overflow_content(root)
root.mainloop()

Any inputs which would satiate the requirements will be of great help.

EDIT: I have made a workaround of sorts for now where the canvas has the same background as frame (with highlightborder=0) and the frame bound to reconfigure its width to be the same as canvas whenever it's dimensions are changed (on adding widgets, etc). So the frame doesn't have a height when nothing is in it and the canvas is what is seen. But it still isn't satisfactory in that, I can't really place something like right in the centre of the visible area since the frame really depends on the inner widgets for its height. So an actual solution will be appreciated.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source