Previous Next Table of Contents

4. Placement des widgets

Lorsqu'on crée une application, on veut mettre plus qu'un simple bouton dans une fenêtre. Notre premier exemple « Bonjour le monde » n'utilisait qu'un seul widget et on pouvait donc simplement faire un appel à gtk_container_add pour « placer » le widget dans la fenêtre. Mais si l'on désire en mettre plus, comment peut-on contrôler l'endroit où le widget sera positionné ? C'est ici que le placement entre en jeu.

4.1 Théorie des boîtes de placement

La majeure partie du placement est faites en créant des boîtes comme dans l'exemple ci-dessus. Ce sont des widgets containers invisibles où l'on peut placer nos widgets. Elles existent sous deux formes : boîtes horizontales et boîtes verticales. Lorsque l'on place des widgets dans une boîte horizontale, les objets sont insérés horizontalement de gauche à droite ou de droite à gauche selon l'appel utilisé. Dans une boîte verticale, les widgets sont placés de haut en bas ou vice versa. On peut utiliser n'importe quelle combinaison de boîtes à l'intérieur ou à côté d'autres boîtes pour créer l'effet désiré.

Pour créer une nouvelle boîte horizontale, on appelle gtk_hbox_new(), et pour les boîtes verticales, gtk_vbox_new(). Les fonctions gtk_box_pack_start() et gtk_box_pack_end() servent à placer les objets à l'intérieur de ces containers. La fonction gtk_box_pack_start() placera de haut en bas dans une boîte verticale et de gauche à droite dans une boîte horizontale. gtk_box_pack_end() fera le contraire en plaçant de bas en haut et de droite à gauche. En utilisant ces fonctions, on peut aligner à droite ou à gauche nos widgets et même les mélanger de n'importe quelle façon pour obtenir l'effet désiré. Dans la plupart de nos exemples, on utilisera gtk_box_pack_start(). Un objet peut être un autre container ou un widget. En fait, de nombreux widgets (dont les boutons) sont eux-mêmes des containers, mais on utilise généralement seulement un label dans un bouton.

En utilisant ces appels, GTK sait où vous voulez placer vos widgets et il peut donc les dimensionner automatiquement et faire d'autres choses bien pratiques. Il existe aussi plusieurs options permettant de préciser comment les widgets doivent être placés. Comme vous pouvez l'imaginer, cette méthode nous donne pas mal de liberté pour placer et créer les widgets.

4.2 Détails sur les boîtes

À cause de cette liberté, le placement des boîtes avec GTK peut paraître déroutant au premier abord. Il existe beaucoup d'options et il n'est pas tout de suite évident de comprendre comment elles s'accordent toutes ensemble. En fait, il y a 5 styles de base différents.

Box Packing Example Image

Chaque ligne contient une boîte horizontale (hbox) contenant plusieurs boutons. L'appel à gtk_box_pack indique la façon dont sont placés tous les boutons dans la hbox. Chaque bouton est placé dans la hbox de la même façon (mêmes paramètres que la fonction gtk_box_pack_start()).

Voici la déclaration de la fonction gtk_box_pack_start.

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

Le premier paramètre est la boîte dans laquelle on place l'objet, le second est cet objet. Tous les objets sont tous des boutons jusqu'à maintenant, on place donc des boutons dans des boîtes.

Le paramètre expand de gtk_box_pack_start() ou gtk_box_pack_end() contrôle la façon dont le widget est placé dans la boîte. S'il vaut TRUE, les widgets sont disposés dans la boîte de façon à en occuper tout l'espace. S'il vaut FALSE, la boîte est rétrécie pour correspondre à la taille du widget. Mettre expand à FALSE vous permettra d'aligner à droite et à gauche vos widgets. Sinon, ils s'élargiront pour occuper toute la boîte. Le même effet pourrait être obtenu en utilisant uniquement une des deux fonctions gtk_box_pack_start ou pack_end.

Le paramètre fill des fonctions gtk_box_pack contrôle si de l'espace supplémentaire doit être alloué aux objets eux-mêmes (TRUE), ou si on doit rajouter de l'espace (padding) dans la boîte autour des objets (FALSE). Il n'a de sens que si le paramètre expand vaut TRUE.

