3.3 Empaquetar mediante tablas

Vamos a aprender otro modo de empaquetar: las tablas. Pueden ser extremadamente útiles en determinadas situaciones. Usando tablas, creamos una cuadrícula (grid) en la que podemos situar los widgets. Los widgets pueden ocupar tantos espacios como especifiquemos.

En primer lugar consideraremos la función tableNew (Nueva tabla)):

tableNew :: Int -> Int -> Bool -> IO Table

El primer argumento es el número de filas que tendrá la tabla, y el segundo, el número de columnas.

El argumento booleano (homogéneo) indica como se establece el tamaño de las cajas de la tabla. Si homogéneo se establece a True, entonces las cajas se crean al tamaño del mayor widget de la tabla, y todas iguales. Si homogéneo se establece a False, el tamaño de las cajas de la tabla está determinado por el widget más alto de su fila, and el widget más ancho de su columna. Por ejemplo, supón que tienes una matríz de 10x10 botones cada uno etiquetado con los numeros del 1 al 100. Así, te aparecen tres posibles anchuras, la de los números del 1 al 9, la de los números del 10 al 99 y la del 100. Si has establecido en True el valor de homogéneo, todos los botones serán iguales y el ancho corresponderá al del botón 100. Sin embargo, si el valor que has puesto es Falso, la última columna mantendrá el ancho del botón de 100 y las otras el de los botones correspondientes al 10 al 99. En este ejemplo las alturas de los botones serían iguales con las dos opciones, ya que sólo tenemos una posible altura.

Las filas y colunas se numeran desde 0 hasta n, donde n es el número especificado en la llamada a tableNew. Así si especificas filas = 2 y columnas = 2, la distribución parecerá algo así:

 0          1          2
0+----------+----------+
 |          |          |
1+----------+----------+
 |          |          |
2+----------+----------+

Fíjate en que el sistema de coordenadas empieza en la esquina superior izquierda. Para colocar un widget en una caja, usa la siguiente función:

tableAttach :: (TableClass self, WidgetClass child)
  => self            -- self         - La tabla.
  -> child           -- child        - El widget a añadir.
  -> Int             -- leftAttach   - El número de la columna para situar el lado izquierdo 
                     --                del widget a colocar.
  -> Int             -- rightAttach  - El número de la columna para situar el lado derecho
                     --                del widget a colocar.
  -> Int             -- topAttach    - El número de la fila donde situar la parte superior del
                     --                widget a colocar.
  -> Int             -- bottomAttach - El número de la fila donde situar la parte inferior del
                     --                widget a colocar.
  -> [AttachOptions] -- xoptions     - Se emplea para indicar las propiedades del widget hijo
                     --                cuando la tabla modifica su tamaño (horizontales)
  -> [AttachOptions] -- yoptions     - Igual que xoptions, exceptuando que el campo determina
                     --                el comportamiento vertical
  -> Int             -- xpadding     - Un valor entero que especifica el espacio en blanco a
                     --                izquierda y derecha de la tabla del widget que se añade.
  -> Int             -- ypadding     - El espacio arriba y abajo del widget.
  -> IO ()

El primer argumento, (self), es la tabla que has creado y el segundo, (child), es el widget que quieres colocar en la tabla.

Los argumentos izquierdo y derecho (leftAttach, rightAttach) indican donde debe ponerse el widget, y cuantas cajas usar. Si quieres poner un botón en la parte de abajo a la derecha de tu tabla de 2x2, y quieres colocar solo esa entrada, leftAttach debería ser = 1, rightAttach = 2, topAttach = 1, y bottomAttach = 2.

Ahora, si quieres que un widget llene completamenta la fila superior de nuestra tabla de 2x2, deberías susar los siguientes valores: leftAttach = 0, rightAttach = 2, topAttach = 0, and bottomAttach = 1.

xoptions y yoptions se usan para indicar las opciones de empaquetado y la lista puede contener más de una para permitir múltiples opciones.

Las posibles opciones son:

