3.1 Packing Widgets

When creating an application, you'll want to put more than one widget inside a window. Our first "hello world" example only used one widget so we could simply use set to specify a containerChild widget for window, or use containerAdd to "pack" the widget into the window. But when you want to put more than one widget into a window, how do you control where that widget is positioned? This is where packing comes in.

Theory of Packing Boxes

Most packing is done by creating boxes. These are invisible widget containers that we can pack our widgets into which come in two forms: a horizontal box and a vertical box. When packing widgets into a horizontal box, the objects are inserted horizontally from left to right or right to left depending on the call used. In a vertical box, widgets are packed from top to bottom or vice versa. You may use any combination of boxes inside or beside other boxes to create the desired effect.

To create a new horizontal box, we use hBoxNew, and for vertical boxes, vBoxNew. Both take a Bool and an Int parameter. The first parameter will give all children equal space allotments if set to True and the second sets the number of pixels to place by default between the children.

The boxPackStart and boxPackEnd functions are used to place objects inside of these containers. The boxPackStart function will start at the top and work its way down in a VBox, and pack left to right in an HBox. boxPackEnd will do the opposite, packing from bottom to top in a VBox, and right to left in an HBox. Using these functions allows us to right justify or left justify our widgets and they may be mixed in any way to achieve the desired effect. We will use boxPackStart in most of our examples.

An object may be another container or a widget. In fact, many widgets are actually containers themselves—including button, but we usually only use a label inside a button.

Packed buttons

import Graphics.UI.Gtk

main :: IO ()
main = do
  initGUI
  window  <- windowNew
  hbox    <- hBoxNew True 10
  button1 <- buttonNewWithLabel "Button 1"
  button2 <- buttonNewWithLabel "Button 2"
  set window [windowDefaultWidth := 200, windowDefaultHeight := 200,
              containerBorderWidth := 10, containerChild := hbox]
  boxPackStart hbox button1 PackGrow 0
  boxPackStart hbox button2 PackGrow 0
  onDestroy window mainQuit
  widgetShowAll window
  mainGUI

By using boxPackStart or boxPackEnd, GTK knows where you want to place your widgets so it can do automatic resizing and other nifty things.

boxPackStart :: (WidgetClass child, BoxClass self) => self -> child -> Packing -> Int -> IO ()
boxPackEnd :: (WidgetClass child, BoxClass self) => self -> child -> Packing -> Int -> IO ()

The Packing parameter specifies the way the widgets in the container behave when the window is resized. PackNatural means the widgets will retain their size and stay where they are, PackGrow means they will be resized, and using PackRepel the widgets will be padded equally on both sides. The last parameter is an Int, which specifies any extra padding to be put between this child and its neighbours.

Note that the packing only applies to the dimension of the box. If, for example, you specify PackNatural instead of PackGrow in the above, resizing horizontally will keep the buttons at their original size, but resizing vertically will also resize the buttons. This is because the buttons are placed homogeneously in the horizontal box (the first parameter is True) and the box itself will resize with the window. The next example will make the effects more clear.