Lorsque l'on crée une nouvelle boîte, on utilise une fonction comme :

GtkWidget * gtk_hbox_new (gint homogeneous,
                          gint spacing);

Le paramètre homogeneous de gtk_hbox_new (et c'est la même chose pour gtk_vbox_new) vérifie que chaque objet de la boîte ait la même taille (i.e. la même largeur dans une hbox, la même hauteur dans une vbox). S'il vaut TRUE, le paramètre expand des fonctions gtk_box_pack sera toujours mis à TRUE.

Quelle est alors la différence entre les paramètres spacing (configuré lorsque la boîte est créée) et padding (configuré lorque les éléments sont placés) ? spacing ajoute de l'espace entre les objets, et padding en ajoute de chaque côté d'un objet. La figure suivante devrait éclairer tout cela :

Box Packing Example Image

Voici le code utilisé pour créer les images ci-dessus. J'y ai mis beaucoup de commentaires en espérant que vous n'aurez pas de problème pour le relire. Compilez-le et jouez avec les différents paramètres.

4.3 Programme de démonstration des placements

#include "gtk/gtk.h"

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

/* Construction d'une nouvelle hbox remplie de boutons. Les paramètres qui 
 * nous intéressent sont passés à cette fonction.
 * On n'affiche pas la boîte, mais tout ce qu'elle contient. */

GtkWidget *make_box (gint homogeneous, gint spacing,
                     gint expand, gint fill, gint padding) 
{
    GtkWidget *box;
    GtkWidget *button;
    char padstr[80];
    
    /* Création d'une hbox avec les paramètres homogeneous et spacing
     * voulus. */

    box = gtk_hbox_new (homogeneous, spacing);
    
    /* Création d'une série de boutons configurés de façon appropriée */

    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);
    
    /* Création d'un bouton portant un label dépendant de la valeur
     * du paramètre 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);
    
    /* Même chose que ci-dessus mais sous forme abrégée. */

    button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,");
    gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
    gtk_widget_show (button);
    
    /* Récupération du paramètre padding sous forme de chaîne. */

    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;
    
    /* Initialisation, à ne jamais oublier ! :) */

    gtk_init (&argc, &argv);
   
    if (argc != 2) {
        fprintf (stderr, "usage : %s num, où num vaut 1, 2, ou 3.\n", *argv);

        /* Nettoyage dans GTK et sortie avec un code d'erreur de 1 */
        gtk_exit (1);
    }
    
    which = atoi (argv[1]);

    /* Création de notre fenêtre. */

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

    /* Il ne faut jamais oublier de connecter le signal "destroy" à la
     * fenêtre principale. C'est très important pour disposer d'un 
     * comportement intuitif adéquat. */

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

    gtk_container_border_width (GTK_CONTAINER (window), 10);
    

    /* Création d'une boîte verticale (vbox) pour y placer les boîtes
     * horizontales.
     * Ceci permet de placer les boîtes horizontales contenant les boutons
     * les unes au dessus des autres dans cette vbox. */

    box1 = gtk_vbox_new (FALSE, 0);
    
    /* L'exemple à afficher. Ils correspondent aux images ci-dessus. */

    switch (which) {
    case 1:
        /* Création d'un label. */

        label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
        
        /* Alignement du label à gauche.  On précisera cette fonction ainsi
         * que les autres dans la section sur les attributs des widgets. */

        gtk_misc_set_alignment (GTK_MISC (label), 0, 0);

        /* Placement du label dans la boîte verticale (vbox box1). Il ne
         * faut pas oublier que les widgets qui s'ajoutent à une vbox sont
         * placés les uns au dessus des autres. */

        gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
        
        /* Affichage du label */

        gtk_widget_show (label);
        
        /* On appelle notre fonction de construction de boîte :
         * 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);

        /* On appelle notre fonction de construction de boîte :
         * 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);
        
        /* Paramètres : homogeneous = FALSE, spacing = 0, 
         * expand = TRUE, fill = TRUE, padding = 0 */

        box2 = make_box (FALSE, 0, TRUE, TRUE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        /* Création d'un séparateur, on verra cela plus tard, mais ils sont
         * simples à utiliser. */

        separator = gtk_hseparator_new ();
        
        /* Placement du séparateur dans la vbox. Ne pas oublier que tous les
         * widgets sont placés dans une vbox et qu'il seront placés 
         * verticalement. */

        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);
        
        /* Création d'un nouveau label et affichage de celui-ci. */

        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);
        
        /* Paramètres : homogeneous = TRUE, spacing = 0, 
         * expand = TRUE, fill = FALSE, padding = 0 */

        box2 = make_box (TRUE, 0, TRUE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        /* Paramètres : homogeneous = TRUE, spacing = 0, 
         * expand = TRUE, fill = TRUE, padding = 0 */

        box2 = make_box (TRUE, 0, TRUE, TRUE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        /* Un autre séparateur */

        separator = gtk_hseparator_new ();

        /* Les 3 derniers paramètres de gtk_box_pack_start sont : 
         * expand = FALSE, fill = TRUE, padding = 5. */

        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);
        
        break;

    case 2:

        /* Création d'un label, box1 est une vbox identique à 
         * celle créée au début 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);
        
        /* Paramètres : homogeneous = FALSE, spacing = 10, 
         * expand = TRUE, fill = FALSE, padding = 0 */

        box2 = make_box (FALSE, 10, TRUE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        /* Paramètres : homogeneous = FALSE, spacing = 10, 
         * expand = TRUE, fill = TRUE, padding = 0 */

        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 ();

        /* Les 3 derniers paramètres de gtk_box_pack_start sont : 
         * expand = FALSE, fill = TRUE, padding = 5. */

        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);
        
        /* Paramètres : homogeneous = FALSE, spacing = 0, 
         * expand = TRUE, fill = FALSE, padding = 10 */

        box2 = make_box (FALSE, 0, TRUE, FALSE, 10);
        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        /* Paramètres : homogeneous = FALSE, spacing = 0, 
         * expand = TRUE, fill = TRUE, padding = 10 */

        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 ();

        /* Les 3 derniers paramètres de gtk_box_pack_start sont : 
         * expand = FALSE, fill = TRUE, padding = 5. */
        
        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);
        break;
    
    case 3:

        /* Ceci est une démonstration de la possibilité d'utiliser 
         * gtk_box_pack_end() pour aligner les widgets à droite.
         * On crée d'abord une nouvelle boîte comme d'habitude. */

        box2 = make_box (FALSE, 0, FALSE, FALSE, 0);

        /* On crée le label qui sera mis à la fin. */

        label = gtk_label_new ("end");

        /* On le place en utilisant gtk_box_pack_end(), il est ainsi
         * mis à droite de la hbox créée par l'appel à make_box(). */

        gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0);

        /* Affichage du label. */

        gtk_widget_show (label);
        
        /* Placement de box2 dans box1 (la vbox, vous vous rappelez ? :) */

        gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
        gtk_widget_show (box2);
        
        /* Séparateur pour le bas. */

        separator = gtk_hseparator_new ();

        /* Configuration du séparateur en 400x5 pixels. 
         * La hbox que l'on a créée aura donc 400 pixels de large, 
         * et le label "end" sera séparé des autres de la hbox.
         * Sinon, tous les widgets de la hbox seraient placés les plus
         * près possible les uns des autres. */

        gtk_widget_set_usize (separator, 400, 5);

        /* Placement du séparateur dans la vbox (box1) 
         * créée au debut de main(). */

        gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
        gtk_widget_show (separator);    
    }
    
    /* Création d'une nouvelle hbox.. vous pouvez en utiliser autant que
     * que vous en avez besoin ! */

    quitbox = gtk_hbox_new (FALSE, 0);
    
    /* Notre bouton pour quitter. */

    button = gtk_button_new_with_label ("Quit");
    
    /* Configuration du signal pour détruire la fenêtre. Ceci enverra le
     * signal "destroy" à la fenêtre. Ce signal sera à son tour capturé
     * par notre gestionnaire de signal défini plus haut. */

    gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
                               GTK_SIGNAL_FUNC (gtk_widget_destroy),
                               GTK_OBJECT (window));

    /* Placement du bouton dans la « quitbox ».
     * Les 3 derniers paramètres de gtk_box_pack_start sont : 
     * expand = TRUE, fill = FALSE, padding = 0. */

    gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0);

    /* Placement de la quitbox dans la vbox (box1) */

    gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0);
    
    /* Placement de la vbox (box1), qui contient maintenant tous nos
     * widgets, dans la fenêtre principale. */

    gtk_container_add (GTK_CONTAINER (window), box1);
    
    /* Affichage */

    gtk_widget_show (button);
    gtk_widget_show (quitbox);
    
    gtk_widget_show (box1);

    /* Affichage de la fenêtre en dernier */

    gtk_widget_show (window);
    
    /* Ne pas oublier notre fonction principale. */

    gtk_main ();

    /* Le contrôle revient ici lorsque gtk_main_quit() est appelée,
     * jusqu'à ce que  gtk_exit() soitutilisée. */
    
    return 0;
}

