Página siguiente Página anterior Índice general

4. Widgets usados para empaquetar

Al crear una aplicación normalmente se quiere que haya más de un widget por ventana. Nuestro primer ejemplo sólo usaba un widget por lo que usábamos la función gtk_container_add para ``empaquetar'' el widget en la ventana. Pero cuando cuando se quiere poner más de un widget en una ventana, ¿Cómo podemos controlar donde aparecerá el widget?. Aquí es donde entra el empaquetamiento.

4.1 Empaquetamiento usando cajas

Normalmente para empaquetar se usan cajas, tal y como ya hemos visto. Éstas son widgets invisibles que pueden contener nuestros widgets de dos formas diferentes, horizontal o verticalmente. Al hacerlo de la primera forma los objetos son insertados de izquierda a derecha o al revés (dependiendo de que llamada se use). Lo mismo ocurre en los verticales (de arriba a bajo o al revés). Se pueden usar tantas cajas como se quieran para conseguir cualquier tipo de efecto.

Para crear una caja horizontal llamamos a gtk_hbox_new() y para las verticales gtk_vbox_new(). Las funciones usadas para introducir objetos dentro son gtk_box_pack_start() y gtk_box_pack_end(). La primera llenará de arriba a abajo o de izquierda a derecha. La segunda lo hará al revés. Usando estas funciones podemos ir metiendo widgets con una justificación a la izquierda o a la derecha y además podemos mezclarlas de cualquier manera para conseguir el efecto deseado. Nosotros usaremos gtk_box_pack_start() en la mayoria de nuestros ejemplos. Un objeto puede ser otro contenedor o un widget. De hecho, muchos widgets son contenedores, incluyendo el widget botón (button) (aunque normalmente lo único que meteremos dentro será una etiqueta de texto).

Mediante el uso de estas funciones le decimos a GTK dónde queremos situar nuestros widgets, y GTK podrá, por ejemplo, cambiarles el tamaño de forma automática y hacer otras cosas de utilidad. También hay unas cuantas opciones que tienen que ver con la forma en la que los widgets serán empaquetados. Como puede imaginarse, este método nos da una gran flexibilidad a la hora de colocar y crear widgets.

4.2 Detalles de la cajas.

Debido a esta flexibilidad el empaquetamiento puede ser confuso al principio. Hay muchas opciones y no es obvio como encajan unas con otras. Pero en la práctica sólo hay cinco estilos diferentes.

Imagen de ejemplo sobre el empaquetado de cajas

Cada línea contiene una caja horizontal (hbox) con diferentes botones. La llamada a gtk_box_pack es una manera de conseguir empaquetar cada uno de los botones dentro de la caja. Eso sí, cada uno de ellos se empaqueta de la misma forma que el resto (se llama con los mismos argumentos a gtk_box_pack_start()).

Esta es la declaración de la función gtk_box_pack_start:

void gtk_box_pack_start( GtkBox    *box,
                         GtkWidget *child,
                         gint       expand,
                         gint       fill,
                         gint       padding );

El primer argumento es la caja dónde se empaqueta, el segundo el objeto. Por ahora el objeto será un botón, ya que estamos empaquetando botones dentro de las cajas.

El argumento expand de gtk_box_pack_start() y de gtk_box_pack_end() controla si los widgets son expandidos en la caja para rellenar todo el espacio de la misma (TRUE) o si por el contrario no se usa el espacio extra dentro de la caja (FALSE). Poniendo FALSE en expand podremos hacer que nuestros widgets tengan una justaficación a la derecha o a la izquierda. En caso contrario, los widgets se expandirán para llenar toda la caja, y podemos conseguir el mismo efecto utilizando sólo una de las funciones gtk_box_pack_start o pack_end.

El argumento fill de gtk_box controla si el espacio extra se mete dentro de los objetos (TRUE) o como relleno extra (FALSE). Sólo tiene efecto si el argumento de expansión también es TRUE.

Al crear una nueva ventana la función debe ser parecida a esta:

GtkWidget *gtk_hbox_new (gint homogeneous,
                         gint spacing);

El argumento homogeneous (tanto para gtk_hbox_new como para gtk_vbox_new) controla si cada objeto en la caja tiene el mismo tamaño (anchura en una hbox o altura en una vbox). Si se activa, el argumento expand de las rutinas gtk_box_pack siempre estará activado.

Puede que el lector se esté haciendo la siguiente pregunta: ¿Cúal es la diferencia entre espaciar (establecido cuando se crea la caja) y rellenar (determinado cuando se empaquetan los elementos)? El espaciado se añade entre objetos, y el rellenado se hace en cada parte de cada objeto. La siguiente figura debe aclarar la cuestión.

Box Packing Example Image

Estudiemos el código usado para crear las imágenes anteriores. Con los comentarios no debería de haber ningún problema para entenderlo.

