I have tried to add a scrollbar through canvas and added a frame named frametwo in canvas. I am adding a few buttons and a table in that frame but nothing is visible. If I add all these things in the root then they become visible. I have tried different things but nothing worked.
Here is the code that I wrote
import myvariant
from tkinter import ttk
from tkinter import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from pandas import DataFrame
from PIL import ImageTk, Image
rsid_list=[8,9,5,5]
mv = myvariant.MyVariantInfo()
def main():
main_window = Tk()
app = info(main_window)
main_window.mainloop()
class info:
def __init__(self, root):
self.root = root
self.root.title('VCESS-ExAC')
self.root.geometry('1600x800+0+0')
self.root.configure(background='light grey')
main_frame = Frame(self.root)
main_frame.pack(fill=BOTH, expand=1, padx=0, pady=0)
main_frame.place(x=0, y=0, width=1600, height=800)
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_scroll = ttk.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
my_scroll.pack(side=RIGHT, fill=Y)
my_canvas.configure(yscrollcommand=my_scroll.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.config(scrollregion=my_canvas.bbox(ALL)))
self.frametwo = Frame(my_canvas)
my_canvas.create_window((0, 0), window=self.frametwo, anchor='nw')
table1 = LabelFrame(self.root, text="Retreived Data") ################
table1.pack(fill="both", expand="yes", padx=0, pady=0) ###################
table1.place(x=40, y=250, width=250, height=380)
table = ttk.Treeview(table1, height="8") #################
table['columns'] = ['rsID']
table.column('#0', width=120, minwidth=25)
table.column('rsID', anchor=W, width=120)
table.heading('#0', text='Serial No.', anchor=W)
table.heading('rsID', text='rsID', anchor=W)
for i in range(len(rsid_list)):
table.insert(parent='', index='end', iid=i, text=i + 1,
values=(rsid_list[i]))
table.place(x=0, y=0) ##########################
# VERTICAL SCROLLBAR
yscrollbar = ttk.Scrollbar(table1, orient=VERTICAL, command=table.yview) #############
yscrollbar.pack(side=RIGHT, fill='y') ##################
# HORIZONTAL SCROLLBAR
xscrollbar = ttk.Scrollbar(table1, orient=HORIZONTAL, command=table.xview) ###################
xscrollbar.pack(side=BOTTOM, fill='x') #######################
table.configure(yscrollcommand=yscrollbar.set, xscrollcommand=xscrollbar.set) ##############
table.pack(side=LEFT)
btn_download = Button(self.frametwo, text='Save File',
font=("Times New Roman", 14, 'bold'), bd=3, relief=RIDGE,
cursor='hand2', bg='#154857', fg='white', activeforeground='white',
activebackground='#154857')
btn_download.place(x=190, y=640, width=120)
btn_graph = Button(self.frametwo, text='Graph',
font=("Times New Roman", 14, 'bold'), bd=3, relief=RIDGE,
cursor='hand2', bg='#154857', fg='white', activeforeground='white',
activebackground='#154857')
btn_graph.place(x=530, y=640, width=120)
if __name__ == '__main__':
main()
Looking forward for any possible solution.
You have created the buttons inside self.frametwo. You are using place, which means that the buttons don't affect the size of the frame. Since you don't give self.frametwo a size, it defaults to one pixel wide and one pixel tall. Therefore, the frame is essentially invisible and thus all buttons inside the frame are invisible.
You can easily see this by switching to using pack or grid for the buttons. When you use pack or grid, the parent frame by default will grow or shrink to fit its children. Thus, using either of these for the buttons will cause the frame to grow just big enough to show the buttons.
I am adding ... a table in that frame but nothing is visible.
You are not adding the table to the frame, you are adding it to the root window. If you want to add it to the frame, you must use the frame as its parent. And again, you should probably not use place. place is almost never the right choice unless you are prepared to do a lot of extra work to make sure widgets are visible and responsive to changes in widget size, font size, display resolution, etc.
Related
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.
I am trying to get my scrollable canvas to work. It works when I pack the elements using .pack, however when I insert the elements via .place, the scrollbar stops working. Here is a minimal reproducable example of my code.
startup.py file:
import frame as f
import placeWidgetsOnFrame as p
p.populate3()
f.window.mainloop()
frame.py file:
#Creates widnow
window = customtkinter.CTk()
window.geometry("1900x980")
customtkinter.set_appearance_mode("dark")
window.resizable(False, False)
#Creates Frame for GUI
mainFrame = customtkinter.CTkFrame(window, width=1900, height=980, corner_radius=0)
mainFrame.pack(expand=True, fill=tk.BOTH)
mainFrame.pack_propagate(False)
topFrame = customtkinter.CTkFrame(master=mainFrame, width=1865, height=140, corner_radius=10)
topFrame.grid(columnspan=2, padx=15, pady=15)
topFrame.pack_propagate(0)
leftFrame = customtkinter.CTkFrame(master=mainFrame, width=380, height=530, corner_radius=10)
leftFrame.grid(row=1, column=0, padx=15, pady=10)
leftFrame.pack_propagate(False)
rightFrame = customtkinter.CTkFrame(master=mainFrame, width=1450, height=775, corner_radius=10)
rightFrame.grid(row=1, column=1, padx=15, pady=10, rowspan=2)
rightFrame.pack_propagate(False)
bottomLeftFrame = customtkinter.CTkFrame(mainFrame, width=380, height=220, corner_radius=10)
bottomLeftFrame.grid(row=2, column=0, padx=15, pady=10)
bottomLeftFrame.pack_propagate(False)
#Creates Scrollbar for right Frame
#Creates a canvas for the right Frame
canvas2=tk.Canvas(rightFrame, bg="#000000", highlightthickness=0, relief="flat")
canvas2.pack(side="left", fill="both", expand=True)
#Creates a scroll bar for the right Frame
scrollbar = customtkinter.CTkScrollbar(master=rightFrame, orientation="vertical", command=canvas2.yview, corner_radius=10)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
#Configures scrollbar to canvas
canvas2.configure(yscrollcommand=scrollbar.set)
canvas2.bind("<Configure>", lambda *args, **kwargs: canvas2.configure(scrollregion=canvas2.bbox("all")))
#Creates a scrollable frame to place widgets on
scrollableFrame = customtkinter.CTkFrame(canvas2, fg_color=("#C0C2C5", "#343638"), corner_radius=10)
canvasFrame = canvas2.create_window((0,0), window=scrollableFrame, anchor="nw", tags=("cf"))
#TO DO - resize canvas and to fit all widgets
def handleResize(event):
c = event.widget
cFrame = c.nametowidget(c.itemcget("cf", "window"))
minWidth = cFrame.winfo_reqwidth()
minHeight = cFrame.winfo_reqheight()
print (event.width)
print (event.height)
if minWidth < event.width:
c.itemconfigure("cf", width=event.width)
if minHeight < event.height:
c.itemconfigure("cf", height=event.height)
print (event.width)
print (event.height)
c.configure(scrollregion=c.bbox("all"))
canvas2.bind('<Configure>', handleResize)
def onMousewheel(event):
canvas2.yview_scroll(-1 * round(event.delta / 120), "units")
canvas2.bind_all("<MouseWheel>", onMousewheel)
canvas2.bind("<Destroy>", lambda *args, **kwargs: canvas2.unbind_all("<MouseWheel>"))
placeWidgetsOnFrame.py file:
import tkinter
import customtkinter
import frame as f
rightFrame = f.scrollableFrame
def populate2():
for i in range(30):
emailLabel = customtkinter.CTkLabel(master=rightFrame, text="Please enter your email:")
emailLabel.pack(padx=10, pady=10)
def populate3():
x=50
for i in range(30):
emailLabel = customtkinter.CTkLabel(master=rightFrame, text="Please enter your email:")
emailLabel.place(x=40, y=x)
x=x+50
Here is the output when populate3() is run:
Here
Here is the output when populate2() is run
Here
Does anyone know why this is? I can always go back and change the way I insert widgets to .pack rather than .place, however I would rather use .place as I find it easier to place widgets where I want to.
The reason is because pack by default will cause the containing frame to grow or shrink to fit all of the child widgets, but place does not. If your frame starts out as 1x1 and you use place to add widgets to it, the size will remain 1x1. When you use place, it is your responsibility to make the containing widget large enough to contain its children.
This single feature is one of the most compelling reasons to choose grid or pack over place - these other geometry managers do a lot of work for you so that you can think about the layout logically without getting bogged down in the details of the layout.
So I want to insert some objects in a frame, but when I firstly added a button the frames where were they weren't suppoused to.
Before
After
And this is the code:
import tkinter as tk
root = tk.Tk()
root.geometry("1200x700")
# Main frames
frame1 = tk.Frame(root, width=1200, height=625)
frame2 = tk.Frame(root, width=1200, bg="black", height=75)
frame1.grid(row=1, column=1)
frame2.grid(row=2, column=1)
# Secondary frames
frame1browser = tk.Frame(frame1, height=625, width=850, bg="grey")
frame1a = tk.Frame(frame1,height=625, width=(1200-850))
frame1browser.grid(row=1, column=1)
frame1a.grid(row=1, column=2)
# Last frames
frame1aa = tk.Frame(frame1a, width=(1200-850),height=525, bg="green")
frame1ab = tk.Frame(frame1a, width=(1200-850),height=100, bg="yellow")
frame1aa.grid(row=1, column=1)
frame1ab.grid(row=2, column=1, sticky="nswe")
# Elements that are not frames
Button1 = tk.Button(frame1ab, text="ur mother")
Button1.grid(column=1, row=1)
root.mainloop()
The frame ignores the width/height explicitly given if there is a widget inside it, by default. AFAIK, It finds and uses the minimum size required to fit all the widgets, also accommodating to extra properties like sticky, expand and so on.
To override this behavior, you will have to use the <grid/pack>_propagate(False) depending on whether you use pack or grid on the items inside the frame. Now the frame will grow/shrink as much as the size you specify.
frame1ab.grid_propagate(False)
I'm playing around with the scrollbar and the ttk.Notebook functions to create two scrollable tabs. I have the mousewheel bound to scrolling, but it always scrolls both tabs. When you first run the code, you can scroll the first tab by itself because the second tab hasn't been "activated" for lack of a better term. Once you click the second tab to view it, all further scrolling on either tab scrolls both tabs evenly. The scrollbars do work independently when scrolled manually.
Is there a way to only bind the mousewheel to the selected tab at any one time?
Edit: I've noticed another weird behavior that if you resize the window outward everything reacts as it should, but if you resize the window making it narrower, the scrollbar disappears to the right of the now narrower window.
from tkinter import *
from tkinter import ttk
Window = Tk()
# Create the Outer Frame
outer_frame = Frame(Window)
outer_frame.pack(fill=BOTH, expand=1)
# Create and add the tabs
tab_control = ttk.Notebook(outer_frame)
tab1 = ttk.Frame(tab_control)
tab_control.add(tab1, text="Tab 1")
tab2 = ttk.Frame(tab_control)
tab_control.add(tab2, text="Tab 2")
tab_control.pack(expand=1, fill=BOTH)
# Create the canvases
canvas1 = Canvas(tab1)
canvas1.pack(side=LEFT, fill=BOTH, expand=1)
canvas2 = Canvas(tab2)
canvas2.pack(side=LEFT, fill=BOTH, expand=1)
# Add scrollbars to canvases
scrollbar1 = ttk.Scrollbar(tab1, orient=VERTICAL, command=canvas1.yview)
scrollbar1.pack(side=RIGHT, fill=Y)
scrollbar2 = ttk.Scrollbar(tab2, orient=VERTICAL, command=canvas2.yview)
scrollbar2.pack(side=RIGHT, fill=Y)
def _on_mousewheel(event):
canvas1.yview_scroll(int(-1*(event.delta/120)), "units")
canvas2.yview_scroll(int(-1*(event.delta/120)), "units")
# Bind mousewheel to scrollbars
canvas1.bind("<MouseWheel>", _on_mousewheel)
canvas2.bind("<MouseWheel>", _on_mousewheel)
# Configure the canvases scrollbars
canvas1.configure(scrollregion=canvas1.bbox('all'), yscrollcommand=scrollbar1.set)
canvas1.bind('<Configure>', lambda e: canvas1.configure(scrollregion = canvas1.bbox("all")))
canvas2.configure(scrollregion=canvas2.bbox('all'), yscrollcommand=scrollbar2.set)
canvas2.bind('<Configure>', lambda e: canvas2.configure(scrollregion = canvas2.bbox("all")))
# Create frames inside the canvases
inner_frame1 = ttk.Frame(canvas1)
inner_frame2 = ttk.Frame(canvas2)
# Add the frames to the canvases
canvas1.create_window((0,0), window=inner_frame1, anchor="nw")
canvas2.create_window((0,0), window=inner_frame2, anchor="nw")
# Add buttons to the frames
for thing in range(20):
ttk.Button(inner_frame1, text=f'Button {thing}').grid(row=thing, column=0, pady=10, padx=10)
ttk.Button(inner_frame2, text=f'Button {thing}').grid(row=thing, column=0, pady=10, padx=10)
Window.mainloop()
Change the function to this:
def _on_mousewheel(event):
event.widget.yview_scroll(int(-1*(event.delta/120)), "units")
It find the canvas that caught the event and changes its yview_scroll.
I also solved the second problem but I still don't know why exactly. Using this answer I figured out what works and a plausible explanation for it. You have to move your canvas.pack(...) statements after you have packed the scroll bar. So your code should look like this:
from tkinter import *
from tkinter import ttk
Window = Tk()
# Create the Outer Frame
outer_frame = Frame(Window)
outer_frame.pack(fill=BOTH, expand=1)
# Create and add the tabs
tab_control = ttk.Notebook(outer_frame)
tab1 = ttk.Frame(tab_control)
tab_control.add(tab1, text="Tab 1")
tab2 = ttk.Frame(tab_control)
tab_control.add(tab2, text="Tab 2")
tab_control.pack(expand=1, fill=BOTH)
# Create the canvases
canvas1 = Canvas(tab1, bg="orange")
canvas2 = Canvas(tab2, bg="light blue")
# Add scrollbars to canvases
scrollbar1 = ttk.Scrollbar(tab1, orient=VERTICAL, command=canvas1.yview)
scrollbar1.pack(side=RIGHT, fill=Y)
scrollbar2 = ttk.Scrollbar(tab2, orient=VERTICAL, command=canvas2.yview)
scrollbar2.pack(side=RIGHT, fill=Y)
# Pack the canvases after the scrollbar
canvas1.pack(side=LEFT, fill=BOTH, expand=True)
canvas2.pack(side=LEFT, fill=BOTH, expand=True)
# This is the updated even handler:
def _on_mousewheel(event):
event.widget.yview_scroll(int(-1*(event.delta/120)), "units")
# Bind mousewheel to scrollbars
canvas1.bind("<MouseWheel>", _on_mousewheel)
canvas2.bind("<MouseWheel>", _on_mousewheel)
# Configure the canvases scrollbars
canvas1.configure(scrollregion=canvas1.bbox('all'), yscrollcommand=scrollbar1.set)
canvas1.bind('<Configure>', lambda e: canvas1.configure(scrollregion = canvas1.bbox("all")))
canvas2.configure(scrollregion=canvas2.bbox('all'), yscrollcommand=scrollbar2.set)
canvas2.bind('<Configure>', lambda e: canvas2.configure(scrollregion = canvas2.bbox("all")))
# Create frames inside the canvases
inner_frame1 = ttk.Frame(canvas1)
inner_frame2 = ttk.Frame(canvas2)
# Add the frames to the canvases
canvas1.create_window((0,0), window=inner_frame1, anchor="nw")
canvas2.create_window((0,0), window=inner_frame2, anchor="nw")
# Add buttons to the frames
for thing in range(20):
ttk.Button(inner_frame1, text=f'Button {thing}').grid(row=thing, column=0, pady=10, padx=10)
ttk.Button(inner_frame2, text=f'Button {thing}').grid(row=thing, column=0, pady=10, padx=10)
Window.mainloop()
My guess is that when you first pack the canvas with expand=True, fill="both", it decided that it will try to take as much space as possible without thinking about the scrollbar. I don't really know why that happens.
There is only one frame in my GUI, and it resizes itself to the size of the window. The frame has a child label, and I want the label to always be 1/3 the height of the frame and 1/1.5 the width of the frame. The code below tries to do that but the label always resizes itself to the size of the frame.
import tkinter
tk = tkinter.Tk()
tk.geometry("400x400")
f = tkinter.Frame(tk, bd=5, bg="white")
f.pack(padx=10, pady=10)
def callback(event):
f.config(height=tk.winfo_height(), width=tk.winfo_width())
l.config(width=int(f.winfo_width()/1.5), height=int(f.winfo_height()/3))
l = tkinter.Label(f, text="lead me lord", bg="yellow", relief=tkinter.RAISED, bd=5)
l.pack(side="bottom")
tk.bind("<Configure>", callback)
tk.mainloop()
The width and height of the label are in characters. In order to use pixels, you need to add an empty image to the label:
img = tkinter.PhotoImage() # an image of size 0
l = tkinter.Label(f, text="lead me lord", bg="yellow", relief=tkinter.RAISED, bd=5,
image=img, compound='center')
Actually you don't need to resize the frame in the callback if you add fill="both", expand=1 into f.pack(...):
import tkinter
tk = tkinter.Tk()
tk.geometry("400x400")
f = tkinter.Frame(tk, bd=5, bg="white")
f.pack(padx=10, pady=10, fill="both", expand=1)
def callback(event):
l.config(width=int(f.winfo_width()/1.5), height=int(f.winfo_height()/3))
#l.config(width=event.width*2//3, height=event.height//3) # same as above line if bind on frame
img = tkinter.PhotoImage()
l = tkinter.Label(f, text="lead me lord", bg="yellow", relief=tkinter.RAISED, bd=5,
image=img, compound='center')
l.pack(side="bottom")
f.bind("<Configure>", callback) # bind on frame instead of root window
tk.mainloop()
Given your precise specifications, the best solution is to use place since it lets you use relative widths and heights. However, if you plan to have other widgets in the window, place is rarely the right choice.
This example will do exactly what you asked: place the label at the bottom with 1/3 the height and 1/1.5 the width. There is no need to have a callback for when the window changes size.
Note: I had to change the call to pack for the frame. The text of your question said it would expand to fill the window but the code you had wasn't doing that. I added the fill and expand options.
import tkinter
tk = tkinter.Tk()
tk.geometry("400x400")
f = tkinter.Frame(tk, bd=5, bg="white")
f.pack(padx=10, pady=10, fill="both", expand=True)
l = tkinter.Label(f, text="lead me lord", bg="yellow", relief=tkinter.RAISED, bd=5)
l.place(relx=.5, rely=1.0, anchor="s", relheight=1/3., relwidth=1/1.5)
tkinter.mainloop()