Fill
Si la caja de la tabla es mayor que el widget, y se especifica Fill, el widget se expandirá hasta ocupar todo el espacio disponible. (como un gas cualquiera).
Shrink
Si el widget de la tabla tiene disponible menos espacio del que necesita (normalmente porque el usuario ha cambiado el tamaño de la tabla), los widgets deberían ser empujados de la parte inferior de la tabla y desaparecerían. Si se especifica Shrink los widgets de reducirán con la tabla.
Expand
Esto originará que la tabla se expanda hasta usar todo el espacio disponible en la ventana.

El padding funciona como en las cajas, dejando un área libre alrededor del widget, del tamaño especificado en píxeles

tableAttach tiene muchas opciones, así que ponemos un resumen:

tableAttachDefaults :: (TableClass self, WidgetClass widget)
  => self   -- self         - La tabla.
  -> widget -- widget       - El widget a añadir.
  -> Int    -- leftAttach   - El número de la columna en el que se situará el lado
            --                izquierdo del widget a añadir.
  -> Int    -- rightAttach  - El número de la columna en el que se situará el lado
            --                derecho del widget a añadir.
  -> Int    -- topAttach    - El número de la fila en el que se situará el lado superior
            --                del widget.
  -> Int    -- bottomAttach - El número de la fila en el que se situará el lado inferior
            --                del widget.
  -> IO ()

Los valores usados para los parámetros de [AttachOptions] son [Expand, Fill], y el espacio (padding) se fija en 0. El resto de los argumentos son los mismos que para la función previa.

También tenemos tableSetRowSpacing y tableSetColSpacing. Estas funciones colocan espacios en la fila (row) o columna (col) indicada.

tableSetRowSpacing :: TableClass self=> self-> Int -> Int -> IO ()
tableSetColSpacing :: TableClass self => self -> Int -> Int -> IO ()

El primer argumento Int es la fila/columna y el segundo es el espacio en píxeles. Fíjate que en el caso de las columnas, el espacio se situa a la derecha de la columna y en las filas, bajo la fila.

Puedes fijar un espacio fijo para todas las filas y/o las columnas con:

tableSetRowSpacings :: TableClass self=> self-> Int -> IO ()

y:

tableSetColSpacings :: TableClass self => self -> Int -> IO ()

Fíjate que con estas llamadas, la última fila y la última columna no consiguien ningún espacio.

Ejemplo de empaquetado con tabla

Vamos a hacer una ventana con tres botones en una tabla 2x2. Los primeros dos botones se colocarán en la fila de arriba. Un tercer botón (Quit) se colocará en la fila de abajo, ocupando ambas columnas. Quedará así:

Table packing

Aquí está el código fuente:

import Graphics.UI.Gtk

main :: IO ()
main = do
  initGUI
  window  <- windowNew
  set window [windowTitle := "Table", containerBorderWidth := 20,
              windowDefaultWidth := 150, windowDefaultHeight := 100]
  table   <- tableNew 2 2 True
  containerAdd window table
  button1 <- buttonNewWithLabel "On"
  onClicked button1 (buttonSwitch button1)
  tableAttachDefaults table button1 0 1 0 1
  button2 <- buttonNewWithLabel "Off"
  onClicked button2 (buttonSwitch button2)
  tableAttachDefaults table button2 1 2 0 1
  button3 <- buttonNewWithLabel "Quit"
  onClicked button3 mainQuit
  tableAttachDefaults table button3 0 2 1 2
  onDestroy window mainQuit
  widgetShowAll window
  mainGUI

buttonSwitch :: Button -> IO ()
buttonSwitch b = do
  txt <- buttonGetLabel b
  let newtxt = case txt of
                 "Off" -> "On"
                 "On"  -> "Off"
  buttonSetLabel b newtxt

La función buttonSwitch es asocia a ambos botones de la fila superior. La función buttonGetLabel es un ejemplo de cómo conseguir un atributo de un widget usando un método estándar. Hay una alternativa más general get (análoga a set) que toma un widget y un atributo. En el ejemplo anterior sería:

  txt <- get b buttonLabel

con el mismo resultado.