Modern Tkinter
This tutorial will quickly get you up and running with the latest Tk from Python, Tcl, Ruby, and Perl on macOS, Windows, or Linux. It provides all the essentials about core Tk concepts, the various widgets, layout, events and more that you need for your application.

The Grid Geometry Manager

We'll take a bit of a break from talking about different widgets (what to put onscreen) and focus instead on geometry management (where to put those widgets). We introduced the general idea of geometry management in the "Tk Concepts" chapter. Here, we focus on one specific geometry manager: grid.

As we've seen, grid lets you layout widgets in columns and rows. If you're familiar with using HTML tables for layout, you'll feel right at home here. This chapter illustrates the various ways you can tweak grid to give you all the control you need for your user interface.

Grid is one of several geometry managers available in Tk, but its mix of power, flexibility, and ease of use make it the best choice for general use. Its constraint model is a natural fit with today's layouts that rely on the alignment of widgets. There are other geometry managers in Tk: pack is also quite powerful but harder to use and understand, while place gives you complete control of positioning each element. Even widgets like paned windows, notebooks, canvas, and text that we'll explore later can act as geometry managers.

It's worth noting that grid was first introduced to Tk in 1996, several years after Tk became popular, and it took a while to catch on. Before that, developers had always used pack to do constraint-based geometry management. When grid came out, many developers kept using pack, and you'll still find it used in many Tk programs and documentation. While there's nothing technically wrong with pack, the algorithm's behavior is often hard to understand. More importantly, because the order that widgets are packed is significant in determining layout, modifying existing layouts can be more difficult. Aligning widgets in different parts of the user interface is also much trickier.

Grid has all the power of pack, produces nicer layouts (that align widgets both horizontally and vertically), and is easier to learn and use. Because of that, grid is the right choice for most developers most of the time. Start your new programs using grid, and switch old ones to grid as you make changes to an existing user interface.

The reference documentation for grid provides an exhaustive description of grid, its behaviors, and all options.

Columns and Rows

In grid, widgets are assigned a column number and a row number. These indicate each widget's position relative to other widgets. Widgets in the same column are above or below each other. Those in the same row are to the left or right of each other.

Column and row numbers must be positive integers (i.e., 0, 1, 2, ...). You don't have to start at 0 and can leave gaps in column and row numbers (e.g., column 1, 2, 10, 11, 12, 20, 21). This is useful if you plan to add more widgets in the middle of the user interface later.

The width of each column will vary depending on the width of the widgets contained within the column. Ditto for the height of each row. This means when sketching out your user interface and dividing it into rows and columns, you don't need to worry about each column or row being equal width.

Spanning Multiple Cells

Widgets can take up more than a single cell in the grid; to do this, we'll use the columnspan and rowspan options when gridding the widget. These are analogous to the "colspan" and "rowspan" attributes of HTML tables.

Here is an example of creating a user interface with multiple widgets, some that take up more than a single cell.

screen shot
Gridding multiple widgets.

from tkinter import *
from tkinter import ttk

root = Tk()

content = ttk.Frame(root)
frame = ttk.Frame(content, borderwidth=5, relief="ridge", width=200, height=100)
namelbl = ttk.Label(content, text="Name")
name = ttk.Entry(content)

onevar = BooleanVar(value=True)
twovar = BooleanVar(value=False)
threevar = BooleanVar(value=True)

one = ttk.Checkbutton(content, text="One", variable=onevar, onvalue=True)
two = ttk.Checkbutton(content, text="Two", variable=twovar, onvalue=True)
three = ttk.Checkbutton(content, text="Three", variable=threevar, onvalue=True)
ok = ttk.Button(content, text="Okay")
cancel = ttk.Button(content, text="Cancel")

content.grid(column=0, row=0)
frame.grid(column=0, row=0, columnspan=3, rowspan=2)
namelbl.grid(column=3, row=0, columnspan=2)
name.grid(column=3, row=1, columnspan=2)
one.grid(column=0, row=3)
two.grid(column=1, row=3)
three.grid(column=2, row=3)
ok.grid(column=3, row=3)
cancel.grid(column=4, row=3)

