GTK+/Empacotando widgets

Capítulo 5: Empacotando Widgets

Quando se cria um aplicativo, você vai querer colocar mais que um widget em uma janela. Nosso primeiro exemplo helloworld utilizou apenas um widget tal que nós podemos simplesmente user uma chamada gtk_container_add() para "empacotar" o widget na janela. Mas quando você colocar mais que um widget na janela, como pode controlar onde o widget é posicionado?

Teoria do empacotamento de caixas

editar

Muitos empacotamentos são feitos criando caixas. Estes são recipientes invisíveis de widgets que nós podemos empacotar nossos widgets, no qual vem em duas formas, uma caixa horizontal e uma caixa vertical. Quando empacotar widgets em caixas horizontais, os objetos são inseridos horizontalmente da esquerda para a direita ou da direita para a esquerda, dependendo da chamada usada. Na caixa vertical, widgets são empacotados de cima para baixo ou vice versa. Você pode usar qualquer combinação de caixas dentro ou ao lado de outras caixas para criar o efeito desejado.

Para criar uma nova caixa horizontal, usamos uma chamada para gtk_hbox_new(), e para caixas verticais, gtk_vbox_new(). As funções gtk_box_pack_start() e gtk_box_pack_end() são usadas para colocar objetos dentro destes recipientes. A função gtk_box_start() iniciará no topo e seguirá para baixo em uma vbox, e empacota da esquerda para a direita em um hbox. Estas funções permitem justificar pela direita ou pela esquerda nossos widgets e podem ser misturados a fim de se obter o efeito desejado. Utilizaremos gtk_box_pack_start() em muitos dos nossos exemplos. Um objeto pode ser um outro recipiente ou um widget. De fato, muitos widgets são na verdade recipientes, inclusive o botão, mas costumamos utilizar apenas uma label no botão.

Através destas chamadas, GTK sabe onde você quer colocar seus widgets tal que eles podem executar uma re-escala automática e outras coisas. Existe também um número de opções para as quais seus widgets podem ser empacotados. Como você pode imaginar, este método nos dá uma boa flexibilidade quando colocamos e criamos widgets.

Detalhes das caixas

editar

Devido a essa flexibilidade as caixas de empacotamento do GTK, a princípio, podem causar confusão. Existem muitas opções e não fica logo evidente como elas se ajustam.

 


Cada linha contém uma caixa horizontal (hbox) com vários botões. A chamada para gtk_box_pack é abreviação para a chamada de empacotamento de cada um dos botões na hbox. Cada botão é empacotado na hbox da mesma maneira (os mesmos argumentos da função gtk_box_pack_start()).

Esta é a declaração da função gtk_box_pack_start().

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

O primeiro argumento é a caixa na qual você esta empacotando o objeto, e o segundo é o objeto. Os objetos, por hora, serão botões, então vamos empacotar os botões nas caixas.

O argumento de expansão para gtk_box_pack_start() e gtk_box_pack_end() controla se os widgets se expandem para preencher todo o espaço extra na caixa (TRUE); ou a caixa se encolhe para apenas ajustar aos widgets (FALSE). Declarar expand como FALSE permite a voce justificação à direita ou à esquerda dos seus widgets. De outra maneira eles vão expandir para se ajustar na caixa, o mesmo efeito acontece escolhendo apenas um, gtk_box_pack_start() ou gtk_box_pack_end().

O argumento de preenchimento para as funções gtk_box_pack controla se o espaço extra nos objetos -botões- (TRUE), ou como espaço extra entre cada objeto. Ele apenas tem efeito se o argumento expand esta TRUE.

Ao se criar uma nova caixa, a função parece assim:

GtkWidget *gtk_hbox_new ( gboolean homogeneous,
                         gint     spacing );

O argumento homogeneous gtk_hbox_new() (o mesmo para gtk_vbox_new()) controla se os objetos na caixa terão o mesmo tamanho (isto é, a mesma largura numa hbox ou a mesma altura numa vbox). Se ele é selecionado, as rotinas do gtk_box_pack() funcionam como se o argumento expand estivesse sempre ativo (TRUE).

Qual a diferença entre spacing (quando a caixa é criada) e padding (quando os elementos são empacotados)? Spacing controla o espaço entre os objetos e padding o espaço a cada lado do objeto. A seguinte figura torna isso claro:


 

Aqui está o código utilizado para criar as duas imagens. Comentei completamente, assim espero que você não tenha problemas ao examiná-lo. Voce pode compilar e executar.

/* example-start packbox packbox.c */

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

