Skillversitas

Containers and Layouts

Containers are the widgets that hold other widgets and decide where they go - the box that stacks them, the grid that arranges them in rows and columns, the frame that gives a region a border and a label, and the notebook that gives the window tabs. The layout model is the one that makes a multi-widget UI manageable: the top-level window holds one child, that child is a container, and the hierarchy of containers is what positions everything, with each container applying its own layout rule to its children. This chapter covers the boxes (the workhorse), the grid (2.24's modern layout), the frame and the event box, the fixed container, and the layout rules that keep a UI from collapsing when the window resizes.

Boxes: The Workhorse Container

import gtk

# the vertical box - the most-used container in PyGTK:
vbox = gtk.VBox(spacing=6)          # 6px between children
vbox.set_border_width(10)           # padding inside the border

# pack_start: add a child; the two expand/fill flags decide
# whether the child GROWS with the window and FILLS its cell
vbox.pack_start(toolbar, False, False, 0)   # fixed height, top
vbox.pack_start(main_area, True,  True, 0)  # grows, fills center
vbox.pack_start(status,   False, False, 0)  # fixed height, bottom

# the horizontal box - same API, horizontal direction:
hbox = gtk.HBox()
hbox.pack_start(left,  True,  True, 0)
hbox.pack_start(right, False, False, 0)

# the padding argument (last) adds space around that child only
vbox.pack_start(sep, False, False, 8)

The box is the workhorse: a VBox stacks children top-to-bottom, an HBox lays them left-to-right, and pack_start (or pack_end, which packs from the opposite side) adds a child with the two layout flags that matter. The expand flag decides whether the child is offered extra space when the window grows; the fill flag decides whether the child then fills that space or stays at its requested size. The canonical editor layout is the three-pack: a fixed toolbar (False, False) at the top, the growing main area (True, True) in the middle, a fixed status bar (False, False) at the bottom - and that one VBox with three pack_start calls is the skeleton of the running example's window. The box's spacing is the gap between all children, and the border_width is the padding inside the box's own edge - the two properties that give the layout its breathing room.

The Grid: Rows and Columns

import gtk

# GtkGrid - available from GTK 2.24, the modern 2-D layout:
grid = gtk.Grid()
grid.set_column_spacing(8)
grid.set_row_spacing(8)
grid.set_border_width(12)

# attach(child, left, top, width, height,
#        xexpand, yexpand, xalign, yalign)
grid.attach(gtk.Label("Name:"),  0, 0, 1, 1,
            False, False, gtk.Align.START, gtk.Align.CENTER)
grid.attach(name_entry,          1, 0, 1, 1,
            True,  False, gtk.Align.FILL, gtk.Align.CENTER)
grid.attach(gtk.Label("Email:"), 0, 1, 1, 1,
            False, False, gtk.Align.START, gtk.Align.CENTER)
grid.attach(email_entry,         1, 1, 1, 1,
            True,  False, gtk.Align.FILL, gtk.Align.CENTER)
grid.attach(submit,              1, 2, 1, 1,
            False, False, gtk.Align.END,   gtk.Align.FILL)

# the grid is the form layout: label column + field column,
# rows aligned, the fields expanding horizontally

The GtkGrid (from GTK 2.24) is the modern two-dimensional layout: children are attached at a column and row with a span, and the grid aligns the cells. The attach signature takes the child, the left and top cell, the width and height span (how many cells the child covers), the expand flags, and the alignment within the cell. The canonical use is the form - a label column and a field column, each row a field, the labels right-aligned to the fields, the fields expanding to fill their cell - and the grid is what makes that layout declarative instead of a box-within-a-box construction. The grid's column_spacing and row_spacing are the uniform gaps, and the alignment arguments (START, END, FILL, CENTER) place each child within its cell. Where the box is the one-directional stack, the grid is the two-directional arrangement, and the choice is the layout's shape: a toolbar or a status bar is a box, a form is a grid.

Frames, Event Boxes, and the Fixed

import gtk

# the frame - a labeled border around a region:
frame = gtk.Frame("Connection")
frame.set_label_align(0.0, 0.5)
frame.add(inner_box)      # the frame holds ONE child

# the event box - an invisible container that CATCHES events:
# a plain box does not receive events (it is not a windowed
# widget); the event box wraps a child to make it clickable
ebox = gtk.EventBox()
ebox.connect("button-press-event", on_click)
ebox.add(label)           # the label is now inside a windowed box

# the fixed - absolute positioning (the escape hatch):
fixed = gtk.Fixed()
fixed.put(knob, 120, 80)  # place at exact pixel coordinates
# the fixed does NOT respond to resize - the children stay
# where they were put, which is why it is the exception

The three special containers. The Frame is the labeled border - it holds one child and draws a titled frame around it, the standard way to group a region of a dialog ("Connection," "Options"). The EventBox solves a subtle problem: a plain box is not a windowed widget, so it does not receive input events - the EventBox wraps a child in a windowed container so the region is clickable or can track pointer motion, and it is the tool for making a label or a box an interactive target. The Fixed is absolute positioning - children are put at exact coordinates, and the fixed does not reflow on resize, which is why it is the escape hatch (the canvas the drawing chapter's free-form layout uses) rather than the default. The pattern across the three: each exists for a specific layout need the box and grid do not cover - the grouping border, the event capture, the absolute placement - and reaching for them is deliberate, not habitual.

The Layout Rules

  • The window holds one child, and it is a container. The hierarchy is window -> box -> everything, and the top-level's single-child rule is what makes the layout a tree instead of a pile.
  • One growing region, everything else fixed. The editor's main area is the True, True child; the toolbar and status are False, False. More than one growing region in a stack fights over the space.
  • Use the grid for forms, the box for stacks. A label-field layout is a grid; a toolbar or a status bar is a box. The container's shape matches the layout's shape.
  • The EventBox for the clickable region, the Fixed only for the canvas. The event box is the fix for a box that should catch input; the fixed is the absolute-positioning escape, and its non-reflow is the reason it stays the exception.
  • Spacing and border width are the breathing room. The uniform spacing between children and the border_width padding are what keep a dense layout from reading as crowded - set them on the container, not per-child, unless one child genuinely needs its own.

Summary

Containers position the widgets, and the layout is a tree: the window holds one child, a container, and the hierarchy of containers places everything. The VBox/HBox are the workhorse - pack_start with the expand/fill flags decides which child grows with the window, and the canonical editor is the three-pack (fixed toolbar, growing main area, fixed status). The GtkGrid (2.24) is the two-dimensional form layout - children attached at cells with span and alignment, the label-field grid its canonical use. The Frame is the labeled grouping border, the EventBox the windowed wrapper that makes a plain region catch input events, and the Fixed the absolute-positioning escape that does not reflow. The rules keep a layout from collapsing: one growing region in a stack, the container's shape matched to the layout's shape, and the spacing and border width as the uniform breathing room.