I'm having a scrollbar issue with a Tkinter GUI that I'm creating. The GUI contains a class Gen_Box that reproduces a given widget vertically downwards as many times as the widgets add_btn is called. Obviously, this runs off the window frame pretty quickly.
I've tried adding a scrollbar to account for this running off the frame. I know these are pretty complicated to add in tkinter and require some hacking around. I referenced this video by Codemy. While I can see the scrollbar to the right of the frame, it doesn't expand accomodate the added widgets (see screenshots below). I've been tinkering around with this all day but am at a loss.
Starting frame:
Frame after second Monitor widget is added:

Also as a note, I'm using CustomTkinter to stylize the widgets.
Current code. Scrollbar is set in the Form.__init__() function. Any help on this issue or other input here is much appreciated.
class Form(tk.Frame):
def __init__(self, root):
self.root = root
self.main_frame = tk.Frame(self.root)
self.main_frame.grid(sticky='NSEW')
self.canvas = tk.Canvas(self.main_frame, height=750, width=750)
self.canvas.grid(row=0, column=0, sticky='NSEW')
self.canvas.grid_rowconfigure(0, weight=1)
self.scrollbar = tk.Scrollbar(self.main_frame, orient=VERTICAL)
self.scrollbar.config(command=self.canvas.yview)
self.scrollbar.grid(row=0, column=1, sticky='NSE')
self.canvas.config(yscrollcommand=self.scrollbar.set)
self.canvas.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox(ALL)))
self.inner_frame = tk.Frame(self.canvas)
self.canvas.create_window((0,0), window=self.inner_frame, anchor='nw')
self.load()
def load(self):
self.right = ctk.CTkFrame(self.inner_frame, bg_color='#09275d', fg_color='#09275d', height=750, width=750)
basics_label = ctk.CTkLabel(self.right, text='Setup', text_font=('Helvetica', 24), text_color='#f5d397')
basics_label.grid(row=0, column=0, sticky='NW', pady=10)
name_frame = ctk.CTkFrame(self.right, fg_color='#E4E7F1', bg_color='#09275d')
name_frame.grid(row=1, columnspan=2, sticky=NW,pady=5)
name_label = ctk.CTkLabel(name_frame, text='Name', text_font=("MS Sans Serif", 15))
name_label.grid(row=1, column=0, padx=2, pady=2, sticky=NW)
name_entry = ctk.CTkEntry(name_frame, width=250, border_width=1, corner_radius=10)
name_entry.grid(row=1, column=1, padx=2, pady=2)
start_frame = ctk.CTkFrame(self.right, corner_radius=2, fg_color='#E4E7F1', bg_color='#09275d')
start_frame.grid(row=2, columnspan=2, sticky=NW,pady=5)
start_url_label = ctk.CTkLabel(start_frame, text='Start URL', text_font=("MS Sans Serif", 15))
start_url_label.grid(row=2, column=0, sticky=NW, pady=2, padx=2)
start_urls_frame = ctk.CTkFrame(start_frame, fg_color='#E4E7F1', bg_color='#09275d')
start_urls_frame.grid(row=2, column=1, pady=2, padx=2)
start_urls = Gen_Box(start_urls_frame, Include)
include_frame = ctk.CTkFrame(self.right, fg_color='#E4E7F1', bg_color='#09275d', corner_radius=5)
include_frame.grid(row=3, columnspan=2, sticky=NW,pady=5)
include_url_label = ctk.CTkLabel(include_frame, text='Include URL', text_font=("Myriad", 15))
include_url_label.grid(row=3, column=0, sticky=NW, padx=2, pady=2)
include_url_entry = ctk.CTkFrame(include_frame, fg_color='#E4E7F1', bg_color='#09275d')
include_url_entry.grid(row=3, column=1, columnspan=2, padx=2, pady=2)
url_patterns = Gen_Box(include_url_entry, Include)
depth_frame = ctk.CTkFrame(self.right, fg_color='#E4E7F1', bg_color='#09275d', corner_radius=5)
depth_frame.grid(row=4, columnspan=2, sticky=NW, pady=5)
depth_label = ctk.CTkLabel(depth_frame, text='Max Depth', text_font=("Myriad", 15))
depth_label.grid(row=4, column=0, sticky=NW, padx=2, pady=2)
depth_level = ctk.IntVar(depth_frame)
depth_level.set(1)
depth_options = ctk.CTkOptionMenu(master=depth_frame, variable=depth_level, values=[str(i) for i in range(1,11)], fg_color='white', button_color='#6AA6DE', width=25, corner_radius=10)
depth_options.grid(row=4, column=1, sticky=NW, padx=2, pady=2)
profile_frame = ctk.CTkFrame(self.right, fg_color='#E4E7F1', bg_color='#09275d', corner_radius=5)
profile_frame.grid(row=5, columnspan=2, sticky=NW, pady=5)
profile_label = ctk.CTkLabel(profile_frame, text='Chrome Profile', text_font=("Myriad", 15))
profile_label.grid(row=5, column=0, sticky=NW, padx=2, pady=2)
profile_level = ctk.StringVar(profile_frame)
profile_level.set('False')
profile_options = ctk.CTkOptionMenu(master=profile_frame, variable=profile_level, values=['True', 'False'], fg_color='white', button_color='#6AA6DE', width=25, corner_radius=10)
profile_options.grid(row=5, column=1, sticky=NW, padx=2, pady=2)
headless_frame = ctk.CTkFrame(self.right, fg_color='#E4E7F1', bg_color='#09275d', corner_radius=5)
headless_frame.grid(row=6, columnspan=2, sticky=NW, pady=5)
headless_label = ctk.CTkLabel(headless_frame, text='Headless', text_font=("Myriad", 15))
headless_label.grid(row=6, column=0, sticky=NW, padx=2, pady=2)
headless_level = ctk.StringVar(headless_frame)
headless_level.set('True')
headless_options = ctk.CTkOptionMenu(master=headless_frame, variable=headless_level, values=['True', 'False'], fg_color='white', button_color='#6AA6DE', width=25, corner_radius=10)
headless_options.grid(row=6, column=1, sticky=NW, padx=2, pady=2)
delay_frame = ctk.CTkFrame(self.right, fg_color='#E4E7F1', bg_color='#09275d', corner_radius=5)
delay_frame.grid(row=7, columnspan=2, sticky=NW, pady=5)
delay_label = ctk.CTkLabel(delay_frame, text='Delay', text_font=("Myriad", 15))
delay_label.grid(row=7, column=0, sticky=NW, padx=2, pady=2)
delay_level = ctk.IntVar(delay_frame)
delay_level.set(0)
delay_options = ctk.CTkOptionMenu(master=delay_frame, variable=delay_level, values=[str(i) for i in range(1,11)], fg_color='white', button_color='#6AA6DE', width=25, corner_radius=10)
delay_options.grid(row=7, column=1, sticky=NW, padx=2, pady=2)
download_frame = ctk.CTkFrame(self.right, fg_color='#E4E7F1', bg_color='#09275d')
download_frame.grid(row=8, columnspan=2, sticky=NW,pady=5)
download_label = ctk.CTkLabel(download_frame, text='Download Path', text_font=("MS Sans Serif", 15))
download_label.grid(row=8, column=0, padx=2, pady=2, sticky=NW)
download_entry = ctk.CTkEntry(download_frame, width=350, border_width=1, corner_radius=10)
download_entry.grid(row=8, column=1, padx=2, pady=2)
self.monitors_label = ctk.CTkLabel(self.right, text='Monitors', text_font=('Helvetica', 24), text_color='#f5d397')
self.monitors_label.grid(sticky='NW', row=9, column=0, pady=10)
self.monitor_frame = ctk.CTkFrame(self.right, bg_color='#09275d', fg_color='#09275d')
Gen_Box(self.monitor_frame, Monitor)
self.monitor_frame.grid(row=10, sticky='NW')
self.right.grid(row=0)
class Gen_Box:
def __init__(self, master, gen_obj, existing=None):
self.master = master
self.gen_obj = gen_obj
self.existing = existing
self.load(existing)
def load(self, existing=None):
if existing:
self.obj_rows = [self.gen_obj(key) for key in existing.keys()]
else:
self.obj_rows = [self.gen_obj(0)]
self.frame = ctk.CTkFrame(self.master, fg_color='#09275d')
for index, obj in enumerate(self.obj_rows):
add = lambda row=index+1 : self.add(row)
remove = lambda row=index : self.remove(row)
obj.row_no = index
if existing:
obj.load(self.frame, existing=existing[index])
else:
obj.load(self.frame)
obj.add_btn.configure(command=add)
obj.remove_btn.configure(command=remove)
self.frame.grid(sticky='NSEW')
self.master.update()
def add(self, row):
self.obj_rows.insert(row, self.gen_obj(row))
existing_entries = self.save_existing()
self.frame.destroy()
self.load(existing_entries)
def remove(self, row):
self.obj_rows.pop(row)
existing_entries = self.save_existing()
self.frame.destroy()
self.load(existing_entries)
def save_existing(self):
existing_dict = {}
for index, object in enumerate(self.obj_rows):
try:
existing_dict[index] = object.generate_scheme()
except AttributeError as e:
existing_dict[index] = None
return existing_dict
class Monitor:
def __init__(self, row_no):
self.row_no = row_no
def generate_scheme(self):
return_dict = {}
scheme_dict = {
'output': self.output.get,
'includes': self.includes.save_existing,
'selectors': self.selectors.save_existing
}
for key, value in scheme_dict.items():
return_dict[key] = value()
return return_dict
def load(self, frame, existing=None):
self.monitor_frame = ctk.CTkFrame(frame, fg_color='#E4E7F1', bg_color='#09275d')
self.button_frame = ctk.CTkFrame(frame, bg_color='#09275d', fg_color='#09275d')
self.button_frame.grid(row=self.row_no, column=1, sticky='NSEW', pady=10, padx=2)
self.add_btn = ctk.CTkButton(self.button_frame, text='+', corner_radius=2, width=50, height=50, fg_color='#ecae3e')
self.add_btn.grid(row=self.row_no, column=0, padx=10)
self.remove_btn = ctk.CTkButton(self.button_frame, text='-', corner_radius=2, width=50, height=50, fg_color='#ecae3e')
self.remove_btn.grid(row=self.row_no, column=1, padx=10)
self.top_frame = ctk.CTkFrame(self.monitor_frame)
self.top_frame.grid(sticky='NSEW', pady=10, padx=10)
self.include_frame = ctk.CTkFrame(self.top_frame, fg_color='#E4E7F1', bg_color='#E4E7F1')
self.include_frame.grid(row=0, sticky='NSEW')
self.include_label = ctk.CTkLabel(self.include_frame, text='Include URLs', text_font=("Myriad", 15))
self.include_label.grid(row=0, sticky='NW')
if not existing:
self.includes = Gen_Box(self.include_frame, Include)
else:
self.includes = Gen_Box(self.include_frame, Include, existing=existing['includes'])
self.selector_frame = ctk.CTkFrame(self.top_frame, fg_color='#E4E7F1', bg_color='#E4E7F1')
self.selector_frame.grid(row=1, sticky='NSEW')
self.selector_label = ctk.CTkLabel(self.selector_frame, text='Selectors (Optional)', text_font=("Myriad", 15))
self.selector_label.grid(row=1, sticky='NW')
if not existing:
self.selectors = Gen_Box(self.selector_frame, Include)
else:
self.selectors = Gen_Box(self.selector_frame, Include, existing=existing['selectors'])
self.output_frame = ctk.CTkFrame(self.monitor_frame, fg_color='#E4E7F1', bg_color='#E4E7F1')
self.output_frame.grid(row=2, pady=5, padx=2, sticky='NSEW', columnspan=2)
self.output_label = ctk.CTkLabel(self.output_frame, text='Output', text_font=("Myriad", 15))
self.output_label.grid(row=2, column=0)
self.output = ctk.StringVar(self.output_frame)
if existing:
self.output.set(existing['output'])
else:
self.output.set('TXT')
self.output_options = ['TXT', 'PDF']
self.output_menu = ctk.CTkOptionMenu(self.output_frame, variable=self.output, values=self.output_options, fg_color='white', button_color='#6AA6DE', width=25, corner_radius=10)
self.output_menu.grid(row=2, column=1)
self.actions_frame = ctk.CTkFrame(self.monitor_frame, bg_color='#E4E7F1', fg_color='#E4E7F1')
self.actions_frame.grid(row=3, pady=5)
self.add_actions_btn = ctk.CTkButton(self.actions_frame, text='Actions Editor ▼', text_font=("Myriad", 10), fg_color='#3eecae', corner_radius=10)
self.add_actions_btn.grid(sticky='NSEW')
self.add_actions_btn.configure(width=self.actions_frame.winfo_width())
self.monitor_frame.grid(row=self.row_no, pady=10, padx=10)
frame.update()
class Include:
def __init__(self, row_no):
self.row_no = row_no
def generate_scheme(self):
return_dict = {}
scheme_dict = {
'entry': self.entry.get
}
for key, value in scheme_dict.items():
return_dict[key] = value()
return return_dict
def load(self, master, existing=None):
entry_frame = ctk.CTkFrame(master, bg_color='#E4E7F1', fg_color='#E4E7F1')
self.entry = ctk.CTkEntry(entry_frame, corner_radius=10, border_width=1, width=300, bg_color='#E4E7F1')
if existing:
self.entry.insert(END, existing['entry'])
self.entry.grid(row=self.row_no, column=0, pady=2, ipadx=2, sticky=NSEW)
button_frame = ctk.CTkFrame(master, bg_color='#E4E7F1', fg_color='#E4E7F1')
self.add_btn = ctk.CTkButton(button_frame, text='+', height=10, width=10, corner_radius=5, text_font=("Helvetica", 12), fg_color='#6AA6DE', bg_color='#E4E7F1', text_color='#E4E7F1')
self.add_btn.grid(row=self.row_no, column=1, padx=3, pady=5)
self.remove_btn = ctk.CTkButton(button_frame, text='-', height=10, width=10, corner_radius=5, text_font=("Helvetica", 12), fg_color='#6AA6DE', bg_color='#E4E7F1', text_color='#E4E7F1')
self.remove_btn.grid(row=self.row_no, column=2, padx=3, pady=5)
entry_frame.grid(row=self.row_no, column=0, sticky='NSEW')
button_frame.grid(row=self.row_no, column=1, sticky='NSEW')
root = ctk.CTk()
root.configure(bg='#09275d')
root.geometry('1000x650')
Form(root)
root.mainloop()