root.mainloop()
ttk::frame .c
ttk::frame .c.f -borderwidth 5 -relief ridge -width 200 -height 100
ttk::label .c.namelbl -text Name
ttk::entry .c.name
ttk::checkbutton .c.one -text One -variable one -onvalue 1; set one 1
ttk::checkbutton .c.two -text Two -variable two -onvalue 1; set two 0
ttk::checkbutton .c.three -text Three -variable three -onvalue 1; set three 1
ttk::button .c.ok -text Okay
ttk::button .c.cancel -text Cancel

grid .c -column 0 -row 0
grid .c.f -column 0 -row 0 -columnspan 3 -rowspan 2
grid .c.namelbl -column 3 -row 0 -columnspan 2
grid .c.name -column 3 -row 1 -columnspan 2
grid .c.one -column 0 -row 3
grid .c.two -column 1 -row 3
grid .c.three -column 2 -row 3
grid .c.ok -column 3 -row 3
grid .c.cancel -column 4 -row 3
require 'tk'
require 'tkextlib/tile'
root = TkRoot.new

content = Tk::Tile::Frame.new(root)
frame = Tk::Tile::Frame.new(content) {borderwidth 5; relief "ridge"; width 200; height 100}
namelbl = Tk::Tile::Label.new(content) {text "Name"}
name = Tk::Tile::Entry.new(content)
$option_one = TkVariable.new( 1 )
one = Tk::Tile::CheckButton.new(content) {text "One"; variable $option_one; onvalue 1}
$option_two = TkVariable.new( 0 )
two = Tk::Tile::CheckButton.new(content) {text "Two"; variable $option_two; onvalue 1}
$option_three = TkVariable.new( 1 ) 
three = Tk::Tile::CheckButton.new(content) {text "Three"; variable $option_three; onvalue 1}
ok = Tk::Tile::Button.new(content) {text "Okay"}
cancel = Tk::Tile::Button.new(content) {text "Cancel"}

content.grid :column => 0, :row => 0 
frame.grid :column => 0, :row => 0, :columnspan => 3, :rowspan => 2
namelbl.grid :column => 3, :row => 0, :columnspan => 2
name.grid :column => 3, :row => 1, :columnspan => 2
one.grid :column => 0, :row => 3
two.grid :column => 1, :row => 3
three.grid :column => 2, :row => 3
ok.grid :column => 3, :row => 3
cancel.grid :column => 4, :row => 3

Tk.mainloop
use Tkx;

my $mw = Tkx::widget->new(".");
my $content = $mw->new_ttk__frame;
my $frame = $content->new_ttk__frame(-borderwidth => 5, -relief => "ridge", -width => 200, -height => 100);
my $namelbl = $content->new_ttk__label(-text => "Name");
my $name = $content->new_ttk__entry;
$option_one = 1; $option_two = 0; $option_three = 1;
my $one = $content->new_ttk__checkbutton(-text => "One", -variable => \$option_one, -onvalue => 1); 
my $two = $content->new_ttk__checkbutton(-text => "Two", -variable => \$option_two, -onvalue => 1);
my $three = $content->new_ttk__checkbutton(-text => "Three", -variable => \$option_three, -onvalue => 1);
my $ok = $content->new_ttk__button(-text => "Okay");
my $cancel = $content->new_ttk__button(-text => "Cancel");

$content->g_grid(-column => 0, -row => 0);
$frame->g_grid(-column => 0, -row => 0, -columnspan => 3, -rowspan => 2);
$namelbl->g_grid(-column => 3, -row => 0, -columnspan => 2);
$name->g_grid(-column => 3, -row => 1, -columnspan => 2);
$one->g_grid(-column => 0, -row => 3);
$two->g_grid(-column => 1, -row => 3);
$three->g_grid(-column => 2, -row => 3);
$ok->g_grid(-column => 3, -row => 3);
$cancel->g_grid(-column => 4, -row => 3);

Tkx::MainLoop;

Layout within the Cell

The width of a column (and height of a row) depends on all the widgets contained in it. That means some widgets could be smaller than the cells they are placed in. If so, where exactly should they be put within their cells?