4.4 Placement avec les tables

Étudions une autre méthode de placement : les tables. Elles peuvent s'avérer très utiles dans certaines situations.

En utilisant des tables, on crée une grille dans laquelle on peut placer les widgets. Ceux-ci peuvent occuper tous les endroits que l'on désire.

La première chose à faire est, bien sûr, d'étudier la fonction gtk_table_new :

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

Le premier paramètre est le nombre de lignes de la table et le deuxième, le nombre de colonnes.

Le paramètre homogeneous s'occupe de la façon dont les cases de la table seront dimensionnées. Si homogeneous vaut TRUE, les cases prennent la taille du plus grand widget de la table. S'il vaut FALSE, la taille des cases dépend du widget le plus haut de la ligne et du plus large de cette colonne.

Le nombre de lignes et colonnes va de 0 à n, où n est le nombre spécifié dans l'appel à gtk_table_new. Ainsi, avec rows = 2 et columns = 2, la table ressemblera à ceci :

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

On notera que le système de coordonnées part du coin en haut à gauche. Pour placer un widget dans une case, ou utilise la fonction suivante :

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);

Où le premier paramètre (table) est la table que l'on a créée et le second (child) est le widget que l'on veut placer dans la table.

Les paramètres left_attach et right_attach spécifient l'emplacement du widget et le nombre de cases à utiliser. Par exemple, si on veut placer un bouton dans le coin inférieur droit de la table décrite plus haut et que l'on désire ne remplir QUE cette case, left_attach vaudra 1, right_attach vaudra 2; top_attach vaudra 1 et bottom_attach vaudra 2.

