I have created a scrollable frame with tkinter and would like to use the mousewheel for the scrolling, I am also switching frames as pages. Everything works as expected for page 2 however the page does not scroll with the mousewheel on page 1, the scrollbar itself works and the mousewheel event is being triggered its just not scrolling.
Here is an example of what I have so far:-
import tkinter as tk
class ScrollableFrame(tk.Frame):
def __init__(self, container, *args, **kwargs):
super().__init__(container, *args, **kwargs)
self.canvas = tk.Canvas(self)
scrollbar = tk.Scrollbar(self, command=self.canvas.yview)
self.scrollable_frame = tk.Frame(self.canvas)
self.scrollable_frame.bind(
"<Configure>",
lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")),
)
self.scrollable_frame.bind_all("<MouseWheel>", self._on_mousewheel)
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
self.canvas.configure(yscrollcommand=scrollbar.set)
self.canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
def _on_mousewheel(self, event):
caller = event.widget
if "scrollableframe" in str(caller):
if event.delta == -120:
self.canvas.yview_scroll(2, "units")
if event.delta == 120:
self.canvas.yview_scroll(-2, "units")
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("tkinter scrollable frame example.py")
self.geometry("700x450")
# set grid layout 1x2
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
# create navigation frame
self.navigation_frame = tk.Frame(self)
self.navigation_frame.grid(row=0, column=0, padx=20, pady=20, sticky="nsew")
# create navigation buttons
self.page1_button = tk.Button(
self.navigation_frame,
text="Page 1",
command=self.page1_button_event,
)
self.page1_button.grid(row=0, column=0, sticky="ew")
self.page2_button = tk.Button(
self.navigation_frame,
text="Page 2",
command=self.page2_button_event,
)
self.page2_button.grid(row=1, column=0, sticky="ew")
# create page1 frame
self.page1_frame = ScrollableFrame(self)
self.page1_frame.grid_columnconfigure(0, weight=1)
# create page1 content
self.page1_frame_label = tk.Label(
self.page1_frame.scrollable_frame, text="Page 1"
)
self.page1_frame_label.grid(row=0, column=0, padx=20, pady=10)
for i in range(50):
tk.Label(
self.page1_frame.scrollable_frame, text=str("Filler number " + str(i))
).grid(row=i + 1, column=0)
# create page2 frame
self.page2_frame = ScrollableFrame(self)
self.page2_frame.grid_columnconfigure(1, weight=1)
# create page2 content
self.page2_frame_label = tk.Label(
self.page2_frame.scrollable_frame, text="Page 2"
)
self.page2_frame_label.grid(row=0, column=0, padx=20, pady=10)
for i in range(50):
tk.Label(
self.page2_frame.scrollable_frame, text=str("Filler number " + str(i))
).grid(row=i + 1, column=0)
# show default frame
self.show_frame("page1")
# show selected frame
def show_frame(self, name):
self.page1_button.configure(
background=("red") if name == "page1" else "#F0F0F0"
)
self.page2_button.configure(
background=("red") if name == "page2" else "#F0F0F0"
)
if name == "page1":
self.page1_frame.grid(row=0, column=1, padx=0, pady=0, sticky="nsew")
else:
self.page1_frame.grid_forget()
if name == "page2":
self.page2_frame.grid(row=0, column=1, sticky="nsew")
else:
self.page2_frame.grid_forget()
def page1_button_event(self):
self.show_frame("page1")
def page2_button_event(self):
self.show_frame("page2")
if __name__ == "__main__":
app = App()
app.mainloop()
Any ideas where I have gone wrong?
The issue is caused by using .bind_all() which will make the final callback to be self.page2_frame._on_mousewheel(). Therefore self.canvas inside the function will refer to the canvas of self.page2_frame, that is why canvas in self.page1_frame cannot be scrolled by mouse wheel.
One of the solution is to find the correct canvas using the event.widget (i.e. the caller in your code):
def find_canvas(self, widget):
while not isinstance(widget, tk.Canvas):
widget = widget.master
return widget
def _on_mousewheel(self, event):
caller = event.widget
if "scrollableframe" in str(caller) and "canvas" in str(caller):
self.find_canvas(caller).yview_scroll(event.delta//-60, "units")
Related
I have a screen that I load a bunch of data to via excel. Then I have the option to clear the screen and do it again. The problem is, when I load again, if the frame was not scrolled back to the top, the screen is laggy and jittery until it gets back to the position it was at, or until the excel file is finished loading.
What I want to know is what do I need to add into my clear function, to reset the scrollbar to the top of the screen so that this does not happen anymore.
This minimal reproducible example just shows the scrollbar not going to the top when the screen is cleared.
Thanks
import tkinter as tk
class test(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title('TEST')
self.config(bg='snow3')
self.minsize(350, 250)
self.resizable(False, False)
def onFrameConfigure(self, canvas):
'''Reset the scroll region to encompass the inner frame'''
canvas.configure(scrollregion=canvas.bbox('all'))
self.excel_upload_frame = tk.Frame(self)
self.excel_upload_frame.config(height=650)
self.excel_upload_frame.grid(row=4, column=0, columnspan=10, rowspan=100, sticky='nsw')
self.canvas = tk.Canvas(self.excel_upload_frame, borderwidth=0)
self.scroll_area = tk.Frame(self.canvas)
self.scroll = tk.Scrollbar(self.excel_upload_frame, orient='vertical', command=self.canvas.yview)
self.canvas.config(width=530, height=493, yscrollcommand=self.scroll.set)
self.scroll_area.pack(fill=tk.BOTH)
self.canvas.pack(side='left', fill=tk.Y)
self.canvas.create_window((4, 4), window=self.scroll_area, anchor='nw')
self.scroll_area.bind('<Configure>',
lambda event, canvas=self.canvas: onFrameConfigure(self, canvas=canvas))
upload_button = tk.Button(self, text='Upload Excel File', font=('helvetica', '16'), bg='green',
command=lambda: test.excel_upload_page(self))
upload_button.grid(row=1, column=0, columnspan=10, pady=10)
clear_screen_btn = tk.Button(self, text=' Clear ', bg='yellow', command=lambda: test.clear_screen(self))
clear_screen_btn.grid(row=1, column=7, columnspan=2, pady=10)
def excel_upload_page(self):
for widgets in self.scroll_area.winfo_children():
widgets.destroy()
'''Reading the excel file and making it a pandas DF'''
for x in range(0,200):
label = tk.Label(self.scroll_area, text=f'TEST - {x}')
label.grid(row=x, column=0, pady=10)
self.scroll.pack(side='right', fill='y')
def clear_screen(self):
for row in self.scroll_area.grid_slaves():
row.grid_forget()
if __name__ == "__main__":
app = test()
app.mainloop()
You can call self.canvas.yview_moveto(0) at the end of both excel_upload_page() and clear_screen() functions to scroll back to the top.
def excel_upload_page(self):
for widgets in self.scroll_area.winfo_children():
widgets.destroy()
'''Reading the excel file and making it a pandas DF'''
for x in range(0,200):
label = tk.Label(self.scroll_area, text=f'TEST - {x}')
label.grid(row=x, column=0, pady=10)
self.scroll.pack(side='right', fill='y')
self.canvas.yview_moveto(0) # scroll back to the top
def clear_screen(self):
for row in self.scroll_area.grid_slaves():
row.grid_forget()
self.canvas.yview_moveto(0) # scroll back to the top
I'm working on a drag and drop app using tkinter; both of my frames have a scrollbar and, right now, when the text in the right is dragged is the right frame that scroll when using the mouse wheel, what I'd like to do is that when someone drag a text above the left frame then the left frame is the one that need to scroll when using the mouse wheel, while the right frame needs to be where it is without moving. I've really not idea how do so. This is my code:
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.first_frame = tk.LabelFrame(root, text="")
self.first_frame.pack(padx=15, pady=15, fill="both", expand="true", side="left", anchor="w")
self.first_canvas = tk.Canvas(self.first_frame, highlightthickness=0)
self.first_canvas.pack(side="left", fill="both", expand="true")
self.first_canvas.bind("<Enter>", lambda _: self.first_canvas.bind_all('<MouseWheel>', self.on_mousewheel_left))
self.first_canvas.bind("<Leave>", lambda _: self.first_canvas.unbind_all('<MouseWheel>'))
self.second_frame = tk.LabelFrame(root, text="")
self.second_frame.pack(padx=15, pady=15, fill="both", expand="true", side="left")
self.second_canvas = tk.Canvas(self.second_frame, highlightthickness=0)
self.second_canvas.pack(side="left", fill="both", expand="true")
self.second_canvas.bind("<Enter>", lambda _: self.second_canvas.bind_all('<MouseWheel>', self.on_mousewheel_right))
self.second_canvas.bind("<Leave>", lambda _: self.second_canvas.unbind_all('<MouseWheel>'))
self.upload_one()
self.upload_two()
def upload_one(self):
self.first_list = ["11:00", "03:43", "22:04", "17:21", "22:35", "07:01", "16:11", "09:00"]
self.first_scrollbar = ttk.Scrollbar(self.first_frame, orient="vertical", command=self.first_canvas.yview)
self.first_scrollbar.pack(side="right", fill="y")
self.first_canvas.configure(yscrollcommand=self.first_scrollbar.set)
self.first_canvas.bind('<Configure>', lambda e: self.first_canvas.configure(scrollregion = self.first_canvas.bbox("all")))
self.first_list_frame = tk.Frame(self.first_canvas)
self.first_canvas.create_window((0,0), window=self.first_list_frame, anchor="nw")
for index, cit in enumerate(sorted(self.first_list)):
self.one_frame = tk.LabelFrame(self.first_list_frame)
self.one_frame.grid(row=index, column=0, padx=10, pady=(20,0))
self.one_text = tk.Text(self.one_frame, width=77, height=4)
self.one_text.grid(row=1, column=1, rowspan=2, sticky="nw")
self.dragged_text = tk.Text(self.one_frame, wrap="word", width=68, height=3, highlightthickness=1, highlightcolor= "DodgerBlue3",)
self.dragged_text.grid(row=3, column=1, rowspan=2, columnspan=2, pady=(10,20), ipady=7, sticky="nw")
def upload_two(self):
self.second_list = ["morning\nmorning\nmorning", "afternoon\nafternoon\nafternoon", "evening\nevening\nevening", "nigth\nnight\nnight"]
self.second_scrollbar = ttk.Scrollbar(self.second_frame, orient="vertical", command=self.second_canvas.yview)
self.second_scrollbar.pack(side="right", fill="y")
self.second_canvas.configure(yscrollcommand=self.second_scrollbar.set)
self.second_canvas.bind('<Configure>', lambda e: self.second_canvas.configure(scrollregion = self.second_canvas.bbox("all")))
# can i remove this frame?
self.two_list_frame = tk.Frame(self.second_canvas)
self.second_canvas.create_window((0,0), window=self.two_list_frame, anchor="nw")
for index, two in enumerate(sorted(self.second_list)):
self.two_frame = tk.Frame(self.two_list_frame)
self.two_frame.pack(pady=(15,0), fill="x", expand="yes", side="top")
self.two_text = tk.Text(self.two_frame, font=("Times New Roman", 11), wrap="word", highlightthickness=1, highlightcolor= "DodgerBlue3", cursor="hand2", width=81, height=4)
self.two_text.grid(row=0, column=0, padx=15)
self.two_text.insert("end", str(two.strip()), "normal")
self.two_text.config(state="disabled")
self.two_text.bind('<Button-1>', self.click)
self.two_text.bind('<B1-Motion>',self.drag)
self.two_text.bind('<ButtonRelease-1>',self.release)
def on_mousewheel_left(self, event):
widget = event.widget.winfo_parent()
self.first_canvas.yview_scroll(int(-1*(event.delta//120)), 'units')
def on_mousewheel_right(self, event):
widget = event.widget.winfo_parent()
print(widget)
self.second_canvas.yview_scroll(int(-1*(event.delta//120)), 'units')
def click(self, event):
self.text = event.widget.get("1.0",'end-1c')
root.clipboard_clear()
root.clipboard_append(self.text)
self.top = tk.Toplevel(root)
self.top.attributes('-alpha', 0.7)
self.top.overrideredirect(True)
self.top._offsetx = event.x
self.top._offsety = event.y
x = self.top.winfo_pointerx() - self.top._offsetx - 30
y = self.top.winfo_pointery() - self.top._offsety - 30
self.top.geometry('+{x}+{y}'.format(x=x,y=y))
two_toplevel_label = tk.Label(self.top, text=self.text, wraplength=571, justify="left", bg="SystemButtonFace")
two_toplevel_label.grid(row=0, column=0)
def drag(self, event):
x = self.top.winfo_pointerx() - self.top._offsetx
y = self.top.winfo_pointery() - self.top._offsety
self.two_text.tag_remove("sel", "1.0", "end")
self.top.geometry(f"+{x}+{y}")
def release(self, event):
widget = event.widget.winfo_containing(event.x_root, event.y_root)
self.top.destroy()
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
I need to change the background color and disable the button that is clicked in the following code. I have tried to pass button in the command for the button but it doesn't seem to work.
I tried adding to my lambda call on the button variable: btn=button and then passing that through my function call, but I get the error "UnboundLocalError: local variable 'button' referenced before assignment"
How can i add to my disable_btn() function to disable the button and turn it grey.
Thanks
import tkinter as tk
from tkinter import font as tkfont
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.frames["StartPage"] = StartPage(parent=container, controller=self)
self.frames["StartPage"].grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.grid(columnspan=5, rowspan=5)
self.populate = tk.Button(self, text='Populate Data', command=lambda : self.data())
self.populate.pack(side='top')
self.frame = tk.Frame(self)
self.frame.pack()
self.canvas = tk.Canvas(self.frame, borderwidth=0)
self.canvas.pack(side='left', fill='both', expand=True)
def data(self):
row = 1
for x in range(5):
print(x)
label = tk.Label(self.canvas, text='Line {}'.format(row))
label.grid(row=row, column=1, padx=15, pady=15)
text = tk.Text(self.canvas, width=5, height=1)
text.grid(row=row, column=2, padx=15, pady=15)
button = tk.Button(self.canvas, text='Click me', bg='white', command=lambda line=row:stored_functions.disable_btn(self, line))
button.grid(row=row, column=3, padx=15, pady=15)
row+=1
class stored_functions():
def disable_btn(self, line):
print('Disabled the button on line {}'.format(line))
btn.configure(state='disabled', bg='grey')
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
You need to pass the button widget as an argument to the function associated with its command. However you can't do it in the same call as the one that creates the Button because it doesn't exist yet. A simple way to workaround that impediment is to do it in two steps:
def data(self):
row = 1
for x in range(5):
print(x)
label = tk.Label(self.canvas, text='Line {}'.format(row))
label.grid(row=row, column=1, padx=15, pady=15)
text = tk.Text(self.canvas, width=5, height=1)
text.grid(row=row, column=2, padx=15, pady=15)
button = tk.Button(self.canvas, text='Click me', bg='white')
button.grid(row=row, column=3, padx=15, pady=15)
button.config(command=lambda btn=button, line=row: disable_btn(btn, line))
row += 1
def disable_btn(btn, line):
print('Disabled the button on line {}'.format(line))
btn.configure(state='disabled', bg='grey')
I need this simple form to:
1) correctly expand the fields when the window size is adjusted and
2) correctly scroll the list of fields.
I've tried every way I can think of and only 1 of the 2 above conditions are ever true. This code expands properly but does not scroll. Without frame2, and adding the fields to frame or canvas the opposite is true.
class test(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def makeform(self, root, fields):
i = 0
for field in fields:
Label(root, text=field + ": ", anchor=W).grid(row=i)
entry = Entry(root)
entry.grid(row=i, column=1, sticky=E+W)
entries[field] = entry
i += 1
def initialize(self):
frame = Frame(self)
frame.rowconfigure(0, weight=1)
frame.columnconfigure(0, weight=1)
frame.grid(sticky=N+S+E+W)
canvas = Canvas(frame, width=900, height=800, bg='pink')
canvas.grid(row=0, column=0, sticky=N+S+E+W)
canvas.columnconfigure(0,weight=1)
canvas.rowconfigure(0,weight=1)
frame2 = Frame(canvas)
frame2.grid(row=0, column=0, sticky=N+S+E+W)
frame2.columnconfigure(1, weight=1)
vscrollbar = Scrollbar(frame2,orient=VERTICAL)
vscrollbar.grid(row=0, column=2, sticky=N+S)
vscrollbar.config(command=canvas.yview)
canvas.configure(yscrollcommand=vscrollbar.set)
self.grid_rowconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
names = {'a','long','list','of','names','here'}
self.makeform(frame2, names)
Button(self, text='Quit', command=self.quit).grid(row=1, column=0, sticky=W, pady=4)
canvas.create_window(0, 0)
canvas.config(scrollregion=canvas.bbox(ALL))
self.grid()
if __name__ == "__main__":
entries = {}
app = test(None)
app.title('Hi ')
app.mainloop()
Update
Integrating Bryan's example below, this works for scrolling but does not expand the fields when the window is resized. I tried adding weight=1 to the second column of the frame but it does not help. How do I prevent the frame from shrinking?
class test(Tkinter.Frame):
def __init__(self,parent):
Tkinter.Frame.__init__(self, root)
self.canvas = Tkinter.Canvas(root, borderwidth=0, background="#ffffff")
self.frame = Tkinter.Frame(self.canvas, background="#ffffff")
self.vsb = Tkinter.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((0,0), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
self.populate()
def onFrameConfigure(self, event):
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def makeform(self, fields):
i = 0
for field in fields:
Label(self.frame, text=field + ": ", anchor=W).grid(row=i)
entry = Entry(self.frame)
entry.grid(row=i, column=1, sticky=E+W)
entries[field] = entry
i += 1
Button(self.frame, text='Quit', command=self.quit).grid(row=i, column=0, sticky=W, pady=4)
def populate(self):
names = {'a','long','list','of','names','here'}
self.makeform(names)
self.frame.columnconfigure(1, weight=1)
self.frame.grid_columnconfigure(1, weight=1)
if __name__ == "__main__":
entries = {}
root=Tk()
test(root).pack(side="top", fill="both", expand=True)
root.mainloop()
There are at least three problems in your code.
First, the frame to be scrolled must be a part of the canvas. You can't use pack or grid to place it in the canvas, you must use create_window. You're calling create_window but you aren't telling it what window to add.
Second, the scrollbars are children of the frame, but I'm assuming those are the scrollbars you want to scroll the canvas. The need to be outside of the inner frame, and outside of the canvas.
Third, you need to set up a binding to the canvas's <Configure> event so that you can resize the inner frame and recompute the scrollregion of the canvas.
A complete working example of a scrollable frame is in this answer: https://stackoverflow.com/a/3092341/7432
Based on the example from Dynamically changing scrollregion of a canvas in Tkinter, I am trying to implement a Frame where you can add and delete entries in a scrollable Frame using tkinter. My Problem is that the Frame holding items does not resize after deleting entries. When adding entries, it resizes correctly. I call update_layout() in both cases:
from tkinter import *
class ScrollableContainer(Frame):
"""A scrollable container that can contain a number of messages"""
def __init__(self, master, **kwargs):
Frame.__init__(self, master, **kwargs) #holds canvas & scrollbars
# configure row and col to take additional space if there is some
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
# create canvas
self.canv = Canvas(self, bd=0, highlightthickness=0)
# create scrollbars
self.hScroll = Scrollbar(self, orient='horizontal',
command=self.canv.xview)
self.hScroll.grid(row=1, column=0, sticky='we')
self.vScroll = Scrollbar(self, orient='vertical',
command=self.canv.yview)
self.vScroll.grid(row=0, column=1, sticky='ns')
# set postiotion of canvas in (self-)Frame
self.canv.grid(row=0, column=0, sticky='nsew')
self.canv.configure(xscrollcommand=self.hScroll.set,
yscrollcommand=self.vScroll.set)
# create frame to hold messages in canvas
self.frm = Frame(self.canv, bd=2, bg='gray') #holds messages
self.frm.grid_columnconfigure(0, weight=1)
# create empty tkinter widget (self.frm) on the canvas
self.canv.create_window(0, 0, window=self.frm, anchor='nw', tags='inner')
# update layout
self.update_layout()
# on change of size or location this event is fired. The event provides new width an height to callback function on_configure
self.canv.bind('<Configure>', self.on_configure)
self.widget_list = []
# update and resize layout
def update_layout(self):
print('update')
self.frm.update_idletasks()
self.canv.configure(scrollregion=self.canv.bbox('all'))
self.size = self.frm.grid_size()
# resize canvas and scroll region depending on content
def on_configure(self, event):
print('on_configure')
# get new size of canvas
w,h = event.width, event.height
# get size of frm required to display all content
natural = self.frm.winfo_reqwidth()
self.canv.itemconfigure('inner', width= w if w>natural else natural)
self.canv.configure(scrollregion=self.canv.bbox('all'))
# add new entry and update layout
def add_message(self, text):
print('add message')
# create var to represent states
int_var = IntVar()
cb = Checkbutton(self.frm, text=text, variable=int_var)
cb.grid(row=self.size[1], column=0, padx=1, pady=1, sticky='we')
self.widget_list.append(cb)
self.update_layout()
# delete all messages
def del_message(self):
print('del message')
for it in self.widget_list:
it.destroy()
self.update_layout()
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
sc = ScrollableContainer(root, bd=2, bg='black')
sc.grid(row=0, column=0, sticky='nsew')
def new_message():
test = 'Something Profane'
sc.add_message(test)
def del_message():
sc.del_message()
b = Button(root, text='New Message', command=new_message)
b.grid(row=1, column=0, sticky='we')
del_b = Button(root, text='Del Message', command=del_message)
del_b.grid(row=2, column=0, sticky='we')
root.mainloop()
I was working on something similar so I took my code and merged it with yours for the answer.
Here is a scrollingFrame class that will add scrollbars and remove them whenever the box is resized. Then there is a second class for your message list that will tell the scrollingFrame to readjust itself as necessary whenever items are added/deleted.
class scrollingFrame(Frame):
def __init__(self, parentObject, background):
Frame.__init__(self, parentObject, background = background)
self.canvas = Canvas(self, borderwidth=0, background = background, highlightthickness=0)
self.frame = Frame(self.canvas, background = background)
self.vsb = Scrollbar(self, orient="vertical", command=self.canvas.yview, background=background)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.grid(row=0, column=1, sticky=N+S)
self.hsb = Scrollbar(self, orient="horizontal", command=self.canvas.xview, background=background)
self.canvas.configure(xscrollcommand=self.hsb.set)
self.hsb.grid(row=1, column=0, sticky=E+W)
self.canvas.grid(row=0, column=0, sticky=N+S+E+W)
self.window = self.canvas.create_window(0,0, window=self.frame, anchor="nw", tags="self.frame")
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.frame.bind("<Configure>", self.onFrameConfigure)
self.canvas.bind("<Configure>", self.onCanvasConfigure)
def onFrameConfigure(self, event):
#Reset the scroll region to encompass the inner frame
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def onCanvasConfigure(self, event):
#Resize the inner frame to match the canvas
minWidth = self.frame.winfo_reqwidth()
minHeight = self.frame.winfo_reqheight()
if self.winfo_width() >= minWidth:
newWidth = self.winfo_width()
#Hide the scrollbar when not needed
self.hsb.grid_remove()
else:
newWidth = minWidth
#Show the scrollbar when needed
self.hsb.grid()
if self.winfo_height() >= minHeight:
newHeight = self.winfo_height()
#Hide the scrollbar when not needed
self.vsb.grid_remove()
else:
newHeight = minHeight
#Show the scrollbar when needed
self.vsb.grid()
self.canvas.itemconfig(self.window, width=newWidth, height=newHeight)
class messageList(object):
def __init__(self, scrollFrame, innerFrame):
self.widget_list = []
self.innerFrame = innerFrame
self.scrollFrame = scrollFrame
# Keep a dummy empty row if the list is empty
self.placeholder = Label(self.innerFrame, text=" ")
self.placeholder.grid(row=0, column=0)
# add new entry and update layout
def add_message(self, text):
print('add message')
self.placeholder.grid_remove()
# create var to represent states
int_var = IntVar()
cb = Checkbutton(self.innerFrame, text=text, variable=int_var)
cb.grid(row=self.innerFrame.grid_size()[1], column=0, padx=1, pady=1, sticky='we')
self.widget_list.append(cb)
self.innerFrame.update_idletasks()
self.scrollFrame.onCanvasConfigure(None)
# delete all messages
def del_message(self):
print('del message')
for it in self.widget_list:
it.destroy()
self.placeholder.grid()
self.innerFrame.update_idletasks()
self.scrollFrame.onCanvasConfigure(None)
deviceBkgColor = "#FFFFFF"
root = Tk() # Makes the window
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.wm_title("Title") # Makes the title that will appear in the top left
root.config(background = deviceBkgColor)
myFrame = scrollingFrame(root, background = deviceBkgColor)
myFrame.grid(row=0, column=0, sticky=N+S+E+W)
msgList = messageList(myFrame, myFrame.frame)
def new_message():
test = 'Something Profane'
msgList.add_message(test)
def del_message():
msgList.del_message()
b = Button(root, text='New Message', command=new_message)
b.grid(row=1, column=0, sticky='we')
del_b = Button(root, text='Del Message', command=del_message)
del_b.grid(row=2, column=0, sticky='we')
root.mainloop() #start monitoring and updating the GUI