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
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()
CodePudding user response:
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()
...
CodePudding user response:
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.