4.3 Programa demostración de empaquetamiento

/* comienzo del ejemplo packbox packbox.c */

#include <stdio.h>
#include "gtk/gtk.h"

void
delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
{
    gtk_main_quit ();
}

/* Hacemos una hbox llena de etiquetas de botón. Los argumentos para las variables que estamos
 * interesados son pasados a esta función. No mostramos la caja, pero hacemos todo lo que
 * queremos./ 

GtkWidget *make_box (gint homogeneous, gint spacing,
                     gint expand, gint fill, gint padding) 
{
    GtkWidget *box;
    GtkWidget *button;
    char padstr[80];
    
    /* creamos una nueva caja con los argumentos homogeneous y spacing */       
    box = gtk_hbox_new (homogeneous, spacing);
    
    /* crear una serie de botones */
    button = gtk_button_new_with_label ("gtk_box_pack");
    gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
    gtk_widget_show (button);
    
    button = gtk_button_new_with_label ("(box,");
    gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
    gtk_widget_show (button);
    
    button = gtk_button_new_with_label ("button,");
    gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
    gtk_widget_show (button);
    
    /* Este botón llevará por etiqueta el valor de expand*/
    if (expand == TRUE)
            button = gtk_button_new_with_label ("TRUE,");
    else
            button = gtk_button_new_with_label ("FALSE,");
    
    gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
    gtk_widget_show (button);
    
    /* Este es el mismo caso que el de arriba, pero más compacto*/

    button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,");
    gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
    gtk_widget_show (button);
    
    sprintf (padstr, "%d);", padding);
    
    button = gtk_button_new_with_label (padstr);
    gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
    gtk_widget_show (button);
    
    return box;
}