static gboolean delete_event( GtkWidget *widget,
                             GdkEvent  *event,
                             gpointer   data )
{
   gtk_main_quit ();
   return FALSE;
}

  /* Cria uma nova hbox preenchida com button-labels(rótulos de botões). Os argumentos 
  * das variáveis que estamos interessados são passados nessa função. 
  * Nós não mostramos a caixa, mas mostramos tudo dentro dela. */
  static GtkWidget *make_box( gboolean homogeneous,
                           gint     spacing,
                           gboolean expand,
                           gboolean fill,
                           guint    padding ) 
{
   GtkWidget *box;
   GtkWidget *button;
   char padstr[80];
   
   /* Cria uma nova hbox com ajustes apropriados de spacing
    * e homogeneous*/
   box = gtk_hbox_new (homogeneous, spacing);
   
   /* Cria uma série de botões com ajustes apropriados */
   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);
   
   /* Cria um botão com rótulo que depende do 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);
   
   /* Igual a criação para o botão "expand" acima,
    * mas utiliza a forma abreviada. */
   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;
   
   /* Nosso init, não se esqueça! :) */
   gtk_init (&argc, &argv);
   
   if (argc != 2) {
       fprintf (stderr, "usage: packbox num, where num is 1, 2, or 3.\n");
        /* Isso apenas limpa e sai com exit status 1. */
        exit (1);
   }
   
   which = atoi (argv[1]);

   /* Cria nossa janela */
   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

   /* Você deve se lembrar sempre de conectar o sinal delete_event
    * à janela principal. Isso é muito importante para um comportamento intuitivo
    * apropriado */
   g_signal_connect (G_OBJECT (window), "delete_event",
                     G_CALLBACK (delete_event), NULL);
   gtk_container_set_border_width (GTK_CONTAINER (window), 10);
   
   /* Criamos uma caixa vertical (vbox) para empacotar caixas horizontais nela.
    * Isso nos permite empilhar as caixas horizontais preenchidas com botões umas sobre as
    * outras no topo da vbox. */
   box1 = gtk_vbox_new (FALSE, 0);
   
   /* Qual exemplo mostrar. Corresponde às figuras acima. */
   switch (which) {
   case 1:
   /* Cria um novo rótulo. */
    label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
 
   /* Alinha o rótulo à esquerda.Discutiremos essa função e outras na seção 
     * sobre atributos de Widget. */
      gtk_misc_set_alignment (GTK_MISC (label), 0, 0);

   /* Empacota o rótulo na caixa vertical (vbox box1). Lembre-se que 
     * widgets adicionados a vbox serão empacotados um sobre o outro. */
       gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
 
      /* Mostra o rótulo*/
      gtk_widget_show (label);
 
    /* Chama nossa função que faz caixas - homogeneous = FALSE, spacing = 0,
      * 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);

     /* Chama nossa função que faz caixas - homogeneous = FALSE, spacing = 0,
       * expand = TRUE, 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);

      /* Argumentos são: 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);

       /* Cria um separador, aprenderemos mais sobre isso mais tarde, 
         * mas eles são muito simples. */
       separator = gtk_hseparator_new ();
  
       /* Empacota o separador na vbox. Lembre-se, cada um desse 
         * widgets é empacotado em uma vbox, asssim eles são posicionados 
        * verticalmente. */
       gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
       gtk_widget_show (separator);
    
       /* Cria um outro novo rótulo e o mostra. */
       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);

       /* Argumentos são: 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);
 
       /* Argumentos são: 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);
       
       /* Outro novo separador. */
       separator = gtk_hseparator_new ();
      /* Os últimos 3 argumentos para gtk_box_pack_start são:
      * expand, fill, padding. */
       gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
       gtk_widget_show (separator);
       
       break;

case 2:
 
     /* Cria um novo rótulo, lembre-se box1 é uma vbox criada 
     * próxima ao início de main() */
     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);

     /* Argumentos sãos : 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);

    /* Argumentos são: 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 ();
    /* Os últimos 3 argumentos de gtk_box_pack_start são:
     * 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);

     /* Argumentos são: 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);

     /* Argumentos são: 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 ();
     /* Os últimos 3 argumentos de gtk_box_pack_start são: expand, fill, padding. */
     gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
     gtk_widget_show (separator);
     break;
    
case 3:

     /* Este mostra a habilidade do uso de gtk_box_pack_end() para
      * ajustar widgets à direita. Primeiro nós criamos uma nova caixa como antes. */
      box2 = make_box (FALSE, 0, FALSE, FALSE, 0);

     /* Cria o rótulo que colocaremos no final. */
       label = gtk_label_new ("end");
     /* Empacota ele usando gtk_box_pack_end(), assim ele é colocado no lado direito
        * da hbox criada no chamado make_box(). */
      gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0);
      /* Mostra o rótulo. */
      gtk_widget_show (label);
 
      /* Empacota a box2 na box1 (A vbox, lembra-se ? :) */
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
 
      /* O separador para o botão. */
        separator = gtk_hseparator_new ();
      /* Isso explicitamente ajusta o separador para 400 pixels de largura por 5 pixels
        * de altura. Assim a hbox que criamos também terá 400 pixels de largura,
        * e o rótulo "end" será separado de outros rótulos na
        * hbox. De outro modo todos os widgets na hbox seriam empacotados o mais próximos
        * possível. */
        gtk_widget_set_size_request (separator, 400, 5);
      /* Empacota o separador na vbox (box1) criada próximo ao início de
       * of main() */
       gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
       gtk_widget_show (separator);    
   }
   
   /* Cria uma outra nova hbox. Lembre-se que nós podemos usar quantas forem necessárias! */
   quitbox = gtk_hbox_new (FALSE, 0);
   
   /* Nosso botão Fechar "quit button". */
   button = gtk_button_new_with_label ("Quit");
   
   /* Arruma o sinal para terminar o programa quando o botão é clicado */
   g_signal_connect_swapped (G_OBJECT (button), "clicked",
                             G_CALLBACK (gtk_main_quit),
                             G_OBJECT (window));
   /* Empacota o botão na quitbox.
    * Os últimos 3 argumentos de gtk_box_pack_start são:
    * expand, fill, padding. */
   gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0);
   /* Empacota a quitbox na vbox (box1) */
   gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0);
   
   /* Empacota a vbox (box1), que agora contém todos os nossos widgets, na
    * janela principal. */
   gtk_container_add (GTK_CONTAINER (window), box1);
   
   /* E deixa mostrar tudo */
   gtk_widget_show (button);
   gtk_widget_show (quitbox);
   
   gtk_widget_show (box1);
   /* Mostra a janela por último, assim tudo aparece de uma vez, ao mesmo tempo. */
   gtk_widget_show (window);
   
   /* E, claro, nossa função principal. */
   gtk_main ();

   /* O controle retorna aqui quando gtk_main_quit() é invocada, mas não quando 
    * exit() é chamada. */
   
   return 0;
}

Ver também

editar