Books / Tkinter Tutorial for Beginners / Chapter 3
Geometry Managers in Tkinter
So far we have used the pack()
method to add labels and buttons to our
root window. Pack is one of the simplest geometry managers in Tk. In
this tutorial we’ll learn more about pack and other geometry managers.
Pack
The pack(3tk) geometry manager is really simple and easy to use. Let’s create a window like this with it:
,--------------------------------------------.
| Pack Test | _ | o | X |
|--------------------------------------------|
|,--------------------. |
|| | |
|| | |
|| | |
|| This stretches | This doesn't stretch |
|| | |
|| | |
|| | |
|`--------------------' |
|--------------------------------------------|
| This is a status bar |
`--------------------------------------------'
Here’s the code.
import tkinter
from tkinter import ttk
root = tkinter.Tk()
big_frame = ttk.Frame(root)
big_frame.pack(fill='both', expand=True)
button = ttk.Button(big_frame, text="This stretches")
label = ttk.Label(big_frame, text="This doesn't stretch")
statusbar = ttk.Label(big_frame, text="This is a status bar", relief='sunken')
statusbar.pack(side='bottom', fill='x')
button.pack(side='left', fill='both', expand=True)
label.pack(side='left')
root.title("Pack Test")
root.geometry('300x150')
root.mainloop()
Run the program and change the size of the window by dragging its edges with the mouse. The button and status bar should stretch and shrink nicely.
Let’s go through the pack stuff in the code:
statusbar.pack(side='bottom', fill='x')
The fill='x'
means that the status bar will fill all of the space it
has horizontally. We can also do fill='y'
or fill='both'
.
Obviously, side='bottom'
means that the status bar will appear at the
bottom of the window. The default is side='top'
. Note that I packed
the status bar before packing any other widgets, because even with
side='bottom'
, it won’t go below widgets that are already packed with
side='left'
or side='right'
.
button.pack(side='left', fill='both', expand=True)
We want the button to fill all the space it has, so we’re using
fill='both'
. But we also want to give it as much space as possible, so
we’re using expand=True
. If you remove the expand=True
and run the
program again, you’ll notice that the button doesn’t stretch anymore
becuase it doesn’t have any space to fill.
label.pack(side='left')
We packed the button with side='left'
first and then the label, so
they ended up next to each other so that the button is on the left side
of the label.
Grid
Let’s make a similar GUI as in the pack example, but with grid(3tk) instead of pack.
import tkinter
from tkinter import ttk
root = tkinter.Tk()
big_frame = ttk.Frame(root)
big_frame.pack(fill='both', expand=True)
button = ttk.Button(big_frame, text="This stretches")
label = ttk.Label(big_frame, text="This doesn't stretch")
statusbar = ttk.Label(big_frame, text="This is a status bar", relief='sunken')
button.grid(row=0, column=0, sticky='nswe')
label.grid(row=0, column=1)
statusbar.grid(row=1, column=0, columnspan=2, sticky='we')
big_frame.grid_rowconfigure(0, weight=1)
big_frame.grid_columnconfigure(0, weight=1)
root.title("Grid Test")
root.geometry('300x150')
root.mainloop()
The code is mostly the same. Let’s go through the differences:
button.grid(row=0, column=0, sticky='nswe')
The row=0
and column=0
mean that the button will end up in the
top left corner of our root window. In math, the coordinate (0, 0)
usually means the bottom left corner, but in programming it almost
always means the top left corner.
sticky='nswe'
might look weird at first, but it’s actually simple to
understand. The n means north, s means south and so on. To be honest I
have no idea why Tk doesn’t just use l like left, r like right etc.
The sticky option is kind of like the fill option of pack()
.
sticky='nswe'
is like fill='both'
, sticky='we'
is like fill='x'
and so on.
label.grid(row=0, column=1)
This adds the label next to the button. It doesn’t stretch because we
didn’t set a sticky value, and it defaults to ''
.
statusbar.grid(row=1, column=0, columnspan=2, sticky='we')
columnspan=2
means that the status bar will be two columns wide, so it
fills the entire width of the window because we have two columns.
Note that we don’t have to grid the statusbar before gridding other widgets. Each widget goes to its own row and column so it doesn’t matter which order we add them in.
big_frame.grid_rowconfigure(0, weight=1)
big_frame.grid_columnconfigure(0, weight=1)
This is like setting expand=True
with pack. The button is packed to
row 0 and column 0, and this gives it as much space as possible.
Here’s another, more advanced grid example:
,-------------------.
| Calculator | X |
|-------------------|
| 7 | 8 | 9 | * | / |
|---+---+---+---+---|
| 4 | 5 | 6 | + | - |
|---+---+---+-------|
| 1 | 2 | 3 | |
|-------+---| = |
| 0 | . | |
`-------------------'
Something like this would be almost impossible to do with pack, but it’s easy with grid:
import tkinter
from tkinter import ttk
root = tkinter.Tk()
big_frame = ttk.Frame(root)
big_frame.pack(fill='both', expand=True)
# None means that we'll create the button later
rows = [
['7', '8', '9', '*', '/'],
['4', '5', '6', '+', '-'],
['1', '2', '3', None, None],
[None, None, '.', None, None],
]
for y, row in enumerate(rows):
for x, character in enumerate(row):
if character is not None:
# try this without width=3 so you'll know why i put it there
button = ttk.Button(big_frame, text=character, width=3)
button.grid(row=y, column=x, sticky='nswe')
# the widths of these buttons are set to smallest possible values because grid
# will make sure that they are wide enough, e.g. zerobutton is below '1' and
# '2', and it will have the same width as the '1' and '2' buttons together
zerobutton = ttk.Button(big_frame, text='0', width=1)
zerobutton.grid(row=3, column=0, columnspan=2, sticky='nswe')
equalbutton = ttk.Button(big_frame, text='=', width=1)
equalbutton.grid(row=2, column=3, rowspan=2, columnspan=2, sticky='nswe')
# let's make everything stretch when the window is resized
for x in range(5):
big_frame.grid_columnconfigure(x, weight=1)
for y in range(4):
big_frame.grid_rowconfigure(y, weight=1)
root.title("Calculator")
root.mainloop()
Place
The place(3tk) geometry manager can be used for absolute positioning with pixels, and that’s almost always a bad idea. But place supports relative positioning too, and it’s useful for things like message boxes. Tk has built-in message boxes too, but place is useful if you want a customized message box.
,-----------------------------------.
| Important Message | _ | o | X |
|-----------------------------------|
| |
| |
| This is a very important message. |
| |
| |
| |
| |
| ,------. |
| | OK | |
| `------' |
`-----------------------------------'
Here’s the code:
import tkinter
from tkinter import ttk
root = tkinter.Tk()
big_frame = ttk.Frame(root)
big_frame.pack(fill='both', expand=True)
label = ttk.Label(big_frame, text="This is a very important message.")
label.place(relx=0.5, rely=0.3, anchor='center')
button = ttk.Button(big_frame, text="OK", command=root.destroy)
button.place(relx=0.5, rely=0.8, anchor='center')
root.title("Important Message")
root.geometry('250x150')
root.mainloop()
Run the program and resize the window. The label and the button should “float” in it nicely.
label.place(relx=0.5, rely=0.3, anchor='center')
The relx
and rely
options are actually short for “relative x” and
“relative y”, not relaxing and relying. This means that the center of
the button will always be 50% from the left side of the window and 30%
from the top of the window. Other valid anchor values are n like north,
nw like north-west and so on.
button = ttk.Button(..., command=root.destroy)
This example isn’t as boring as our calculator is because the OK button
actually works. Calling root.destroy()
stops root.mainloop()
.
Which geometry manager should I use?
Pack is good for laying out big things. If you have a program with a few big widgets next to each other and a statusbar, pack is the best way to add them to the root window.
Grid works for big things too, but it’s best for things that are obviously grids, like our calculator.
Place is the easiest choice when you want to use percents to position widgets relatively, like we did in the example. This is useful with things like message dialogs.
Combining the geometry managers
Only use one geometry manager in one widget. The results can be surprising if you first grid something and then pack something else. Unfortunately tkinter doesn’t raise an exception if you try to do that, but don’t do it.
You can still use multiple geometry managers in one program with
ttk.Frame
. The frame is a simple widget that can be added to any other
parent widget, and then other widgets can be added into the frame with a
different geometry manager.
For example, this program uses pack, grid and place, but in separate frames:
import tkinter
from tkinter import ttk
def make_calculator_frame(big_frame):
frame = ttk.Frame(big_frame)
rows = [
['7', '8', '9', '*', '/'],
['4', '5', '6', '+', '-'],
['1', '2', '3', None, None],
[None, None, '.', None, None],
]
for y, row in enumerate(rows):
for x, character in enumerate(row):
if character is not None:
button = ttk.Button(frame, text=character, width=3)
button.grid(row=y, column=x, sticky='nswe')
zerobutton = ttk.Button(frame, text='0', width=1)
zerobutton.grid(row=3, column=0, columnspan=2, sticky='nswe')
equalbutton = ttk.Button(frame, text='=', width=1)
equalbutton.grid(row=2, column=3, rowspan=2, columnspan=2, sticky='nswe')
for x in range(5):
frame.grid_columnconfigure(x, weight=1)
for y in range(4):
frame.grid_rowconfigure(y, weight=1)
return frame
def make_message_frame(big_frame):
frame = ttk.Frame(big_frame)
label = ttk.Label(frame, text="This is a very important message.")
label.place(relx=0.5, rely=0.3, anchor='center')
button = ttk.Button(frame, text="OK")
button.place(relx=0.5, rely=0.8, anchor='center')
return frame
def main():
root = tkinter.Tk()
big_frame = ttk.Frame(root)
big_frame.pack(fill='both', expand=True)
calculator = make_calculator_frame(big_frame)
message = make_message_frame(big_frame)
statusbar = ttk.Label(big_frame, text="This is a status bar.",
relief='sunken')
statusbar.pack(side='bottom', fill='x')
calculator.pack(side='left', fill='y')
message.pack(side='left', fill='both', expand=True)
root.title("Useless GUI")
root.geometry('450x200')
root.minsize(400, 100)
root.mainloop()
if __name__ == '__main__':
main()
Summary
- Geometry managers are used for adding child widgets to parent widgets.
- Use pack for big and simple layouts, grid for griddy things and place for relative things.
- Don’t use multiple geometry managers in one widget. You can mix
different geometry managers with
ttk.Frame
by using one geometry manager in each frame.