By default, if a cell is larger than the widget contained in it, the widget will be centered within it, both horizontally and vertically. The master's background color will display in the empty space around the widget. In the figure below, the widget in the top right is smaller than the cell allocated to it. The (white) background of the master fills the rest of the cell.

screen shot
Layout within the cell and the 'sticky' option.

The sticky option can change this default behavior. Its value is a string of 0 or more of the compass directions nsew, specifying which edges of the cell the widget should be "stuck" to. For example, a value of n (north) will jam the widget up against the top side, with any extra vertical space on the bottom; the widget will still be centered horizontally. A value of nw (north-west) means the widget will be stuck to the top left corner, with extra space on the bottom and right.

In Tkinter, you can also specify this as a list containing any of N, S, E, and W. It's a stylistic choice, and we'll tend to use the list format in this book.

Specifying two opposite edges, such as we (west, east), means the widget will be stretched. In this case, it will be stuck to both the left and right edges of the cell. So the widget will be wider than its "ideal" size.

If you want the widget to expand to fill up the entire cell, grid it with a sticky value of nsew (north, south, east, west), meaning it will stick to every side. This is shown in the bottom left widget in the above figure.

Most widgets have options that can control how they are displayed if they are larger than needed. For example, a label widget has an anchor option that controls where the label's text will be positioned within the widget's boundaries. The bottom left label in the figure above uses the default anchor (w, i.e., left side, vertically centered).

If you're having trouble getting things to line up the way you want them to, first make sure you know how large the widget is. As discussed with the label widget in the previous chapter, changing the widget's background or border can help.

Handling Resize

If you've tried to resize the example, you'll notice that nothing moves at all, as shown below.

screen shot
Resizing the window.

Even if you took a peek below and added the extra sticky options to our example, you'd still see the same thing. It looks like sticky may tell Tk how to react if the cell's row or column does resize but doesn't actually say that the row or columns should resize if any extra room becomes available. Let's fix that.

Every column and row in the grid has a weight option associated with it. This tells grid how much the column or row should grow if there is extra room in the master to fill. By default, the weight of each column or row is 0, meaning it won't expand to fill any extra space.

For the user interface to resize, we'll need to specify a positive weight to the columns and rows that we'd like to expand. You must provide weights for at least one column and one row. This is done using the columnconfigure and rowconfigure methods of grid. This weight is relative. If two columns have the same weight, they'll expand at the same rate. In our example, we'll give the three leftmost columns (holding the checkbuttons) weights of 3 and the two rightmost columns weights of 1. For every one pixel the right columns grow, the left columns will grow by three pixels. So as the window grows larger, most of the extra space will go to the left side.

screen shot
Resizing the window after adding weights.

Both columnconfigure and rowconfigure also take a minsize grid option, which specifies a minimum size you really don't want the column or row to shrink beyond.

Padding

Normally, each column or row will be directly adjacent to the next so that widgets will be right next to each other. This is sometimes what you want (think of a listbox and its scrollbar), but often you want some space between widgets. In Tk, this is called padding, and there are several ways you can choose to add it.

We've already actually seen one way, and that is using a widget's own options to add the extra space around it. Not all widgets have this, but one that does is a frame; this is useful because frames are most often used as the master to grid other widgets. The frame's padding option lets you specify a bit of extra padding inside the frame, whether the same amount for each of the four sides or even different for each.

A second way is using the padx and pady grid options when adding the widget. As you'd expect, padx puts a bit of extra space to the left and right, while pady adds extra space top and bottom. A single value for the option puts the same padding on both left and right (or top and bottom), while a two-value list lets you put different amounts on left and right (or top and bottom). Note that this extra padding is within the grid cell containing the widget.

If you want to add padding around an entire row or column, the columnconfigure and rowconfigure methods accept a pad option, which will do this for you.

Let's add the extra sticky, resizing, and padding behavior to our example (additions in bold).

from tkinter import *
from tkinter import ttk

root = Tk()

content = ttk.Frame(root, padding=(3,3,12,12))
frame = ttk.Frame(content, borderwidth=5, relief="ridge", width=200, height=100)
namelbl = ttk.Label(content, text="Name")
name = ttk.Entry(content)

