Using grid in tkinter, I'm trying to align a set of frames (I would love to post a picture, but I'm not allowed.)
I've two outer LabelFrames of different sizes and on top of each other which I'd like to stretch and align.
Within the bottom frame, I've a stack of several other LabelFrames and within each of the LabelFrames there is a Label. I would like for the LabelFrames to extend as much as the outer container and for each of the inner Labels to be right align with respect to the containing LabelFrame.
I've tried, without success, with various combinations of sticky, anchor, justify.
Any suggestion, recommendation?
#!/usr/bin/env python
import Tkinter as tk
class AlignTest(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.grid()
self.parent.title('Align test')
self.createMenus()
self.createWidgets()
def createMenus(self):
# Menu
self.menubar = tk.Menu(self.parent)
self.parent.config(menu=self.menubar)
# Menu->File
self.fileMenu = tk.Menu(self.menubar)
# Menu->Quit
self.fileMenu.add_command(label='Quit',
command=self.onExit)
# Create File Menu
self.menubar.add_cascade(label='File',
menu=self.fileMenu)
def createWidgets(self):
# Main frame
self.mainFrame = tk.Frame(self.parent)
self.mainFrame.grid(row=0, column=0)
# Outer LabelFrame1
self.outerLabelFrame1 = tk.LabelFrame(self.mainFrame,
text='Outer1')
self.outerLabelFrame1.grid(row=0, column=0)
# Inner Label
self.innerLabel = tk.Label(self.outerLabelFrame1,
text='This is a longer string, for example!')
self.innerLabel.grid(row=0, column=0)
# Outer LabelFrame2
self.outerLabelFrame2 = tk.LabelFrame(self.mainFrame,
text='Outer2')
self.outerLabelFrame2.grid(row=1, column=0, sticky='ew')
# Inner labelFrames each with a single labels
self.innerLabel1 = tk.LabelFrame(self.outerLabelFrame2,
bg='yellow',
text='Inner1')
self.innerLabel1.grid(row=0, column=0, sticky='ew')
self.value1 = tk.Label(self.innerLabel1,
bg='green',
text='12.8543')
self.value1.grid(row=0, column=0, sticky='')
self.innerLabel2 = tk.LabelFrame(self.outerLabelFrame2,
bg='yellow',
text='Inner2')
self.innerLabel2.grid(row=1, column=0, sticky='ew')
self.value2 = tk.Label(self.innerLabel2,
bg='green',
text='0.3452')
self.value2.grid(row=0, column=0, sticky='')
self.innerLabel3 = tk.LabelFrame(self.outerLabelFrame2,
bg='yellow',
text='Inner3')
self.innerLabel3.grid(row=2, column=0, sticky='')
self.value3 = tk.Label(self.innerLabel3,
bg='green',
text='123.4302')
self.value3.grid(row=0, column=0, sticky='')
def onExit(self):
self.parent.quit()
def main():
root = tk.Tk()
app = AlignTest(root)
app.mainloop()
if __name__ == '__main__':
main()
Without even running your code I see two problems. The first is that you aren't always using the sticky parameter when calling grid. That could be part of the problem. I've rarely ever used grid without using that parameter.
The second problem is that you aren't giving any of your rows and columns any weight. Without a positive weight, columns and rows will only ever use up exactly as much space as they need for their contents, and no more. Any extra space goes unallocated.
A good rule of thumb is that in every widget that is being used as a container for other widgets (typically, frames), you should always give at least one row and one column a positive weight.
As a final suggestion: during development it's really helpful to give each of your frames a distinctive color. This really helps to visualize how the frames are using the available space.
Thanks to Bryan's comment on weight, here is a working version of the code as potential reference. (I'll add pictures when allowed.)
#!/usr/bin/env python
import Tkinter as tk
class AlignTest(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.grid()
self.parent.title('Align test')
self.createMenus()
self.createWidgets()
def createMenus(self):
# Menu
self.menubar = tk.Menu(self.parent)
self.parent.config(menu=self.menubar)
# Menu->File
self.fileMenu = tk.Menu(self.menubar)
# Menu->Quit
self.fileMenu.add_command(label='Quit',
command=self.onExit)
# Create File Menu
self.menubar.add_cascade(label='File',
menu=self.fileMenu)
def createWidgets(self):
# Main frame
self.mainFrame = tk.Frame(self.parent)
self.mainFrame.grid(row=0, column=0)
# Outer LabelFrame1
self.outerLabelFrame1 = tk.LabelFrame(self.mainFrame,
text='Outer1')
self.outerLabelFrame1.grid(row=0, column=0)
# Inner Label
self.innerLabel = tk.Label(self.outerLabelFrame1,
text='This is a longer string, for example!')
self.innerLabel.grid(row=0, column=0)
# Outer LabelFrame2
self.outerLabelFrame2 = tk.LabelFrame(self.mainFrame,
text='Outer2')
self.outerLabelFrame2.grid(row=1, column=0, sticky='ew')
self.outerLabelFrame2.grid_columnconfigure(0, weight=1)
# Inner labelFrames each with a single labels
self.innerLabel1 = tk.LabelFrame(self.outerLabelFrame2,
bg='yellow',
text='Inner1')
self.innerLabel1.grid(row=0, column=0, sticky='ew')
self.innerLabel1.grid_columnconfigure(0, weight=1)
self.value1 = tk.Label(self.innerLabel1,
bg='green',
anchor='e',
text='12.8543')
self.value1.grid(row=0, column=0, sticky='ew')
self.innerLabel2 = tk.LabelFrame(self.outerLabelFrame2,
bg='yellow',
text='Inner2')
self.innerLabel2.grid(row=1, column=0, sticky='ew')
self.innerLabel2.grid_columnconfigure(0, weight=1)
self.value2 = tk.Label(self.innerLabel2,
bg='green',
anchor='e',
text='0.3452')
self.value2.grid(row=0, column=0, sticky='ew')
self.innerLabel3 = tk.LabelFrame(self.outerLabelFrame2,
bg='yellow',
text='Inner3')
self.innerLabel3.grid(row=2, column=0, sticky='ew')
self.innerLabel3.grid_columnconfigure(0, weight=1)
self.value3 = tk.Label(self.innerLabel3,
bg='green',
anchor='e',
text='123.4302')
self.value3.grid(row=0, column=0, sticky='ew')
def onExit(self):
self.parent.quit()
def main():
root = tk.Tk()
app = AlignTest(root)
app.mainloop()
if __name__ == '__main__':
main()
Related
I am trying to make a GUI such as this with pods, each containing their own elements such as text, images and buttons.
My goal is to make it so that the so called pods can be added to the GUI window (a scrolling capable window) at any point in the code and updated in the window shifting the previous pod to the right or down to the next row if the current row is full like the image below.
I have never messed with Tkinter before so I was wondering if anyone could help me with what steps I would need to take to make such a GUI.
Implement a class that inherits from the Frame class. You can then create as many instances of this class that you want. Since you want the pods to wrap, you can use a Text widget to hold the pods since it's the only scrollable widget that natively supports wrapping.
The "pod" class might look something like this:
class Pod(tk.Frame):
def __init__(self, parent, title, subtitle, image):
super().__init__(parent, bd=2, relief="groove")
if isinstance(image, tk.PhotoImage):
self.image = image
else:
self.image = tk.PhotoImage(file=image_path)
self.title = tk.Label(self, text=title)
self.image_label = tk.Label(self, image=self.image, bd=1, relief="solid")
self.subtitle = tk.Label(self, text=subtitle)
self.b1 = tk.Button(self, text="Button 1")
self.b2 = tk.Button(self, text="Button 2")
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure((0,1), weight=1)
self.title.grid(row=0, column=0, columnspan=2, sticky="ew")
self.image_label.grid(row=1, column=0, columnspan=2, sticky="nsew", padx=8, pady=8)
self.subtitle.grid(row=2, column=0, columnspan=2, sticky="ew")
self.b1.grid(row=3, column=0)
self.b2.grid(row=3, column=1)
You can create another class to manage these objects. If you base it on a Text widget you get the wrapping behavior for free. Though, you could also base it on a Frame or Canvas and manage the wrapping yourself.
It might look something like this:
class PodManager(tk.Text):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.configure(state="disabled", wrap="char")
self.pods = []
def add(self, pod):
self.pods.append(pod)
self.configure(state="normal")
self.window_create("end", window=pod)
self.configure(state="disabled")
To tie it all together, create one PodManager class, then pass one or more instances of Pod to its add method:
import tkinter as tk
...
root = tk.Tk()
pm = PodManager(root)
vsb = tk.Scrollbar(root, orient="vertical", command=pm.yview)
pm.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
pm.pack(side="left", fill="both", expand=True)
for i in range(10):
image = tk.PhotoImage(width=200,height=100)
pod = Pod(pm, f"Title #{i+1}", "More Text", image)
pm.add(pod)
root.mainloop()
I have a scrollable frame class that I borrowed from some code I found, and I'm having trouble adjusting it to fit my needs. It was managed by .pack(), but I needed to use .grid(), so I simply packed a frame (self.region) into it so I could grid my widgets inside of that. However, the widgets inside of this frame won't expand to meet the edges of the container and I'm not sure why. Theres a lot of issues similar to mine out there, but none of the solutions seemed to help. I tried using .grid_columnconfigure, .columnconfigure(), and .bind("Configure") all to no avail. Does anyone have any suggestions to get the widgets in my scrollable region to expand east and west to fill the window?
import tkinter as tk
from tkinter import ttk
class ScrollableFrame(ttk.Frame):
"""taken from https://blog.tecladocode.com/tkinter-scrollable-frames/ and modified to
allow for the use of grid inside self.region
Class that allows for the creation of a frame that is scrollable"""
def __init__(self, container, *args, **kwargs):
super().__init__(container, *args, **kwargs)
canvas = tk.Canvas(self)
scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
self.scrollable_frame = ttk.Frame(canvas)
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
canvas.rowconfigure(0, weight=1)
canvas.columnconfigure(0, weight=1)
scrollbar.pack(side="right", fill="y")
self.scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all")
)
)
self.scrollable_frame.rowconfigure(0, weight=1)
self.scrollable_frame.columnconfigure(0, weight=1)
self.region=ttk.Frame(self.scrollable_frame)
self.region.pack(fill='both', expand=1)
self.region.grid_rowconfigure(0, weight=1)
self.region.grid_columnconfigure(0, weight=1)
class OtherWindow():
"""data collector object and window"""
def __init__(self, window):
self.window=tk.Toplevel(window)
self.window.grid_columnconfigure(0, weight=1)
self.window.grid_columnconfigure(1, weight=1)
self.window.grid_columnconfigure(2, weight=1)
self.window.grid_columnconfigure(3, weight=1)
self.window.grid_rowconfigure(3, weight=1)
self.lbl1=ttk.Label(self.window, text="this is a sort of long label")
self.lbl1.grid(row=0, column=0, columnspan=2)
self.lbl2=ttk.Label(self.window, text="this is another label")
self.lbl2.grid(row=0, column=2)
self.lbl3=ttk.Label(self.window, text="Other information: blah blah blah blah")
self.lbl3.grid(row=0, column=3)
self.directions=ttk.Label(self.window, text='These are instructions that are kind of long and take'+\
'up about this much space if I had to guess so random text random text random text', wraplength=700)
self.directions.grid(row=1, column=0, columnspan=4)
self.scrolly=ScrollableFrame(self.window)
self.scrolly.grid(row=2, column=0, columnspan=4,sticky='nsew')
self.frame=self.scrolly.region
self.fillScrollRegion()
self.continueBtn=ttk.Button(self.window, text="Do Something", command=self.do)
self.continueBtn.grid(row=3, column=0, columnspan=4, sticky='nsew')
def fillScrollRegion(self):
"""fills scrollable region with label widgets"""
for i in range(15):
for j in range(5):
lbl=ttk.Label(self.frame, text="Sample text"+str(i)+' '+str(j))
lbl.grid(row=i, column=j, sticky='nsew')
def do(self):
pass
root=tk.Tk()
app=OtherWindow(root)
root.mainloop()
The problem is that the scrollframe container Frame is not filling the Canvas horizontally. Instead of bothering to fix some copy/paste, example scrollframe, and explain it, I'll just give you my scrollframe. It is substantially more robust than the one you are using, and the problem you are having doesn't exist with it. I've already plugged it into a version of your script below.
The solution to your scrollframe's issue is found in my on_canvas_configure method. It simply tells the container frame to be the same width as the canvas, on canvas <Configure> events.
import tkinter as tk, tkinter.ttk as ttk
from typing import Iterable
class ScrollFrame(tk.Frame):
def __init__(self, master, scrollspeed=5, r=0, c=0, rspan=1, cspan=1, grid={}, **kwargs):
tk.Frame.__init__(self, master, **{'width':400, 'height':300, **kwargs})
#__GRID
self.grid(**{'row':r, 'column':c, 'rowspan':rspan, 'columnspan':cspan, 'sticky':'nswe', **grid})
#allow user to set width and/or height
if {'width', 'height'} & {*kwargs}:
self.grid_propagate(0)
#give this widget weight on the master grid
self.master.grid_rowconfigure(r, weight=1)
self.master.grid_columnconfigure(c, weight=1)
#give self.frame weight on this grid
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
#_WIDGETS
self.canvas = tk.Canvas(self, bd=0, bg=self['bg'], highlightthickness=0, yscrollincrement=scrollspeed)
self.canvas.grid(row=0, column=0, sticky='nswe')
self.frame = tk.Frame(self.canvas, **kwargs)
self.frame_id = self.canvas.create_window((0, 0), window=self.frame, anchor="nw")
vsb = tk.Scrollbar(self, orient="vertical")
vsb.grid(row=0, column=1, sticky='ns')
vsb.configure(command=self.canvas.yview)
#attach scrollbar to canvas
self.canvas.configure(yscrollcommand=vsb.set)
#_BINDS
#canvas resize
self.canvas.bind("<Configure>", self.on_canvas_configure)
#frame resize
self.frame.bind("<Configure>", self.on_frame_configure)
#scroll wheel
self.canvas.bind_all('<MouseWheel>', self.on_mousewheel)
#makes frame width match canvas width
def on_canvas_configure(self, event):
self.canvas.itemconfig(self.frame_id, width=event.width)
#when frame dimensions change pass the area to the canvas scroll region
def on_frame_configure(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
#add scrollwheel feature
def on_mousewheel(self, event):
self.canvas.yview_scroll(int(-event.delta / abs(event.delta)), 'units')
#configure self.frame row(s)
def rowcfg(self, index, **options):
index = index if isinstance(index, Iterable) else [index]
for i in index:
self.frame.grid_rowconfigure(i, **options)
#so this can be used inline
return self
#configure self.frame column(s)
def colcfg(self, index, **options):
index = index if isinstance(index, Iterable) else [index]
for i in index:
self.frame.grid_columnconfigure(i, **options)
#so this can be used inline
return self
class AuxiliaryWindow(tk.Toplevel):
def __init__(self, master, **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
self.geometry('600x300+600+200')
self.attributes('-topmost', True)
self.title('This Is Another Title') #:D
#if you reconsider things, you can accomplish more with less
labels = ["this is a sort of long label",
"this is another label",
"Other information: blah blah blah blah"]
for i, text in enumerate(labels):
ttk.Label(self, text=text).grid(row=0, column=i)
self.grid_columnconfigure(i, weight=1)
#doing it this way the text will always fit the display as long as you give it enough height to work with
instr = tk.Text(self, height=3, wrap='word', bg='gray94', font='Arial 8 bold', bd=0, relief='flat')
instr.insert('1.0', ' '.join(['instructions']*20))
instr.grid(row=1, columnspan=3, sticky='nswe')
#instantiate the scrollframe, configure the first 5 columns and return the frame. it's inline mania! :p
self.scrollframe = ScrollFrame(self, 10, 2, 0, cspan=3).colcfg(range(5), weight=1).frame
self.fillScrollRegion()
#why store a reference to this? Do you intend to change/delete it later?
ttk.Button(self, text="Do Something", command=self.do).grid(row=3, columnspan=3, sticky='ew')
def fillScrollRegion(self):
"""fills scrollable region with label widgets"""
r, c = 30, 5 #math is our friend
for i in range(r*c):
ttk.Label(self.scrollframe, text=f"row_{i%r} col_{i//r}").grid(row=i%r, column=i//r, sticky='nsew')
def do(self):
pass
class Root(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry('+550+150')
self.title('This Is A Title Probably Or Something') #:D
aux = AuxiliaryWindow(self)
self.mainloop()
Root() if __name__ == "__main__" else None
I can't pack widgets in rows or columns as in image, can you help me?
The problem is text widget deforms column size, text should not be in row=3,column=0 ?
def _formato(self):
t1=tkinter.Toplevel(self._finestra)
labelTop = tkinter.Label(t1,text = "Tipo di carattere")
labelTop.grid(row=0, column=0)
labelTop2 = tkinter.Label(t1,text = "Dimensione")
labelTop2.grid(row=0, column=1)
labelTop3 = tkinter.Label(t1)
labelTop3.grid(row=2, column=0)
listaFont=tkinter.ttk.Combobox(t1)
allfonts = sorted(tkinter.font.families())
listaFont["values"] = allfonts
listaFont.grid(row=1, column=0)
listaFont.bind("<<ComboboxSelected>>", None)
listaDimensione = tkinter.ttk.Combobox(t1)
allfontsizes = list(range(8,70))
listaDimensione['values'] = allfontsizes
listaDimensione.grid(row=1, column=1)
testo= tkinter.Text(t1)
testo.insert(tkinter.INSERT,'AaBbYyZz')
testo.grid(row=3,column=0)
Question: All widgets in the same column should have equal width.
The core is, use a Frame for every column and layout the widgets into the Frame.
This allows all widgets to resize to the Frame width.
Define class App for demonstration purpose
class App(tk.Tk):
def __init__(self):
super().__init__()
To get equal width use a tk.Frame for every column.
Allow the Frame to grow his width.
Allow the widgets inside the Frame to grow his width.
Define the Frame to grow up to the App width.
# column 0
self.grid_columnconfigure(0, weight=1)
frame_0 = tk.Frame(self)
frame_0.grid_columnconfigure(0, weight=1)
frame_0.grid(row=0, column=0, sticky='nsew')
Add the widgets ...
Define every widget to grow his width up to the Frame width.
labelTop = tkinter.Label(frame_0, text="Tipo di carattere")
labelTop.grid(row=0, column=0, sticky='ew')
listaFont = tkinter.ttk.Combobox(frame_0)
listaFont.grid(row=1, column=0, sticky='ew')
allfonts = sorted(tkinter.font.families())
listaFont["values"] = allfonts
listaFont.bind("<<ComboboxSelected>>", None)
Note: Reset the default using width=1
testo = tkinter.Text(frame_0, width=1)
testo.insert(tkinter.INSERT, 'AaBbYyZz')
testo.grid(row=3, column=0, sticky='ew')
The same for columen 1 ...
# column 1
self.grid_columnconfigure(1, weight=1)
frame_1 = tk.Frame(self)
frame_1.grid_columnconfigure(0, weight=1)
frame_1.grid(row=0, column=1, sticky='nsew')
labelTop2 = tkinter.Label(frame_1, text="Dimensione")
labelTop2.grid(row=0, column=0, sticky='ew')
listaDimensione = tkinter.ttk.Combobox(frame_1)
allfontsizes = list(range(8, 70))
listaDimensione['values'] = allfontsizes
listaDimensione.grid(row=1, column=0, sticky='ew')
Usage:
if __name__ == "__main__":
App().mainloop()
Tested with Python: 3.5
I have a Text widget, textbox, where the user can type, and another Text widget, linenumbers, directly to the left of it which displays line numbers. I have it working, but with one problem: the more lines I create, the more the two Text widgets get out of sync when scrolled.
Here's a gif showing what happens.
And here's my code (scroll_both and update_scroll came from another StackOverflow thread):
class MainGUI:
def scroll_both(self, action, position, type=None):
self.textbox.yview_moveto(position)
self.linenumbers.yview_moveto(position)
def update_scroll(self, first, last, type=None):
self.textbox.yview_moveto(first)
self.linenumbers.yview_moveto(first)
self.scrollbar_y.set(first, last)
def on_textbox_update(self, event):
modified = self.textbox.edit_modified()
if modified:
#If the number of lines in the textbox has changed:
if self.linecount != self.textbox.index("end").split('.')[0]:
#Update textbox's linecount
self.linecount = self.textbox.index("end").split('.')[0]
# Clear the line count sidebar
self.linenumbers.config(state="normal")
self.linenumbers.delete(0.0, "end")
# Fill it up again
for LineNum in range(1, int(self.linecount)):
self.linenumbers.insert(self.linenumbers.index("end"), str(LineNum)+"\n", "justifyright")
if len(str(LineNum)) == 1:
self.linenumbers.config(width=2)
else:
self.linenumbers.config(width=len(str(LineNum)))
self.linenumbers.config(state="disabled")
def __init__(self, master):
self.master = master
Grid.rowconfigure(master, 1, weight=1)
Grid.columnconfigure(master, 2, weight=1)
self.linenumbers = Text(master, width=2, borderwidth=0, takefocus=0, padx=6, fg="gray")
self.linenumbers.config(font="TkTextFont")
self.linenumbers.tag_configure("justifyright", justify="right")
self.linenumbers.grid(row=0, column=1, rowspan=2, sticky=NSEW)
self.textbox = Text(master, padx=6, borderwidth=0, wrap=NONE)
self.textbox.config(font="TkTextFont")
self.textbox.grid(row=0, column=2, rowspan=2, sticky=NSEW)
self.textbox.bind("<<Modified>>", self.on_textbox_update)
self.scrollbar_y = ttk.Scrollbar(master)
self.scrollbar_y.grid(row=0, column=3, rowspan=2, sticky=NSEW)
self.scrollbar_y.config(command=self.scroll_both)
self.textbox.config(yscrollcommand=self.update_scroll)
self.linenumbers.config(yscrollcommand=self.update_scroll)
root = Tk()
MainGUI = MainGUI(root)
root.mainloop()
Suppose I have a frame which contains 5 widgets and uses the grid method to manage positions:
So far, so good. But what if I want now to make something like this (obtained using bad methods):
i.e. button horizontal settings spans 4 rows. If I were able to set grid configuration (say) in this manner: grid(ncols = 1, nrows = 5) and then I would do something like button.columnconfigure(0,weight=4) and label.columnconfigure(0,weight=1).
How to solve this problem?
I will explain why this is not trivial question. It is because this two groups of widgets (on the 1st figure and on the 2nd) are in separate frames. So the first frame has 5 rows and the second has only 2. And widget will not be aligned:
EDIT:
This is piece of my code:
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
labelFrame = tk.Frame(self)
labelFrame.pack(fill='x', side="top")
startLabel = tk.Label(labelFrame,
text="Start page")
startLabel.pack(pady=10)
quitFrame = tk.Frame(self)
quitFrame.pack(fill='x',side="bottom")
quitButton = tk.Button(quitFrame, text="Quit",
command=quit,
bg="pink")
quitButton.pack(pady=10)
operateFrame = tk.Frame(self)
operateFrame.pack()
channelGroup = tk.Frame(operateFrame)
channelGroup.grid(row=0, column=0, sticky = 'n')
chLabel = tk.Label(channelGroup,
text="Channel group")
chLabel.grid(pady=10)
ch1Button = tk.Button(channelGroup, text="CH1 Settings",
command=lambda: controller.show_frame("CH1"))
ch1Button.grid(row=1, column=0)
ch2Button = tk.Button(channelGroup, text="CH2 Settings",
command=lambda: controller.show_frame("CH2"))
ch2Button.grid(row=2, column=0)
ch3Button = tk.Button(channelGroup, text="CH3 Settings",
command=lambda: controller.show_frame("CH3"))
ch3Button.grid(row=3, column=0)
ch4Button = tk.Button(channelGroup, text="CH4 Settings",
command=lambda: controller.show_frame("CH4"))
ch4Button.grid(row=4, column=0)
triggerGroup = tk.Frame(operateFrame)
triggerGroup.grid(row=0, column=1, sticky='n')
trigLabel = tk.Label(triggerGroup,
text="Trigger group")
trigLabel.grid(row=0,column=0,pady=10)
trigButton = tk.Button(triggerGroup, text="Trigger Settings",
command=lambda: controller.show_frame("Trigger"))
trigButton.grid()
horizGroup = tk.Frame(operateFrame)
horizGroup.grid(row=0, column=2, sticky='n')
horizLabel = tk.Label(horizGroup,
text="Horizontal group")
horizLabel.grid(pady=10)
horizButton = tk.Button(horizGroup,
text="Horizontal settings",
command=lambda: controller.show_frame("Horizontal"))
horizButton.grid()
After that I get:
First, your example is not complete. To troubleshoot this I need to run it; I'm good but I'm not good enough to debug by eye. And you are asking me to write a bunch of boilerplate stuff. Post a COMPLETE code snippet; including imports and entry points.
To fix your problems you just need to tell the frame, the row, and the buttons to expand to all available vertical space with sticky='ns' and rowconfigure(1, weight=1).
horizGroup = tk.Frame(operateFrame)
horizGroup.grid(row=0, column=2, sticky='ns')
horizGroup.rowconfigure(1, weight=1)
horizLabel = tk.Label(horizGroup,
text="Horizontal group")
horizLabel.grid(pady=10)
horizButton = tk.Button(horizGroup,
text="Horizontal settings",
command=lambda: controller.show_frame("Horizontal"))
horizButton.grid(sticky='ns')