int
main (int argc, char *argv[])
{
    GtkWidget *window;
    GtkWidget *button;
    GtkWidget *box1;
    GtkWidget *box2;
    GtkWidget *separator;
    GtkWidget *label;
    GtkWidget *quitbox;
    int which;
    
    /* ¡No olvidar la siguiente llamada! */

    gtk_init (&argc, &argv);
    
    if (argc != 2) {
        fprintf (stderr, "usage: packbox num, where num is 1, 2, or 3.\n");
        /* this just does cleanup in GTK, and exits with an exit status of 1. */
      /* hacemos limpieza en GTK y devolvemos el valor de 1 */
        gtk_exit (1);
    }
    
    which = atoi (argv[1]);

    /* Creamos la ventana */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

    /* Siempre hay que conectar la señal de destrucción con la ventana principal. Esto es muy importante
     * para que el comportamiento de la ventana sea intuitivo. */ 
    gtk_signal_connect (GTK_OBJECT (window), "delete_event",
                        GTK_SIGNAL_FUNC (delete_event), NULL);
    gtk_container_border_width (GTK_CONTAINER (window), 10);
    
    /* Cramos una caja vertical donde empaquetaremos las cajas horizontales.
     * Así podemos apilar las cajas horizontales llenas con botones una encima de las otras.
     */
    box1 = gtk_vbox_new (FALSE, 0);
    
    /* Aclaramos cúal es el ejemplo a mostrar. Se corresponde con las imágenes anteriores. */
    switch (which) {
    case 1:
        
      /* creamos una nueva etiqueta. */
        label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
        
      /* Alineamos la etiqueta a la izquierda. Está función será discutida en detalle en la 
       * sección de los atributos de los widgets. */
        gtk_misc_set_alignment (GTK_MISC (label), 0, 0);

      /* Empaquetamos la etiqueta en la caja vertical (vbox box1). Siempre hay que recordar
       * que los widgets añadidos a una vbox serán empaquetados uno encimo de otro. */

        gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);

      /* mostramos la etiqueta.*/
        gtk_widget_show (label);
        
      /* llamada a la función que hace las cajas. Los argumentos son homogenous = FALSE, 
       * expand = FALSE, fill = FALSE, padding = 0 */

        box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);

        /* Llamad a la función para hacer cajas - homogeneous = FALSE, spacing = 0,
         * expand = FALSE, fill = FALSE, padding = 0 */
        box2 = make_box (FALSE, 0, TRUE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
        box2 = make_box (FALSE, 0, TRUE, TRUE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        
      /* creamos un separador. Mas tarde aprenderemos más cosas sobre ellos, pero son
       * bastante simples. */
        separator = gtk_hseparator_new ();
        
        
      /* empaquetamos el separador el la vbox. Los widgets serán apilados verticalmente. */
        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);
        

      /* creamos una nueva etiqueta y la mostramos */
        label = gtk_label_new ("gtk_hbox_new (TRUE, 0);");
        gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
        gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
        gtk_widget_show (label);
        
        /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
        box2 = make_box (TRUE, 0, TRUE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
        box2 = make_box (TRUE, 0, TRUE, TRUE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        /* un nuevo separador */
        separator = gtk_hseparator_new ();
        /* Los tres últimos argumentos son: expand, fill, padding. */
        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);
        
        break;

    case 2:

        /* Nueva etiqueta*/
        label = gtk_label_new ("gtk_hbox_new (FALSE, 10);");
        gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
        gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
        gtk_widget_show (label);
        
        /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
        box2 = make_box (FALSE, 10, TRUE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
        box2 = make_box (FALSE, 10, TRUE, TRUE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        separator = gtk_hseparator_new ();
        /* Los argumentos son: expand, fill, padding. */
        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);
        
        label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
        gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
        gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
        gtk_widget_show (label);
        
        /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
        box2 = make_box (FALSE, 0, TRUE, FALSE, 10);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        /* Los argumentos son: homogeneous, spacing, expand, fill, padding */
        box2 = make_box (FALSE, 0, TRUE, TRUE, 10);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        separator = gtk_hseparator_new ();
        /* Los argumentos son: expand, fill, padding. */
        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);
        break;
    
    case 3:

     /* Con esto demostramos como hay que usar gtk_box_pack_end () para conseguir que los
        widgets esten alineados a la izquierda. */
        box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
        
      /* la última etiqueta*/ 
        label = gtk_label_new ("end");
      /* la empaquetamos usando gtk_box_pack_end(), por lo que se sitúa en el lado derecho
       * de la hbox.*/

        gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0);
      /* mostrar la etiqueta */
        gtk_widget_show (label);
        

      /* empaquetamos box2 en box1*/
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        
      /* el separador para la parte de abajo. */
        separator = gtk_hseparator_new ();
        /* Así se determina el tamaño del separador a 400 pixels de largo por 5 de alto.
         * La hbox también tendrá 400 pixels de largo y la etiqueta "end" estará separada de
         * las demás etiquetas en la hbox. Si no establecemos estos parámetros todos los
         * widgets en la hbox serán empaquetados tan juntos como se pueda.*/
        
        gtk_widget_set_usize (separator, 400, 5);
        /* Empaquetamos el separador creado al principio de main() en la vbox (box1).*/
        /* pack the separator into the vbox (box1) created near the start 
         * of main() */
        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);    
    }
    
    /* Creamos otra hbox... recordar que podemos crear tantas como queramos.*/
    quitbox = gtk_hbox_new (FALSE, 0);
    
    /* El botón de salida. */
    button = gtk_button_new_with_label ("Quit");
    
    /* Establecemos la señal de destrucción de la ventana. Recuerde que emitirá la señal de 
     * "destroy" que a su vez será procesada por el controlador de señales, tal y como ya
     *  hemos visto.*/  
  
    gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
                               GTK_SIGNAL_FUNC (gtk_main_quit),
                               GTK_OBJECT (window));
    /* Empaquetamos el botón en la caja de salida (quitbox).
     * los tres últimos argumentos de gtk_box_pack_start son:expand, fill, padding. */

    gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0);
    
    /* empaquetamos la vbox (box1) que ya contiene todos los widgets en la ventana principal.*/ 
    gtk_container_add (GTK_CONTAINER (window), box1);
    
    /* mostramos todo aquello que faltaba por mostrar*/
    gtk_widget_show (button);
    gtk_widget_show (quitbox);
    
    gtk_widget_show (box1);
    /* Si mostramos la ventana lo último todo aparece de golpe. */
    gtk_widget_show (window);
  
    /* por supuesto tenemos una función main. */
    gtk_main ();

    /* El control se devuelve aquí cuando gtk_main_quit() es llamada, pero no cuando gtk_exit es 
     * usada.*/
    return 0;
}
/* final del ejemplo*/

4.4 Empaquetamiento usando tablas

Existe otra forma de empaquetar: usando tablas. Estas pueden llegar a ser extremadamente útiles.

Usando tablas creamos una cuadrícula donde podemos poner los widgets. Estos pueden ocupar tanto espacio como queramos.

La primera función que conviene estudiar es gtk_table_new:

GtkWidget *gtk_table_new( gint rows,
                          gint columns,
                          gint homogeneous );

Como es lógico el primer argumento es el número de filas y el segundo el de columnas.

El tercero establece el tamaño de las celdas de la tabla. Si es TRUE se fuerza a que el tamaño de las celdas sea igual al de la celda mayor. Con FALSE se establece el ancho de toda una columna igual al de la celda más ancha de esa columna, y la altura de una fila será la de la celda más alta de esa fila.

El número de filas y columnas varía entre 0 y n, donde n es el número especificado en la llamada a gtk_table_new. Así si se especifica columnas = 2 y filas = 2 la apariencia será parecida a:

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

Conviene destacar que el origen de coordenadas se sitúa en la esquina superior izquierda. Para situar un widget en una ventana se usa la siguiente función:

void gtk_table_attach( GtkTable  *table,
                       GtkWidget *child,
                       gint       left_attach,
                       gint       right_attach,
                       gint       top_attach,
                       gint       bottom_attach,
                       gint       xoptions,
                       gint       yoptions,
                       gint       xpadding,
                       gint       ypadding );

El primer argumento (table) es el nombre de la tabla y el segundo (child) el widget que quiere poner en la tabla.

Los argumentos left_attach, right_attach especifican donde se pone el widget y cuantas cajas se usan. Por ejemplo, supongamos que queremos poner un botón que sólo ocupe la esquina inferior izquierda en nuestra tabla 2x2. Los valores serán left_attach = 1, right_attach = 2, top_attach = 2, top_attach = 1, bottom_attach = 2.

Supongamos que queremos ocupar toda la fila de nuestra tabla 2x2, usaríamos left_attach = 0, right_attach = 2, top_attach = 0, bottom_attach = 1.

Las opciones xoptions e yoptions son usadas para especificar como queremos el empaquetamiento y podemos utilizar multiples opciones simultaneamente con OR.

Las opciones son:

El relleno es igual que con las cajas. Simplemente se crea una zona vacía alrededor del widget (el tamaño se especifica en pixels).

gtk_table_attach() tiene MUCHAS opciones. Asi que hay un atajo:

void gtk_table_attach_defaults( GtkTable  *table,
                                GtkWidget *widget,
                                gint       left_attach,
                                gint       right_attach,
                                gint       top_attach,
                                gint       bottom_attach );

Las opciones X e Y se ponen por defecto a GTK_FILL | GTK_EXPAND, y el relleno X e Y se pone a 0. El resto de los argumentos son identicos a la función anterior.

Existen otras funciones como gtk_table_set_row_spacing() y gtk_table_set_col_spacing(), que sirven para especificar el espaciado entre las columnas/filas en la columna/fila que queramos.

void gtk_table_set_row_spacing( GtkTable *table,
                                gint      row,
                                gint      spacing );

y

void gtk_table_set_col_spacing ( GtkTable *table,
                                 gint      column,
                                 gint      spacing );

Conviene destacar que el espaciado se sitúa a la derecha de la columna y debajo de la fila.

Tambien se puede forzar que el espaciado sea el mismo para las filas y/o las columnas:

void gtk_table_set_row_spacings( GtkTable *table,
                                 gint      spacing );

y

void gtk_table_set_col_spacings( GtkTable *table,
                                 gint      spacing );

Usando estas funciones las últimas fila y columna no estarán espaciadas.

4.5 Ejemplo de empaquetamiento mediante tablas.

Haremos una ventana con tres botones en una tabla 2x2. Los dos primeros botones ocuparán la fila de arriba, mientras que el tercero (de salida) ocupará toda la fila de abajo. El resultado es el siguiente:

Table Packing Example Image

Este es el código:

/* principio del ejemplo table table.c */

#include <gtk/gtk.h>
/* La respuesta, que además se imprime en stdout.*/
void callback (GtkWidget *widget, gpointer data)
{
    g_print ("Hello again - %s was pressed\n", (char *) data);
}

/* con esta otra respuesta terminamos el programa. */
void delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
{
    gtk_main_quit ();
}

int main (int argc, char *argv[])
{
    GtkWidget *window;
    GtkWidget *button;
    GtkWidget *table;

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

    gtk_window_set_title (GTK_WINDOW (window), "Table");

    gtk_signal_connect (GTK_OBJECT (window), "delete_event",
                        GTK_SIGNAL_FUNC (delete_event), NULL);

    gtk_container_border_width (GTK_CONTAINER (window), 20);

    table = gtk_table_new (2, 2, TRUE);

    gtk_container_add (GTK_CONTAINER (window), table);

    button = gtk_button_new_with_label ("button 1");

    gtk_signal_connect (GTK_OBJECT (button), "clicked",
              GTK_SIGNAL_FUNC (callback), (gpointer) "button 1");

    gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 1, 0, 1);

    gtk_widget_show (button);

    button = gtk_button_new_with_label ("button 2");

    gtk_signal_connect (GTK_OBJECT (button), "clicked",
              GTK_SIGNAL_FUNC (callback), (gpointer) "button 2");
    gtk_table_attach_defaults (GTK_TABLE(table), button, 1, 2, 0, 1);

    gtk_widget_show (button);

    button = gtk_button_new_with_label ("Quit");

    gtk_signal_connect (GTK_OBJECT (button), "clicked",
                        GTK_SIGNAL_FUNC (delete_event), NULL);
    gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 2, 1, 2);

    gtk_widget_show (button);

    gtk_widget_show (table);
    gtk_widget_show (window);

    gtk_main ();

    return 0;
}
/* final del ejemplo */


Página siguiente Página anterior Índice general