onevar = BooleanVar()
twovar = BooleanVar()
threevar = BooleanVar()

onevar.set(True)
twovar.set(False)
threevar.set(True)

one = ttk.Checkbutton(content, text="One", variable=onevar, onvalue=True)
two = ttk.Checkbutton(content, text="Two", variable=twovar, onvalue=True)
three = ttk.Checkbutton(content, text="Three", variable=threevar, onvalue=True)
ok = ttk.Button(content, text="Okay")
cancel = ttk.Button(content, text="Cancel")

content.grid(column=0, row=0, sticky=(N, S, E, W))
frame.grid(column=0, row=0, columnspan=3, rowspan=2, sticky=(N, S, E, W))
namelbl.grid(column=3, row=0, columnspan=2, sticky=(N, W), padx=5)
name.grid(column=3, row=1, columnspan=2, sticky=(N,E,W), pady=5, padx=5)
one.grid(column=0, row=3)
two.grid(column=1, row=3)
three.grid(column=2, row=3)
ok.grid(column=3, row=3)
cancel.grid(column=4, row=3)

root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
content.columnconfigure(0, weight=3)
content.columnconfigure(1, weight=3)
content.columnconfigure(2, weight=3)
content.columnconfigure(3, weight=1)
content.columnconfigure(4, weight=1)
content.rowconfigure(1, weight=1)

root.mainloop()
ttk::frame .c -padding "3 3 12 12"
ttk::frame .c.f -borderwidth 5 -relief ridge -width 200 -height 100 
ttk::label .c.namelbl -text Name
ttk::entry .c.name
ttk::checkbutton .c.one -text One -variable one -onvalue 1; set one 1
ttk::checkbutton .c.two -text Two -variable two -onvalue 1; set two 0
ttk::checkbutton .c.three -text Three -variable three -onvalue 1; set three 1
ttk::button .c.ok -text Okay
ttk::button .c.cancel -text Cancel

grid .c -column 0 -row 0 -sticky nsew
grid .c.f -column 0 -row 0 -columnspan 3 -rowspan 2 -sticky nsew
grid .c.namelbl -column 3 -row 0 -columnspan 2 -sticky nw -padx 5
grid .c.name -column 3 -row 1 -columnspan 2 -sticky new -pady 5 -padx 5
grid .c.one -column 0 -row 3
grid .c.two -column 1 -row 3
grid .c.three -column 2 -row 3
grid .c.ok -column 3 -row 3
grid .c.cancel -column 4 -row 3

grid columnconfigure . 0 -weight 1
grid rowconfigure . 0 -weight 1
grid columnconfigure .c 0 -weight 3
grid columnconfigure .c 1 -weight 3
grid columnconfigure .c 2 -weight 3
grid columnconfigure .c 3 -weight 1
grid columnconfigure .c 4 -weight 1
grid rowconfigure .c 1 -weight 1
require 'tk'
require 'tkextlib/tile'
root = TkRoot.new

content = Tk::Tile::Frame.new(root) {padding "3 3 12 12"}
frame = Tk::Tile::Frame.new(content) {borderwidth 5; relief "ridge"; width 200; height 100}
namelbl = Tk::Tile::Label.new(content) {text "Name"}
name = Tk::Tile::Entry.new(content)
$option_one = TkVariable.new( 1 )
one = Tk::Tile::CheckButton.new(content) {text "One"; variable $option_one; onvalue 1}
$option_two = TkVariable.new( 0 )
two = Tk::Tile::CheckButton.new(content) {text "Two"; variable $option_two; onvalue 1}
$option_three = TkVariable.new( 1 ) 
three = Tk::Tile::CheckButton.new(content) {text "Three"; variable $option_three; onvalue 1}
ok = Tk::Tile::Button.new(content) {text "Okay"}
cancel = Tk::Tile::Button.new(content) {text "Cancel"}

