My app displays data from a sqlite database. After displaying the initial data, say 50 records when my program destroys the widgets associated with that initial display and repopulates based on a search function the frame container no longer adapts to the size of the new widgets placed in it.
To make this simpler I've created smaller simpler version of the problem. My initial version of this post was from that actual app and difficult understand. Lesson learned. Running this code defaults to showin 50 rows initially, Then try entering 100 in the search field and click the button and see that the window does not expand to fit. Then can then try entering 40 and see that the window doesn't shrink. I'm using Python v3.11.
Here's the simplified code that captures the essence of my problem:
from tkinter import *
from tkinter import ttk
def displayData():
for widgets in second_frame.winfo_children():
widgets.destroy()
Count = Search.get()
print("displayData entered with value of " + str(Count))
nRows = int(Count)
rows = []
for i in range(nRows):
label_list = Label(second_frame, text='Row '+str(i), relief=GROOVE, font=("Arial 11"), width=17, anchor='w', justify=LEFT)
label_list.grid(row=i, column=0, sticky=NSEW)
rows.append(label_list)
def _on_mousewheel(event):
my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# Set up tkinter GUI
root = Tk()
root.geometry("1250x810+100+0")
root.title("Test")
# Control Frame
control_frame = Frame(root, height=10, highlightbackground="blue", highlightthickness=2, pady=3, padx=3)
control_frame.pack(fill=X)
Search = StringVar()
Search.set('50')
ent_search = ttk.Entry(control_frame, width=15, textvariable=Search)
ent_search.pack(padx=5, pady=5, side=RIGHT)
# Create a Main Frame
main_frame = Frame(root, highlightbackground="yellow", highlightthickness=2)
main_frame.pack(fill=BOTH, expand=1)
# Create a Canvas
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_canvas.bind_all("<MouseWheel>", _on_mousewheel)
# Add a Scrollbar to the Canvas
my_scrollbar = ttk.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
# Configure the Canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion = my_canvas.bbox('all')))
# Create Another Frame inside the Canvas
second_frame = Frame(my_canvas)
# Add the New Frame to a Window inside the Canvas
my_canvas.create_window((0,100), window=second_frame, anchor='nw')
btn_search = Button(control_frame, text='Search', command=displayData)
btn_search.pack(padx=5, pady=5, side=RIGHT)
rows = []
displayData()
root.mainloop()
```
FYI: my actual app is displaying database records based on search parameters. The programs works fine except for the fact once the initial size of 'second_frame' is set up it never changes. So if a search happens to display more records than that initial display, those records will be hidden. e.g. Initial display shows 50 records, if a search asks to display 75 records, 25 of them will not be visible...So the second_frame doesn't resize to show the added widgets in the search.
My workaround for now is just to initially display more records then I anticipate most searches will need to display.
How can I make 'second_frame' adapt to new amounts of widgets on new searches? The simplified code above emulates my issue.
Yahoo, found a solution to this issue. Essentially one must first destroy and recreate the canvas used to display the records. Some complications due to the scrollbar widget being linked to the frame containing the canvas. I solved this by creating an intermediate frame to contain the frame referenced by the scrollbar and the used the methods winfo_childred() and destroy() to delete the frames/canvas/widgets before recreating them anew to display the new data.
Here's the modified example code that now works correctly:
from tkinter import *
from tkinter import ttk
def makeCanvas():
# Create a Canvas
global my_canvas
global second_frame
global my_scrollbar
global main_frame
mid_frame = Frame(main_frame)
mid_frame.pack(fill=BOTH, expand=1)
my_canvas = Canvas(mid_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_canvas.bind_all("<MouseWheel>", _on_mousewheel)
# Add a Scrollbar to the Canvas
my_scrollbar = ttk.Scrollbar(mid_frame, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
# Configure the Canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion = my_canvas.bbox('all')))
# Create Another Frame inside the Canvas
second_frame = Frame(my_canvas)
# Add the New Frame to a Window inside the Canvas
my_canvas.create_window((0,0), window=second_frame, anchor='nw')
def displayData():
global displayCnt
print("displayData entered with displayCnt of " + str(displayCnt))
if displayCnt != 0:
for widgets in main_frame.winfo_children():
widgets.destroy()
Count = Search.get()
makeCanvas()
print("displayData entered with value of " + str(Count))
nRows = int(Count)
rows = []
for i in range(nRows):
label_list = Label(second_frame, text='Row '+str(i), relief=GROOVE, font=("Arial 11"), width=17, anchor='w', justify=LEFT)
label_list.grid(row=i, column=0, sticky=NSEW)
rows.append(label_list)
displayCnt += 1
def _on_mousewheel(event):
my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# Set up tkinter GUI
root = Tk()
root.geometry("1250x810+100+0")
root.title("Test")
# Control Frame
control_frame = Frame(root, height=10, highlightbackground="blue", highlightthickness=2, pady=3, padx=3)
control_frame.pack(fill=X)
Search = StringVar()
Search.set('50')
ent_search = ttk.Entry(control_frame, width=15, textvariable=Search)
ent_search.pack(padx=5, pady=5, side=RIGHT)
# Create a Main Frame
main_frame = Frame(root, highlightbackground="yellow", highlightthickness=2)
main_frame.pack(fill=BOTH, expand=1)
# Create a Canvas
#makeCanvas()
btn_search = Button(control_frame, text='Search', command=displayData)
btn_search.pack(padx=5, pady=5, side=RIGHT)
rows = []
displayCnt = 0
displayData()
root.mainloop()
I ended up creating a separate function makeCanvas() to recreate the frame/canvas structure upon each new display of data. For this much simplified example the data consists simply of numbered Label widgets.