Reputation: 49
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()
Upvotes: 1
Views: 783
Reputation: 46669
When a new monitor section is added, it is the inner frame (self.inner_frame
) get resized, not the canvas (self.canvas
). So the <Configure>
event should be bound on the inner frame instead of the canvas:
class Form(tk.Frame):
def __init__(self, root):
...
self.canvas.config(yscrollcommand=self.scrollbar.set)
### ----- don't bind on canvas
#self.canvas.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox(ALL)))
self.inner_frame = tk.Frame(self.canvas)
### ----- bind on inner frame instead
self.inner_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox(ALL)))
self.canvas.create_window((0,0), window=self.inner_frame, anchor='nw')
self.load()
...
Upvotes: 3
Reputation: 1
Try modifying the scrollregion. See the example at https://dafarry.github.io/tkinterbook/canvas.htm which uses event.x and event.y to get the size.
Upvotes: 0