Si on veut un widget occupant toute la ligne supérieure de notre table, on utilisera les valeurs 0, 2, 0, 1.

Les paramètres xoptions et yoptions servent à préciser les options de placement et peuvent être combinées par un OU logique pour permettre des options multiples.

Ces options sont :

Le paramêtres de padding jouent le même rôle que pour les boîtes, il créent une zone libre, spécifiée en pixels, autour du widget.

gtk_table_attach() a BEAUCOUP d'options. Voici donc une fonction-raccourci :

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

xoptions et options valent par défaut GTK_FILL | GTK_EXPAND, et xpadding et ypadding valent 0. Les autres paramètres sont les mêmes que ceux de la fonction précédente.

Il existe aussi les fonctions gtk_table_set_row_spacing() et gtk_table_set_col_spacing(). Elles permettent de placer des espaces après une ligne ou une colonne.

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

Pour les colonnes, l'espace est ajouté à droite de la colonne et pour les lignes, il est ajouté en dessous.

On peut aussi configurer un espacement pour toutes les lignes et/ou colonnes avec :

void gtk_table_set_row_spacings (GtkTable *table,
                                 gint      spacing);

Et,

void gtk_table_set_col_spacings (GtkTable  *table,
                                 gint       spacing);

Avec ces appels, la dernière ligne et la dernière colonne n'ont pas d'espace supplémentaire.

4.5 Exemple de placement avec table

Pour le moment, étudiez l'exemple sur les tables (testgtk.c) distribué avec les sources de GTK.


Previous Next Table of Contents