scrollable canvas working with .pack but not .place - python

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.

Related

Tkinter frame, how to make it resize after destroying child widgets and repopulating with new widgets

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.

How do I create a scrollbar when using a grid in tkinter?

I'm a little bit stuck on this problem regarding my program. I tried adding as many comments as possible to give a sense of what everything does in the code, but essentially. The program has a field and value entry box. When the "add field/value button" is clicked, more of the entry widgets are added. If this keeps occurring then obviously it'll go off screen. So I've limited the size of the application, but the problem then is I need a scrollbar. I've tried looking it up, but my frame uses grid, and everywhere they use pack which isn't compatible in this case. I get the scrollbar to appear, however it doesn't seem to work. I've seen some people use canvas, and more than one frame, etc. I'm missing something important but I don't know how do the exact same thing with a grid. Think you experts can lend me hand to get it working?
from tkinter import *
import tkinter as tk
class Insert(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
container = tk.Frame(self)
container.grid_columnconfigure(0, weight=1)
container.grid_rowconfigure(0, weight=1)
container.pack(side="top", fill="both", expand=True)
self.frameslist = {}
for frame in (Create,):
frame_occurrence = frame.__name__
active_frame = frame(parent=container, controller=self)
self.frameslist[frame_occurrence] = active_frame
active_frame.grid(row=0, column=0, sticky="snew")
self.show_frame("Create")
def show_frame(self, frame_occurrence):
active_frame = self.frameslist[frame_occurrence]
active_frame.tkraise()
class Create(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
#For all widgets (nested list, 2 widgets per row)
self.inputlist = []
#For just the entries
self.newinputlist = []
#Create two labels, add them into the inputlist to be iterated
labels = [tk.Label(self, text="Field"), tk.Label(self, text="Values")]
self.inputlist.append(labels[:])
#Insert the labels from the list
for toplabels in range(1):
self.inputlist[toplabels][0].grid(row=toplabels, column=0, padx=10, pady=5)
self.inputlist[toplabels][1].grid(row=toplabels, column=1, padx=10, pady=5)
#Create the first two entry boxes, append them to the inputlist, and newinput list
first_entries = [tk.Entry(self, borderwidth=5), tk.Text(self, borderwidth=5, height= 5, width=20)]
self.newinputlist.append(first_entries[:])
self.inputlist.append(first_entries[:])
#Insert the entries from the newinputlist
for x in range(0, len(self.newinputlist) + 1):
self.newinputlist[0][x].grid(row=1, column=x, padx=10, pady=5)
#Create two buttons (Both share same row), append them to list
button_input_1 = [tk.Button(self, text="ADD FIELD/VALUE", command=self.add_insert), tk.Button(self, text="BACK")]
self.inputlist.append(button_input_1[:])
#Insert buttons at the bottom of the grid
for button in range(len(self.inputlist) - 2, len(self.inputlist)):
self.inputlist[button][0].grid(row=button, column=0, padx=10, pady=5)
self.inputlist[button][1].grid(row=button, column=1, padx=10, pady=5)
def add_insert(self):
#Create two new entries, append them to the list
add_input = [tk.Entry(self, borderwidth=5), tk.Text(self, borderwidth=5, height= 5, width=20)]
self.inputlist.insert(-1, add_input)
self.newinputlist.append(add_input)
#Because there are new entry boxes, old grid should be forgotten
for widget in self.children.values():
widget.grid_forget()
#Use the index for the row, get all widgets and place them again
for index, widgets in enumerate(self.inputlist):
widget_one = widgets[0]
widget_two = widgets[1]
widget_one.grid(row=index, column=0, padx=10, pady=5)
widget_two.grid(row=index, column=1, padx=10)
#Create scrollbar when this button is pressed
scrollbar = tk.Scrollbar(self, orient="vertical")
scrollbar.grid(row=0, column=2, stick="ns", rowspan=len(self.inputlist) + 1)
if __name__ == "__main__":
app = Insert()
app.maxsize(0, 500)
app.mainloop()
You could create a Canvas and insert your Entry objects into a Frame.
Here is a simplified example that creates a 2D bank of Buttons using the canvas.create_window.
import tkinter as tk
root = tk.Tk()
# essential to enable full window resizing
root.rowconfigure(0, weight = 1)
root.columnconfigure(0, weight = 1)
# scrollregion is also essential when using scrollbars
canvas = tk.Canvas(
root, scrollregion = "0 0 2000 1000", width = 400, height = 400)
canvas.grid(row = 0, column = 0, sticky = tk.NSEW)
scroll = tk.Scrollbar(root, orient = tk.VERTICAL, command = canvas.yview)
scroll.grid(row = 0, column = 1, sticky = tk.NS)
canvas.config(yscrollcommand = scroll.set)
# I've used a labelframe instead of frame so button are neatly collected and named
frame = tk.LabelFrame(root, labelanchor = tk.N, text = "Buttonpad")
# Note I've placed buttons in frame
for fila in range(20):
for col in range(5):
btn = tk.Button(frame, text = f"{fila}-{col}")
btn.grid(row = fila, column = col, sticky = tk.NSEW)
# Frame is now inserted into canvas via create_window method
item = canvas.create_window(( 2, 2 ), anchor = tk.NW, window = frame )
root.mainloop()

Buttons and tables not visible in tkinter Python

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.

How do you make a child label smaller than its parent frame?

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()

Tkinter: Too much space between label and button frame

I'm pretty new to Tkinter and I build a little window with different widgets.
My Code looks like this:
import tkinter as tk
from tkinter import ttk
class Application(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.master = master
self.master.geometry("800x600")
self.master.title("Tkinter Sandbox")
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(1, weight=1)
self._create_left_frame()
self._create_button_bar()
self._create_label_frame()
def _create_left_frame(self):
frame = tk.Frame(self.master, bg="red")
tree_view = ttk.Treeview(frame)
tree_view.column("#0", stretch=tk.NO)
tree_view.heading("#0", text="Treeview")
tree_view.pack(fill=tk.Y, expand=1)
frame.grid(row=0, column=0, rowspan=2, sticky=tk.N + tk.S)
def _create_button_bar(self):
frame = tk.Frame(self.master, bg="blue")
button_run_single = tk.Button(frame, text="Button 1")
button_run_all = tk.Button(frame, text="Button 2")
button_details = tk.Button(frame, text="Button 3")
button_run_single.grid(row=0, column=0)
button_run_all.grid(row=0, column=1, padx=(35, 35))
button_details.grid(row=0, column=2)
frame.grid(row=0, column=1, sticky=tk.N)
def _create_label_frame(self):
frame = tk.Frame(self.master, bg="blue")
name_label = tk.Label(frame, text="Label 1")
performance_label = tk.Label(frame, text="Label 2")
name_entry = tk.Entry(frame)
performance_entry = tk.Entry(frame)
name_label.grid(row=0, column=0)
name_entry.grid(row=0, column=1)
performance_label.grid(row=1, column=0)
performance_entry.grid(row=1, column=1)
frame.grid(row=1, column=1)
if __name__ == '__main__':
root = tk.Tk()
app = Application(root)
app.mainloop()
Between the three buttons and the label + entry frame is a huge space. I want the button and label + entry frame right under each other, without the huge space but the treeview should also expand vertically over the whole application window.
I think the problem might be my row and column configuration but I don't know how to solve this problem.
The way you've structured your code makes it hard to see the problem. As a good general rule of thumb, all calls to grid or pack for widgets within a single parent should be in one place. Otherwise, you create dependencies between functions that are hard to see and understand.
I recommend having each of your helper functions return the frame rather than calling grid on the frame. That way you give control to Application.__init__ for the layout of the main sections of the window.
For example:
left_frame = self._create_left_frame()
button_bar = self._create_button_bar()
label_frame = self._create_label_frame()
left_frame.pack(side="left", fill="y")
button_bar.pack(side="top", fill="x")
label_frame.pack(side="top", fill="both", expand=True)
I used pack here because it requires less code than grid for this type of layout. However, if you choose to switch to grid, or wish to add more widgets to the root window later, you only have to modify this one function rather than modify the grid calls in multiple functions.
Note: this requires that your functions each do return frame to pass the frame back to the __init__ method. You also need to remove frame.grid from each of your helper functions.
With just that simple change you end up with the button bar and label/entry combinations at the top of the section on the right. In the following screenshot I changed the background of the button_bar to green so you can see that it fills the top of the right side of the UI.
You need to change line
self.master.grid_rowconfigure(0, weight=1)
to
self.master.grid_rowconfigure(1, weight=1)
so that the second row takes all the space. Then you need to stick widgets from the label frame to its top by adding sticky parameter to the grid call in _create_label_frame:
frame.grid(row=1, column=1, sticky=tk.N)
I prefer to use the Pack Function since it gives a more open window - its easy to configure. When you use Pack() you can use labels with no text and just spaces to create a spacer, by doing this you won't run into the problem your facing.

Categories

Resources