content.grid :column => 0, :row => 0, :sticky => 'nsew'
frame.grid :column => 0, :row => 0, :columnspan => 3, :rowspan => 2, :sticky => 'nsew'
namelbl.grid :column => 3, :row => 0, :columnspan => 2, :sticky => 'nw', :padx => 5
name.grid :column => 3, :row => 1, :columnspan => 2, :sticky => 'new', :pady => 5, :padx => 5
one.grid :column => 0, :row => 3
two.grid :column => 1, :row => 3
three.grid :column => 2, :row => 3
ok.grid :column => 3, :row => 3
cancel.grid :column => 4, :row => 3

TkGrid.columnconfigure( root, 0, :weight => 1 )
TkGrid.rowconfigure( root, 0, :weight => 1 )
TkGrid.columnconfigure( content, 0, :weight => 3 )
TkGrid.columnconfigure( content, 1, :weight => 3 )
TkGrid.columnconfigure( content, 2, :weight => 3 )
TkGrid.columnconfigure( content, 3, :weight => 1 )
TkGrid.columnconfigure( content, 4, :weight => 1 )
TkGrid.rowconfigure( content, 1, :weight => 1)

Tk.mainloop
use Tkx;

my $mw = Tkx::widget->new(".");
my $content = $mw->new_ttk__frame(-padding => "3 3 12 12");
my $frame = $content->new_ttk__frame(-borderwidth => 5, -relief => "ridge", -width => 200, -height => 100);
my $namelbl = $content->new_ttk__label(-text => "Name");
my $name = $content->new_ttk__entry;
$option_one = 1; $option_two = 0; $option_three = 1;
my $one = $content->new_ttk__checkbutton(-text => "One", -variable => \$option_one, -onvalue => 1); 
my $two = $content->new_ttk__checkbutton(-text => "Two", -variable => \$option_two, -onvalue => 1);
my $three = $content->new_ttk__checkbutton(-text => "Three", -variable => \$option_three, -onvalue => 1);
my $ok = $content->new_ttk__button(-text => "Okay");
my $cancel = $content->new_ttk__button(-text => "Cancel");

$content->g_grid(-column => 0, -row => 0, -sticky => "nsew");
$frame->g_grid(-column => 0, -row => 0, -columnspan => 3, -rowspan => 2, -sticky => "nsew");
$namelbl->g_grid(-column => 3, -row => 0, -columnspan => 2, -sticky => "nw", -padx => 5);
$name->g_grid(-column => 3, -row => 1, -columnspan => 2, -sticky => "new", -pady => 5, -padx => 5);
$one->g_grid(-column => 0, -row => 3);
$two->g_grid(-column => 1, -row => 3);
$three->g_grid(-column => 2, -row => 3);
$ok->g_grid(-column => 3, -row => 3);
$cancel->g_grid(-column => 4, -row => 3);

$mw->g_grid_columnconfigure(0, -weight => 1);
$mw->g_grid_rowconfigure(0, -weight => 1);
$content->g_grid_columnconfigure(0, -weight => 3);
$content->g_grid_columnconfigure(1, -weight => 3);
$content->g_grid_columnconfigure(2, -weight => 3);
$content->g_grid_columnconfigure(3, -weight => 1);
$content->g_grid_columnconfigure(4, -weight => 1);
$content->g_grid_rowconfigure(1, -weight => 1);

Tkx::MainLoop;

This looks more promising. Play around with the example to get a feel for the resize behavior.

screen shot
Grid example, handling in-cell layout and resize.

Additional Grid Features

If you look at the reference documentation for grid, you'll see many other things you can do with grid. Here are a few of the more useful ones.

Querying and Changing Grid Options

Like widgets themselves, it's easy to introspect the various grid options or change them. Setting options when you first grid the widget is certainly convenient, but you can change them anytime you'd like.

The slaves method will tell you all the widgets that have been gridded inside a master, or optionally those within just a certain column or row. The info method will return a list of all the grid options for a widget and their values. Finally, the configure method lets you change one or more grid options on a widget.

These are illustrated in this interactive session:

