After several tests and help received, I managed to get the code for a scroll with several checkboxes inside. My current problem is that the scroll is much larger than the space it needs and in general I can't change its size to my liking.
This is my code:
import tkinter as tk
class CheckboxList(tk.Frame):
def __init__(self, master=None, **kw):
super().__init__(master, **kw)
self.checkbuttons = []
self.vars = []
self.canvas = tk.Canvas(self, bg='white', bd=0, highlightthickness=0)
self.yscroll = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.frame = tk.Frame(self.canvas)
self.canvas.create_window((0, 0), window=self.frame, anchor='nw')
self.canvas.configure(yscrollcommand=self.yscroll.set)
self.canvas.grid(row=0,column=0, sticky='nsew')
self.yscroll.grid(row=0, column=1, sticky='nse')
for i in range(20):
var = tk.IntVar(value=0)
cb = tk.Checkbutton(self.frame, text=f"checkbutton #{i}", variable=var, onvalue=1, offvalue=0)
cb.grid(row=i, column=0, sticky='w')
self.checkbuttons.append(cb)
self.vars.append(var)
self.frame.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox("all"))
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
root = tk.Tk()
cl = CheckboxList(root, width=20, height=10)
cl.grid(row=0,column=0,sticky='nsew')
root.mainloop()
I have made several tests by changing the values of "grid" but I can not. I wish I could have more control over the size.
As you can see from the image, there is a lot of white space left and I would like to be able to change the overall height as well
EDIT:
Working code:
class CheckboxList(tk.Frame):
def __init__(self, master=None, **kw):
super().__init__(master, **kw)
self.checkbuttons = []
self.vars = []
self.canvas = tk.Canvas(self, bg='white', bd=0, highlightthickness=0)
#self.canvas = tk.Canvas(self, bg='white', bd=0,width=115, highlightthickness=0)
self.yscroll = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.frame = tk.Frame(self.canvas)
self.canvas.create_window((0, 0), window=self.frame, anchor='nw')
self.canvas.configure(yscrollcommand=self.yscroll.set)
self.canvas.grid(row=0,column=0, sticky='nsew')
self.yscroll.grid(row=0, column=1, sticky='nse')
self.frame.bind("<Configure>", lambda e: self.canvas.config(width=e.width, scrollregion=self.canvas.bbox("all")))
for i in range(20):
var = tk.IntVar(value=0)
cb = tk.Checkbutton(self.frame, text=f"checkbutton #{i}", variable=var, onvalue=1, offvalue=0)
cb.grid(row=i, column=0, sticky='w')
self.checkbuttons.append(cb)
self.vars.append(var)
self.frame.update_idletasks()
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
Add width in line 8.
Change this:
self.canvas = tk.Canvas(self, bg='white', bd=0, highlightthickness=0)
to:
self.canvas = tk.Canvas(self, bg='white', bd=0,width=115, highlightthickness=0)
Screenshot:
You can set the width of self.canvas same as self.frame whenever self.frame is resized via callback of event <Configure> on self.frame. Note also that you need to update scrollregion of the canvas when the size of self.frame is changed.
...
self.frame = tk.Frame(self.canvas)
self.frame.bind("<Configure>", lambda e: self.canvas.config(width=e.width, scrollregion=self.canvas.bbox("all")))
...
# scrollregion will be updated via the above event binding on self.frame, so below line is not necessary
#self.canvas.config(scrollregion=self.canvas.bbox("all"))
...
Related
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'm trying to create a GUI for a sort of chat client. I'm fetching the chat with async and I am using multiprocessing to pipe the data to TKinter. That all works fine, but the problem I am currently having is when the new messages come in, they get added to the canvas and I can't scroll and the canvas is horizontally small so I would only be able to see the first two letters of the message anyways. I am like completely new to Python GUI so this is probably a really simple solution.
Here's the code that I have right now:
import tkinter as tk
class App(tk.Tk):
def __init__(self, pipe, *args, **kw):
super().__init__(*args, **kw)
self.app_pipe, _ = pipe
self.check_interval = 250
global message_queue
message_queue = []
self.configure(bg='gray')
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
frame_main = tk.Frame(self, bg='gray')
frame_main.grid(sticky='news')
frame_main.columnconfigure(0, weight=1)
label1 = tk.Label(frame_main, text='Channel')
label1.grid(row=0, column=0, sticky='nw')
frame_canvas = tk.Frame(frame_main, bg='red')
frame_canvas.grid(row=1, column=0, sticky='nw')
canvas = tk.Canvas(frame_canvas, bg='yellow')
canvas.grid(row=0, column=0)
vsb = tk.Scrollbar(canvas, orient='vertical', command=canvas.yview)
vsb.grid(row=0, column=1, sticky='ns')
canvas.configure(yscrollcommand=vsb.set)
global names_frame
names_frame = tk.Frame(canvas)
canvas.create_window((0, 0), window=names_frame, anchor='nw')
frame_canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox('all')))
self.bind('<<newMessage>>', self.add_message)
self.after(self.check_interval, self.check)
def check(self):
while self.app_pipe.poll():
msg = self.app_pipe.recv()
if len(message_queue) < 200:
message_queue.append(msg)
elif len(message_queue) == 199:
message_queue.pop(0)
message_queue.append(msg)
self.event_generate('<<newMessage>>', when='tail')
self.after(self.check_interval, self.check)
def add_message(self, *args):
if message_queue[-1]['command'] == 'message':
message = message_queue[-1]['data']
print(message.author.name, len(message_queue))
name = tk.Label(names_frame, text=message.author.name)
name.grid(row=len(message_queue)-1, column=0)
This is what the GUI ends up looking like:
There are two issues:
You have created the scrollbar vsb as a child of canvas. It should be a child of frame_canvas instead
You need to update the scrollregion of canvas whenever names_frame is resized, not frame_canvas
class App(tk.Tk):
def __init__(self, pipe, *args, **kw):
super().__init__(*args, **kw)
self.app_pipe, _ = pipe
self.check_interval = 250
global message_queue
message_queue = []
self.configure(bg='gray')
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
frame_main = tk.Frame(self, bg='gray')
frame_main.grid(sticky='news')
frame_main.columnconfigure(0, weight=1)
label1 = tk.Label(frame_main, text='Channel')
label1.grid(row=0, column=0, sticky='nw')
frame_canvas = tk.Frame(frame_main, bg='red')
frame_canvas.grid(row=1, column=0, sticky='nw')
canvas = tk.Canvas(frame_canvas, bg='yellow')
canvas.grid(row=0, column=0)
# should create 'vsb' as child of 'frame_canvas' instead
vsb = tk.Scrollbar(frame_canvas, orient='vertical', command=canvas.yview)
vsb.grid(row=0, column=1, sticky='ns')
canvas.configure(yscrollcommand=vsb.set)
global names_frame
names_frame = tk.Frame(canvas)
canvas.create_window((0, 0), window=names_frame, anchor='nw')
# should bind '<Configure>' on 'names_frame' instead
names_frame.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox('all')))
self.bind('<<newMessage>>', self.add_message)
self.after(self.check_interval, self.check)
...
Note that it is better to make message_queue and names_frame instance variables instead of global variables.
I'm added canvas and a scroll bar to one of the frames in my script.
However somethings wrong cause the scroll bar is off (lower bottom is not visible) and the text I drew is off. Could anyone please tell me whats the problem ? I want the canvas to fill the whole frame (obviously without the scroll bar)
import sys
import os
if sys.version_info[0] < 3:
import Tkinter as tk
import ttk as ttk
else:
import tkinter as tk
import tkinter.ttk as ttk
#
# LeftMiddle
#
class LeftMiddle(tk.Frame):
def __init__(self, master=None):
self.parent = master
tk.Frame.__init__(self, self.parent, bg='bisque', borderwidth=1, relief="sunken")
self.__create_layout()
self.draw_text()
def __create_layout(self):
self.canvas = tk.Canvas(self, bg="green", relief=tk.SUNKEN)
self.canvas.config(width=20, height=10)
self.canvas.config(highlightthickness=0)
self.sbar = tk.Scrollbar(self, orient=tk.VERTICAL)
self.sbar.pack(side=tk.RIGHT, fill=tk.Y)
self.canvas.pack(side=tk.LEFT, expand="YES", fill=tk.BOTH)
def draw_text(self):
self.canvas.create_text(0, 0, text='1234567890', fill='red')
self.canvas.create_text(0, 25, text='ABCDEFGH', fill='blue')
#
# MainWindow
#
class MainWindow(tk.Frame):
def __init__(self, master=None):
self.parent = master
tk.Frame.__init__(self, self.parent, bg='bisque', borderwidth=1, relief="sunken")
self.__create_layout()
def __create_layout(self):
self.frame1 = tk.Frame(self, bg="yellow")
self.frame2 = tk.Frame(self, bg="blue")
self.frame3 = LeftMiddle(self) # tk.Frame(self, bg="green")
self.frame4 = tk.Frame(self, bg="brown")
self.frame5 = tk.Frame(self, bg="pink")
self.frame1.grid(row=0, column=0, rowspan=4, columnspan=8, sticky=(tk.N, tk.S, tk.W, tk.E))
self.frame2.grid(row=0, column=8, rowspan=4, columnspan=2, sticky=(tk.N, tk.S, tk.W, tk.E))
self.frame3.grid(row=4, column=0, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))
self.frame4.grid(row=4, column=5, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))
self.frame5.grid(row=5, column=0, rowspan=1, columnspan=10, sticky=(tk.N, tk.S, tk.W, tk.E))
for r in range(6):
self.rowconfigure(r, weight=1)
for c in range(10):
self.columnconfigure(c, weight=1)
#
# MAIN
#
def main():
root = tk.Tk()
root.title("Frames")
root.geometry("550x300+525+300")
root.configure(background="#808080")
root.option_add("*font", ("Courier New", 9, "normal"))
window = MainWindow(master=root)
window.pack(side="top", fill="both", expand=True)
root.mainloop()
if __name__ == '__main__':
main()
You have overlapping frames. Both self.frame3 and self.frame4 are in row 4 with a rowspan of 2, meaning they occupy rows 4 and 5. self.frame5 is also in row 5. So, self.frame5 is obscuring the bottom half of self.frame3, the frame that contains the canvas.
I don't understand why you have so many rowspans, they seem completely unnecessary unless you have some specific reason why you want multiple rows and columns but only single frames that span these rows and columns. Looking at the screenshot I see the need for only three rows.
The reason the text seems off is that by default the text is centered over the coordinate you give. You might want to look at the anchor option for the create_text method.
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