>>> content.grid_slaves()
[<tkinter.ttk.Button object .!frame.!button2>, <tkinter.ttk.Button object .!frame.!button>,
<tkinter.ttk.Checkbutton object .!frame.!checkbutton3>, <tkinter.ttk.Checkbutton object .!frame.!checkbutton2>,
<tkinter.ttk.Checkbutton object .!frame.!checkbutton>, <tkinter.ttk.Entry object .!frame.!entry>, 
<tkinter.ttk.Label object .!frame.!label>, <tkinter.ttk.Frame object .!frame.!frame>]
>>> for w in content.grid_slaves(): print(w)
...
.!frame.!button2
.!frame.!button
.!frame.!checkbutton3
.!frame.!checkbutton2
.!frame.!checkbutton
.!frame.!entry
.!frame.!label
.!frame.!frame
>>> for w in content.grid_slaves(row=3): print(w)
...
.!frame.!button2
.!frame.!button
.!frame.!checkbutton3
.!frame.!checkbutton2
.!frame.!checkbutton
>>> for w in content.grid_slaves(column=0): print(w)
...
.!frame.!checkbutton
.!frame.!frame
>>> namelbl.grid_info()
{'in': <tkinter.ttk.Frame object .!frame>, 'column': 3, 'row': 0, 'columnspan': 2, 'rowspan': 1, 
'ipadx': 0, 'ipady': 0, 'padx': 5, 'pady': 0, 'sticky': 'nw'}
>>> namelbl.grid_configure(sticky=(E,W))
>>> namelbl.grid_info()
{'in': <tkinter.ttk.Frame object .!frame>, 'column': 3, 'row': 0, 'columnspan': 2, 'rowspan': 1, 
'ipadx': 0, 'ipady': 0, 'padx': 5, 'pady': 0, 'sticky': 'ew'}
% grid slaves .c
.c.cancel .c.ok .c.three .c.two .c.one .c.name .c.namelbl .c.f
% grid slaves .c -row 3
.c.cancel .c.ok .c.three .c.two .c.one
% grid slaves .c -column 0
.c.one .c.f
% grid info .c.namelbl
-in .c -column 3 -row 0 -columnspan 2 -rowspan 1 -ipadx 0 -ipady 0 -padx 5 -pady 0 -sticky nw
% grid configure .c.namelbl -sticky ew
% grid info .c.namelbl
-in .c -column 3 -row 0 -columnspan 2 -rowspan 1 -ipadx 0 -ipady 0 -padx 5 -pady 0 -sticky ew
>> TkGrid.slaves(content, nil)
=> [#<Tk::Tile::TButton:0x380fa4 @path=".w00000.w00008">, 
#<Tk::Tile::TButton:0x500d20 @path=".w00000.w00007">, 
#<Tk::Tile::TCheckButton:0x50aac8 @path=".w00000.w00006">, 
#<Tk::Tile::TCheckButton:0x524158 @path=".w00000.w00005">, 
#<Tk::Tile::TCheckButton:0x54b4b0 @path=".w00000.w00004">, 
#<Tk::Tile::TEntry:0x5d07a0 @path=".w00000.w00003">, 
#<Tk::Tile::TLabel:0x5fa9b0 @path=".w00000.w00002">,
#<Tk::Tile::TFrame:0x5ff280 @visual=nil, @container=nil, @colormap=nil, 
@path=".w00000.w00001", @db_class=Tk::Tile::TFrame, @classname="TFrame">]
>> TkGrid.slaves(content, :row => 3)
=> [#<Tk::Tile::TButton:0x380fa4 @path=".w00000.w00008">, 
#<Tk::Tile::TButton:0x500d20 @path=".w00000.w00007">, 
#<Tk::Tile::TCheckButton:0x50aac8 @path=".w00000.w00006">, 
#<Tk::Tile::TCheckButton:0x524158 @path=".w00000.w00005">, 
#<Tk::Tile::TCheckButton:0x54b4b0 @path=".w00000.w00004">]
>> TkGrid.slaves(content, :column => 0)
=> [#<Tk::Tile::TCheckButton:0x54b4b0 @path=".w00000.w00004">, 
#<Tk::Tile::TFrame:0x5ff280 @visual=nil, @container=nil, @colormap=nil, 
@path=".w00000.w00001", @db_class=Tk::Tile::TFrame, @classname="TFrame">]
>> TkGrid.info(namelbl)
=> {"ipadx"=>0, "ipady"=>0, "columnspan"=>2, "row"=>0, "column"=>3, 
"in"=>#<Tk::Tile::TFrame:0x60ef78 @visual=nil, @container=nil, @colormap=nil,
 @path=".w00000", @db_class=Tk::Tile::TFrame, @classname="TFrame">, "rowspan"=>1, 
"sticky"=>"nw", "padx"=>5, "pady"=>0}
>> TkGrid.configure(namelbl, :sticky => 'ew')
=> ""
>> TkGrid.info(namelbl)
=> {"ipadx"=>0, "ipady"=>0, "columnspan"=>2, "row"=>0, "column"=>3, 
"in"=>#<Tk::Tile::TFrame:0x60ef78 @visual=nil, @container=nil, @colormap=nil, 
@path=".w00000", @db_class=Tk::Tile::TFrame, @classname="TFrame">, "rowspan"=>1, 
"sticky"=>"ew", "padx"=>5, "pady"=>0}
Perl> $content->g_grid_slaves
.f.b2 .f.b .f.c3 .f.c2 .f.c .f.e .f.l .f.f
Perl> $content->g_grid_slaves(-row => 3)
.f.b2 .f.b .f.c3 .f.c2 .f.c
Perl> $content->g_grid_slaves(-column => 0)
.f.c .f.f
Perl> $namelbl->g_grid_info
-in .f -column 3 -row 0 -columnspan 2 -rowspan 1 -ipadx 0 -ipady 0 -padx 5 -pady 0 -sticky nw
Perl> $namelbl->g_grid_configure(-sticky => "ew")

Perl> $namelbl->g_grid_info
-in .f -column 3 -row 0 -columnspan 2 -rowspan 1 -ipadx 0 -ipady 0 -padx 5 -pady 0 -sticky ew	

Internal Padding

You saw how the padx and pady grid options added extra space around the outside of a widget. There's also a less used type of padding called "internal padding" controlled by the grid options ipadx and ipady.

The difference can be subtle. Let's say you have a frame that's 20x20, and specify normal (external) padding of 5 pixels on each side. The frame will request a 20x20 rectangle (its natural size) from the geometry manager. Normally, that's what it will be granted, so it'll get a 20x20 rectangle for the frame, surrounded by a 5-pixel border.

With internal padding, the geometry manager will effectively add the extra padding to the widget when figuring out its natural size, as if the widget has requested a 30x30 rectangle. If the frame is centered or attached to a single side or corner (using sticky), we'll end up with a 20x20 frame with extra space around it. If, however, the frame is set to stretch (i.e., a sticky value of we, ns, or nwes), it will fill the extra space, resulting in a 30x30 frame with no border.

Forget and Remove

The forget method of grid removes slaves from the grid they're currently part of. It takes a list of one or more slave widgets as arguments. This does not destroy the widget altogether but takes it off the screen as if it had not been gridded in the first place. You can grid it again later, though any grid options you'd originally assigned will have been lost.

The remove method of grid works the same, except that the grid options will be remembered if you grid it again later.

Nested Layouts

As your user interface gets more complicated, the grid that organizes all your widgets can get increasingly complicated. This can make changing and maintaining your program very difficult.

Luckily, you don't have to manage your entire user interface with a single grid. If you have one area of your user interface that is fairly independent of others, create a new frame and grid the widgets in the area within that frame. For example, if you were building a graphics editor with multiple palettes, toolbars, etc., each one of those areas might be a candidate for putting in its own frame.

In theory, these frames, each with its own grid, can be nested arbitrarily deep, though, in practice, this usually doesn't go beyond a few levels. This can be a big help in modularizing your program. If, for example, you have a palette of drawing tools, you can create the whole thing in a separate function or class. It would be responsible for creating all the component widgets, gridding them together, setting up event bindings, etc. The details of how things work inside that palette can be contained in that one piece of code. Your main program only needs to know about the single frame widget containing your palette.

Our examples have shown just a hint of this: a content frame was gridded into the main window, and then all the other widgets gridded into the content frame.

As your own programs grow, you'll likely run into situations where changing the layout of one part of your interface requires code changes to the layout of another part. That may be a clue to reconsider how you're using grid and if splitting out components into separate frames would help.