Aller au menu - Aller au contenu

Icône Positionner ses widgets avec les layouts

Mise à jour : 27/05/2011
75 639 visites depuis 7 jours, dont 792 sur ce chapitre classé 5/786
Comme vous le savez, une fenêtre peut contenir toutes sortes de widgets : des boutons, des champs de texte, des cases à cocher...

Placer ces widgets sur la fenêtre est une science à part entière. Je veux dire par là qu'il faut vraiment y aller avec méthode, si on ne veut pas que la fenêtre ressemble rapidement à un champ de bataille :p

Comment bien placer les widgets sur la fenêtre ?
Comment gérer les redimensionnements de la fenêtre ?
Comment s'adapter automatiquement à toutes les résolutions d'écran ?

On distingue 2 techniques différentes pour positionner des widgets :

  • Le positionnement absolu : c'est celui que nous avons vu jusqu'ici, avec l'appel à la méthode setGeometry (ou move)... Ce positionnement est très précis, car on place les widgets au pixel près, mais cela comporte un certain nombre de défauts comme nous allons le voir.
  • Le positionnement relatif : c'est le plus flexible et c'est celui que je vous recommande d'utiliser autant que possible. Nous allons l'étudier dans ce chapitre.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Le positionnement absolu et ses défauts

Nous allons commencer par voir le code Qt de base que nous allons utiliser dans ce chapitre, puis nous ferons quelques rappels sur le positionnement absolu que vous avez déjà utilisé sans savoir exactement ce que c'était ;)


Le code Qt de base



Dans les chapitres précédents, nous avions créé un projet Qt constitué de 3 fichiers :

  • main.cpp : contenait le main qui se chargeait juste d'ouvrir la fenêtre principale.
  • MaFenetre.h : contenait l'en-tête de notre classe MaFenetre qui héritait de QWidget.
  • MaFenetre.cpp : contenait l'implémentation des méthodes de MaFenetre, notamment du constructeur.


C'est l'architecture que l'on utilisera dans la plupart de nos projets Qt.

Toutefois, pour ce chapitre nous n'avons pas besoin d'une architecture aussi complexe, et nous allons faire comme dans les tout premiers chapitres Qt : nous allons juste utiliser un main (1 seul fichier : main.cpp).

Voici le code de votre projet, sur lequel nous allons commencer :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <QApplication>
#include <QPushButton>


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget fenetre;

    QPushButton bouton("Bonjour", &fenetre);
    bouton.move(70, 60);

    fenetre.show();

    return app.exec();
}


C'est très simple : nous créons une fenêtre, et nous affichons un bouton que nous plaçons aux coordonnées (70, 60) sur la fenêtre.

Le résultat est le suivant :

Notre fenêtre simple



Les défauts du positionnement absolu



Dans le code précédent, nous avons positionné notre bouton de manière absolue en faisant bouton.move(70, 60);
Le bouton a été très précisément placé 70 pixels sur la droite et 60 pixels plus bas.

Le problème... c'est que ce n'est pas flexible du tout. Imaginez que l'utilisateur s'amuse à redimensionner la fenêtre :

Un bouton coupé en deux
C'est moche, non ?


Le bouton ne bouge pas de place. Du coup, si on réduit la taille de la fenêtre, il sera coupé en deux, et pourra même disparaître si on réduit trop la taille.

Dans ce cas, pourquoi ne pas empêcher l'utilisateur de redimensionner la fenêtre ? On avait fait ça grâce à setFixedSize dans les chapitres précédents...


Oui, vous pouvez faire cela. C'est d'ailleurs ce que font le plus souvent les développeurs de logiciels qui positionnent leurs widgets en absolu. Cependant, l'utilisateur apprécie aussi de pouvoir redimensionner sa fenêtre. Ce n'est qu'une demi-solution.

D'ailleurs, il y a un autre problème que setFixedSize ne peut pas régler : le cas des résolutions d'écran plus petites que la vôtre. Imaginez que vous placiez un bouton 1200 pixels sur la droite parce que vous avez une grande résolution (1600 x 1200), et que l'utilisateur soit dans une résolution plus petite que vous (1024 x 768). Il ne pourra jamais voir le bouton, parce qu'il ne pourra jamais agrandir autant sa fenêtre !


Alors quoi ? Le positionnement absolu c'est mal ? Où veux-tu en venir ?
Et surtout, comment peut-on faire autrement ?


Non, le positionnement absolu ce n'est pas "mal". Il sert parfois quand on a vraiment besoin de positionner au pixel près. Vous pouvez l'utiliser dans certains de vos projets, mais autant que possible, préférez l'autre méthode : le positionnement relatif.

Le positionnement relatif, cela consiste à expliquer comment les widgets sont agencés les uns par rapport aux autres, plutôt que d'utiliser une position en pixels. Par exemple, on peut dire "Le bouton 1 est en-dessous du bouton 2, qui est à gauche du bouton 3".

Le positionnement relatif est géré par ce qu'on appelle les layouts avec Qt. Ce sont des conteneurs de widgets.
C'est justement l'objet principal de ce chapitre :)

L'architecture des classes de layout

Pour positionner intelligemment nos widgets, nous allons utiliser des classes de Qt gérant les layouts.
Il existe par exemple des classes gérant le positionnement horizontal et vertical des widgets (ce que nous allons étudier en premier), ou encore le positionnement sous forme de grille.

Pour que vous y voyiez plus clair, je vous propose de regarder ce schéma de mon cru :

Layouts avec Qt


Ce sont les classes gérant les layouts de Qt.
Toutes les classes héritent de la classe de base QLayout.

On compte donc en gros les classes :

  • QBoxLayout
  • QHBoxLayout
  • QVBoxLayout
  • QGridLayout
  • QFormLayout
  • QStackedLayout


Nous allons étudier chacune de ces classes dans ce chapitre, à l'exception de QStackedLayout (gestion des widgets sur plusieurs pages) qui est un peu trop complexe pour qu'on puisse travailler dessus ici. On utilisera plutôt des widgets qui le réutilisent, comme QWizard qui permet de créer des assistants.

Euh... Mais pourquoi tu as écrit QLayout en italique, et pourquoi tu as grisé la classe ? :euh:


QLayout est une classe abstraite. Souvenez-vous du chapitre sur le polymorphisme, nous y avions vu qu'il est possible de créer des classes avec des méthodes virtuelles pures. Ces classes sont dites abstraites parce qu'on ne peut pas instancier d'objet de ce type.

L'utilisation de classes abstraites par Qt pour les layouts est un exemple typique. Tous les layouts ont des propriétés communes et des méthodes qui effectuent la même action mais de manière différente. Afin de représenter toutes les actions possibles dans une seule interface, les développeurs ont choisi d'utiliser une classe abstraite. Voilà donc un exemple concret pour illustrer ce point de théorie un peu difficile. ;)

Les layouts horizontaux et verticaux

Attaquons sans plus tarder l'étude de nos premiers layouts (les plus simples), vous allez mieux comprendre à quoi tout cela sert ;)

Nous allons travailler sur 2 classes :



QHBoxLayout et QVBoxLayout héritent de QBoxLayout. Ce sont des classes très similaires (la doc Qt parle de "convenience classes", des classes qui sont là pour vous aider à aller plus vite mais qui sont en fait quasiment identiques à QBoxLayout).
Nous n'allons pas utiliser QBoxLayout, mais juste ses classes filles QHBoxLayout et QVBoxLayout (ça revient au même).


Le layout horizontal



L'utilisation d'un layout se fait en 3 temps :

  1. On crée les widgets
  2. On crée le layout et on place les widgets dedans
  3. On dit à la fenêtre d'utiliser le layout qu'on a créé


1/ Créer les widgets



Pour les besoins de ce tutoriel, nous allons créer plusieurs boutons de type QPushButton :

Code : C++
1
2
3
QPushButton *bouton1 = new QPushButton("Bonjour");
QPushButton *bouton2 = new QPushButton("les");
QPushButton *bouton3 = new QPushButton("Zéros");


Vous remarquerez que j'utilise des pointeurs. En effet, j'aurais très bien pu faire sans pointeurs comme ceci :

Code : C++
1
2
3
QPushButton bouton1("Bonjour");
QPushButton bouton2("les");
QPushButton bouton3("Zéros");


... cette méthode a l'air plus simple, mais vous verrez que c'est plus pratique de travailler directement avec des pointeurs par la suite ;)
La différence entre ces 2 codes, c'est que bouton1 est un pointeur dans le premier code, tandis que c'est un objet dans le second code.

On va donc utiliser la première méthode avec les pointeurs.

Bon, on a 3 boutons, c'est bien. Mais les plus perspicaces d'entre vous auront remarqué qu'on n'a pas indiqué quelle était la fenêtre parente, comme on aurait fait avant :

Code : C++
1
QPushButton *bouton1 = new QPushButton("Bonjour", &fenetre);


On n'a pas fait comme ça, et c'est fait exprès justement. Nous n'allons pas placer les boutons dans la fenêtre directement, mais dans un conteneur : le layout.


2/ Créer le layout et placer les widgets dedans



Créons justement ce layout, un layout horizontal :

Code : C++
1
QHBoxLayout *layout = new QHBoxLayout;


Le constructeur de cette classe est simple, on n'a pas besoin d'indiquer de paramètre.

Maintenant que notre layout est créé, rajoutons nos widgets à l'intérieur :

Code : C++
1
2
3
layout->addWidget(bouton1);
layout->addWidget(bouton2);
layout->addWidget(bouton3);


La méthode addWidget du layout attend que vous lui donniez en paramètre un pointeur vers le widget à ajouter au conteneur. Voilà pourquoi je vous ai fait utiliser des pointeurs (sinon il aurait fallu écrire layout->addWidget(&bouton1); à chaque fois).


3/ Indiquer à la fenêtre d'utiliser le layout



Maintenant, dernière chose : il faut placer le layout dans la fenêtre. Il faut dire à la fenêtre : "tu vas utiliser ce layout, qui contient mes widgets".

Code : C++
1
fenetre.setLayout(layout);


La méthode setLayout de la fenêtre attend un pointeur vers le layout à utiliser.
Et voilà, notre fenêtre contient maintenant notre layout, qui contient les widgets. Le layout se chargera d'organiser les widgets horizontalement tout seul.


Résumé du code



Voici le code complet de notre fichier main.cpp :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <QApplication>
#include <QPushButton>
#include <QHBoxLayout>


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget fenetre;

    QPushButton *bouton1 = new QPushButton("Bonjour");
    QPushButton *bouton2 = new QPushButton("les");
    QPushButton *bouton3 = new QPushButton("Zéros");

    QHBoxLayout *layout = new QHBoxLayout;
    layout->addWidget(bouton1);
    layout->addWidget(bouton2);
    layout->addWidget(bouton3);
    
    fenetre.setLayout(layout);
    
    fenetre.show();

    return app.exec();
}


J'ai surligné les principales nouveautés.
En particulier, comme d'hab' lorsque vous utilisez une nouvelle classe Qt, pensez à l'inclure au début de votre code : #include <QHBoxLayout>


Résultat



Voilà à quoi ressemble la fenêtre maintenant que l'on utilise un layout horizontal :

Layout horizontal


Les boutons sont automatiquement disposés de manière horizontale ! :)

L'intérêt principal du layout, c'est son comportement face aux redimensionnements de la fenêtre.
Essayons de l'élargir :

Layout horizontal agrandi


Les boutons continuent de prendre l'espace en largeur.

On peut aussi l'agrandir en hauteur :

Layout horizontal agrandi


On remarque que les widgets restent centrés verticalement.
Vous pouvez aussi essayer de réduire la taille de la fenêtre. On vous interdira de la réduire si les boutons ne peuvent plus être affichés, ce qui vous garantit que les boutons ne risquent plus de disparaître comme avant ! :D


Schéma des conteneurs



En résumé, la fenêtre contient le layout qui contient les widgets. Le layout se charge d'organiser les widgets.
Schématiquement, ça se passe donc comme ça :

Schéma des layouts
Le layout est invisible à l'affichage


On vient de voir le layout QHBoxLayout qui organise les widgets horizontalement.

Il y en a un autre qui les organise verticalement (c'est quasiment la même chose) : QVBoxLayout.


Le layout vertical



Pour utiliser un layout vertical, il suffit de remplacer QHBoxLayout par QVBoxLayout dans le code précédent. Oui oui, c'est aussi simple que ça :p

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget fenetre;

    QPushButton *bouton1 = new QPushButton("Bonjour");
    QPushButton *bouton2 = new QPushButton("les");
    QPushButton *bouton3 = new QPushButton("Zéros");

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(bouton1);
    layout->addWidget(bouton2);
    layout->addWidget(bouton3);

    fenetre.setLayout(layout);

    fenetre.show();

    return app.exec();
}


N'oubliez pas d'inclure QVBoxLayout.

Compilez et exécutez ce code, et admirez le résultat :

Layout vertical


Amusez-vous à redimensionner la fenêtre. Vous voyez là encore que la layout adapte les widgets qu'il contient à toutes les dimensions. Il empêche en particulier la fenêtre de devenir trop petite, ce qui aurait empêché l'affichage des boutons.


La suppression automatique des widgets



Eh ! Je viens de me rendre compte que tu fais des new dans tes codes, mais il n'y a pas de delete ! Si tu alloues des objets sans les supprimer, ils vont pas rester en mémoire ?


Si, mais comme je vous l'avais dit plus tôt, Qt est intelligent ;)
En fait, les widgets sont placés dans un layout, qui est lui-même placé dans la fenêtre. Lorsque la fenêtre est supprimée (ici à la fin du programme), tous les widgets contenus dans son layout sont supprimés par Qt. C'est donc Qt qui se charge de faire les delete pour nous.


Bien, vous devriez commencer à comprendre comment fonctionnent les layouts :)

Comme on l'a vu au début du chapitre, il y a de nombreux layouts, qui ont chacun leurs spécificités ! Intéressons-nous maintenant au puissant (mais complexe) QGridLayout.

Le layout de grille

Les layouts horizontaux et verticaux sont gentils, mais il ne permettent pas de créer des dispositions très complexes sur votre fenêtre.

C'est là qu'entre en jeu QGridLayout, qui est en fait un peu un assemblage de QHBoxLayout et QVBoxLayout. Il s'agit d'une disposition en grille, comme un tableau avec des lignes et des colonnes.


Schéma de la grille



Il faut imaginer que votre fenêtre peut être découpée sous la forme d'une grille avec une infinité de cases, comme ceci :

Grille


Si on veut placer un widget en haut à gauche, il faudra le placer à la case de coordonnées (0, 0).
Si on veut en placer un autre en-dessous, il faudra utiliser les coordonnées (1, 0).
Ainsi de suite :)


Utilisation basique de la grille



Essayons d'utiliser un QGridLayout simplement pour commencer (oui parce qu'on peut aussi l'utiliser de manière compliquée ^^ ).

Nous allons placer un bouton en haut à gauche, un à sa droite et un en-dessous.
La seule différence réside en fait dans l'appel à la méthode addWidget. Celle-ci accepte 2 paramètres supplémentaires : les coordonnées où placer le widget sur la grille.

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <QApplication>
#include <QPushButton>
#include <QGridLayout>


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget fenetre;

    QPushButton *bouton1 = new QPushButton("Bonjour");
    QPushButton *bouton2 = new QPushButton("les");
    QPushButton *bouton3 = new QPushButton("Zéros");

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(bouton1, 0, 0);
    layout->addWidget(bouton2, 0, 1);
    layout->addWidget(bouton3, 1, 0);

    fenetre.setLayout(layout);

    fenetre.show();

    return app.exec();
}


Résultat :

Boutons disposés selon une grille


Si vous comparez avec le schéma de la grille que j'ai fait plus haut, vous voyez que les boutons ont bien été disposés selon les bonnes coordonnées.

D'ailleurs en parlant du schéma plus haut, il y a un truc que je comprends pas, c'est tous ces points de suspension "..." là. Ca veut dire que la taille de la grille est infinie ? Dans ce cas, comment je fais pour placer un bouton en bas à droite ?


Qt "sait" quel est le widget à mettre en bas à droite en fonction des coordonnées des autres widgets. Le widget qui a les coordonnées les plus élevées sera placé en bas à droite.


Petit test, rajoutons un bouton aux coordonnées (1, 1) :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <QApplication>
#include <QPushButton>
#include <QGridLayout>


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget fenetre;

    QPushButton *bouton1 = new QPushButton("Bonjour");
    QPushButton *bouton2 = new QPushButton("les");
    QPushButton *bouton3 = new QPushButton("Zéros");
    QPushButton *bouton4 = new QPushButton("!!!");

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(bouton1, 0, 0);
    layout->addWidget(bouton2, 0, 1);
    layout->addWidget(bouton3, 1, 0);
    layout->addWidget(bouton4, 1, 1);

    fenetre.setLayout(layout);

    fenetre.show();

    return app.exec();
}


Résultat :

Bouton en bas à droite


Si on veut, on peut aussi décaler le bouton encore plus en bas à droite dans une nouvelle ligne et une nouvelle colonne :

Code : C++
1
layout->addWidget(bouton4, 2, 2);


Bouton en bas à droite


C'est compris ? :)


Un widget qui occupe plusieurs cases



L'avantage de la disposition en grille, c'est qu'on peut faire en sorte qu'un widget occupe plusieurs cases à la fois. On parle de spanning (ceux qui font du HTML doivent avoir entendu parler des attributs rowspan et colspan sur les tableaux).

Pour faire cela, il faut appeler une version surchargée de addWidget qui accepte 2 paramètres supplémentaires : le rowSpan et le columnSpan.

  • rowSpan : nombre de lignes qu'occupe le widget (par défaut 1)
  • columnSpan : nombre de colonnes qu'occupe le widget (par défaut 1)


Imaginons un widget placé en haut à gauche, aux coordonnées (0, 0). Si on lui donne un rowSpan de 2, il occupera alors l'espace suivant :

rowSpan


Si on lui donne un columnSpan de 3, il occupera cet espace :

columnSpan


L'espace pris par le widget au final dépend de la nature du widget (les boutons s'agrandissent en largeur mais pas en hauteur par exemple), et dépend du nombre de widgets sur la grille. En pratiquant vous allez rapidement comprendre comment ça fonctionne.


Essayons de faire en sorte que le bouton "Zéros" prenne 2 colonnes de largeur :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <QApplication>
#include <QPushButton>
#include <QGridLayout>


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget fenetre;

    QPushButton *bouton1 = new QPushButton("Bonjour");
    QPushButton *bouton2 = new QPushButton("les");
    QPushButton *bouton3 = new QPushButton("Zéros");

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(bouton1, 0, 0);
    layout->addWidget(bouton2, 0, 1);
    layout->addWidget(bouton3, 1, 0, 1, 2);

    fenetre.setLayout(layout);

    fenetre.show();

    return app.exec();
}


Notez la ligne : layout->addWidget(bouton3, 1, 0, 1, 2);
Les 2 derniers paramètres correspondent respectivement au rowSpan et au columnSpan. Le rowSpan est ici de 1, c'est la valeur par défaut on ne change donc rien, mais le columnSpan est de 2.

Le bouton va donc "occuper" 2 colonnes :

Spanning du bouton


Essayez en revanche de monter le columnSpan à 3 : vous ne verrez aucun changement.
En effet, il aurait fallu qu'il y ait un troisième widget sur la première ligne pour que le columnSpan puisse fonctionner.


Faites des tests avec le spanning pour vous assurer que vous avez bien compris comment ça marche :)

Le layout de formulaire

Le layout de formulaire QFormLayout est un layout assez récent spécialement fait pour les fenêtres qui contiennent des formulaires.

Un formulaire est en général une suite de libellés ("Votre prénom :") associés à des champs de formulaire (zone de texte par exemple) :

Formulaire


Normalement, pour écrire du texte dans la fenêtre, on utilise le widget QLabel (libellé), dont on parlera plus en détail dans le prochain chapitre.

L'avantage du layout que nous allons utiliser, c'est qu'il simplifie notre travail en créant automatiquement des QLabel pour nous.

Vous noterez d'ailleurs que la disposition correspond à celle d'un QGridLayout à 2 colonnes et plusieurs lignes. En effet, le QFormLayout n'est en fait rien d'autre qu'une version spéciale du QGridLayout pour les formulaires, avec quelques particularités : il s'adapte en fonction des habitudes des OS, pour certains les libellés sont alignés à gauche, pour d'autres ils sont alignés à droite, etc.



L'utilisation d'un QFormLayout est très simple. La différence, c'est qu'au lieu d'utiliser une méthode addWidget, nous allons utiliser une méthode addRow qui prend 2 paramètres :

  • Le texte du libellé
  • Un pointeur vers le champ du formulaire


Pour faire simple, nous allons créer 3 champs de formulaire de type "Zone de texte à une ligne" (QLineEdit), puis nous allons les placer dans un QFormLayout au moyen de la méthode addRow :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <QApplication>
#include <QLineEdit>
#include <QFormLayout>


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget fenetre;

    QLineEdit *nom = new QLineEdit;
    QLineEdit *prenom = new QLineEdit;
    QLineEdit *age = new QLineEdit;

    QFormLayout *layout = new QFormLayout;
    layout->addRow("Votre nom", nom);
    layout->addRow("Votre prénom", prenom);
    layout->addRow("Votre âge", age);

    fenetre.setLayout(layout);

    fenetre.show();

    return app.exec();
}


Résultat :

Layout de formulaire


Sympa, non ? :)

On peut aussi définir des raccourcis clavier pour accéder rapidement aux champs du formulaire. Pour ce faire, placez un symbole "&" devant la lettre du libellé que vous voulez transformer en raccourci.

Explication en image (euh, en code) :

Code : C++
1
2
3
layout->addRow("Votre &nom", nom);
layout->addRow("Votre &prénom", prenom);
layout->addRow("Votre â&ge", age);


La lettre "p" est désormais un raccourci vers le champ du prénom.
"n" pour le champ nom.
"g" pour le champ âge.

L'utilisation du raccourci dépend de votre système d'exploitation. Sous Windows, il faut faire Alt puis la touche raccourci.
Lorsque vous appuyez sur Alt, les lettres raccourcis apparaissent soulignées :

Raccourcis dans un form layout


Faites Alt + N pour accéder directement au champ du nom ! :)

Souvenez-vous de ce symbole &, il est très souvent utilisé en GUI Design (design de fenêtre) pour indiquer quelle lettre sert de raccourci. On le réutilisera notamment pour avoir des raccourcis dans les menus de la fenêtre.

Ah, et si vous voulez par contre vraiment afficher un symbole & dans un libellé, tapez-en deux : "&&".
Exemple : "Bonnie && Clyde".

Combiner les layouts

Avant de terminer ce chapitre, il me semble important que nous jetions un oeil aux layouts combinés, une fonctionnalité qui va vous faire comprendre toute la puissance des layouts.
Commençons comme il se doit par une question que vous devriez vous poser :

Les layouts c'est bien joli, mais c'est pas un peu limité ? Si je veux faire une fenêtre un peu complexe, ce n'est pas à grands coups de QVBoxLayout ou même de QGridLayout que je vais m'en sortir !


C'est vrai que mettre ses widgets les uns en-dessous des autres peut sembler limité. Même la grille fait un peu "rigide", je reconnais.
Mais rassurez-vous, tout a été pensé. La magie apparaît lorsque nous commençons à combiner les layouts, c'est-à-dire à placer un layout dans un autre layout.


Un cas concret



Prenons par exemple notre joli formulaire. Supposons que l'on veuille ajouter un bouton "Quitter". Si vous voulez placer ce bouton en bas du formulaire, comment faire ?

Il va falloir d'abord créer un layout vertical (QVBoxLayout), et placer à l'intérieur notre layout de formulaire puis notre bouton "Quitter".

Cela donne le schéma suivant :

Schéma des layouts combinés


On voit que notre QVBoxLayout contient 2 choses, dans l'ordre :

  1. Un QFormLayout (qui contient lui-même d'autres widgets)
  2. Un QPushButton


Un layout peut donc contenir aussi bien des layouts que des widgets.


Utilisation de addLayout



Pour insérer un layout dans un autre, on utilise addLayout au lieu de addWidget (c'est logique me direz-vous ;) ).

Voici un bon petit code pour se faire la main :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <QApplication>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QFormLayout>


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget fenetre;

    // Création du layout de formulaire et de ses widgets

    QLineEdit *nom = new QLineEdit;
    QLineEdit *prenom = new QLineEdit;
    QLineEdit *age = new QLineEdit;

    QFormLayout *formLayout = new QFormLayout;
    formLayout->addRow("Votre &nom", nom);
    formLayout->addRow("Votre &prénom", prenom);
    formLayout->addRow("Votre â&ge", age);


    // Création du layout principal de la fenêtre (vertical)

    QVBoxLayout *layoutPrincipal = new QVBoxLayout;
    layoutPrincipal->addLayout(formLayout); // Ajout du layout de formulaire

    QPushButton *boutonQuitter = new QPushButton("Quitter");
    QWidget::connect(boutonQuitter, SIGNAL(clicked()), &app, SLOT(quit()));
    layoutPrincipal->addWidget(boutonQuitter); // Ajout du bouton

    fenetre.setLayout(layoutPrincipal);

    fenetre.show();

    return app.exec();
}


J'ai surligné les ajouts au layout vertical principal :

  • L'ajout du sous-layout de formulaire (addLayout)
  • L'ajout du bouton (addWidget)


Vous remarquerez que je fais les choses un peu dans l'ordre inverse : d'abord je crée les widgets et layouts "enfants" (le QFormLayout), et ensuite je crée le layout principal (le QVBoxLayout) et j'y ajoute le layout enfant que j'ai créé.

Au final, la fenêtre qui apparaît est la suivante :

Layouts combinés


On ne le voit pas, mais la fenêtre contient d'abord un QVBoxLayout, qui contient lui-même un layout de formulaire et un bouton :

Layouts combinés (schéma)



Exercice



Essayez d'obtenir le rendu suivant :

Exercice des layouts



Si vous voulez mettre plusieurs boutons en bas sur la même ligne, vous pouvez créer un QHBoxLayout et ajouter ce QHBoxLayout au QVBoxLayout !
Vous pouvez aussi utiliser plus simplement un QGridLayout en utilisant un columnSpan. En effet, un QGridLayout n'est rien d'autre qu'un assemblage de QVBoxLayout et de QHBoxLayout.

Plusieurs méthodes sont donc possibles, libre à vous d'utiliser un QGridLayout ou des QVBoxLayout et QHBoxLayout.

Ce ne devrait pas être un exercice difficile si vous avez bien suivi ce chapitre. Ce sera en tout cas l'occasion de vous assurer que vous avez bien compris ;)

Dans mon exemple, les boutons "Aide" et "Envoyer" ne font rien (je n'ai pas géré de signaux et de slots pour eux). Le résultat que vous devez obtenir est juste visuel, n'essayez pas de tenter d'envoyer le formulaire sur internet et de le stocker dans une base de données, il est un peu trop tôt encore :p

Q.C.M.

Les layouts permettent-ils d'obtenir un positionnement absolu ou relatif des widgets ?
Quelle est la particularité d'une classe abstraite ?
Laquelle de ces classes permet de disposer plusieurs widgets sur une même ligne ?
Quelle méthode de la fenêtre doit-on appeler pour lui indiquer le layout principal qu'elle doit utiliser ?
Un layout peut-il en contenir un autre ?
Quelles sont les coordonnées du coin en haut à gauche dans un QGridLayout ?
Quel symbole permet de définir un raccourci clavier, notamment pour les formulaires construits avec un QFormLayout ?

Statistiques de réponses au QCM

Les layouts sont la base du positionnement de widgets en GUI Design. Ils nous donnent un maximum de flexibilité pour que nos fenêtres s'adaptent à toutes les conditions.

Bien entendu, je vous mentirais si je vous disais qu'absolument tout le monde les utilise. Pour certains logiciels simples, il n'est parfois pas nécessaire de recourir aux layouts. Il est néanmoins recommandé de s'en servir autant que possible.

Nous n'avons pas pu absolument tout voir à propos des layouts. La différence, c'est que maintenant je vous ai appris à vous servir de la doc et vous pouvez aller compléter ce que vous savez si besoin est.

Je vous recommande de lire leur page d'explication générale sur les layouts puis de regarder les différentes classes de layouts. N'oubliez pas de consulter les classes parentes à chaque fois, ce sont souvent elles qui contiennent les méthodes et attributs qui semblent manquer.
Jetez un oeil aux "stretch factors", qui permettent de définir des tailles proportionnelles pour les widgets, ainsi qu'à l'alignement des widgets.


Dans le prochain chapitre, nous passerons en revue la plupart des widgets courants et simples. En effet, cela fait un moment que je vous fais utiliser pour le besoin du cours quelques widgets comme les boutons et les champs de texte, mais il est maintenant temps de faire un tour d'horizon plus général pour que vous sachiez quels sont les principaux widgets qui peuvent peupler une fenêtre.
Chapitre précédent Sommaire Chapitre suivant

Partager

32 commentaires pour "Positionner ses widgets avec les layouts"
Note moyenne : 3.85 / 4 (1749 votes)
Pseudo Commentaire
Hors ligne samir84 # Posté le 20/02/2011 à 22:48:36

Bonjour je vous présente l'exercice avec les 3 boutons en layout version amélioré, et fait avec une class :


main.cpp :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Bibliothèque Nécéssaire

#include <QTextCodec> // Permettant de gérer les accents (voir l.25 - 26)

#include "MaFenetre.h"


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);


    QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); // Modifie le codec pour que les accents
    QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));      // soient gérés

    MaFenetre fenetre; // Crée l'objet "fenetre"


    fenetre.aff(fenetre); // ajout du layout dans fenetre par la fonction de MaFenetre
    fenetre.show(); // Affiche la fenêtre

    return app.exec();
}



MaFenetre.h :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#ifndef MAFENETRE_H_INCLUDED
#define MAFENETRE_H_INCLUDED

// Bibliothèque Nécéssaire

#include <QtGui>


class MaFenetre : public QWidget // MaFenetre hérite de QWidget (mêmes attributs/méthodes)
{

    Q_OBJECT // Obligatoire pour créer des Slots et Signaux personnelles


  public : // Méthode

    MaFenetre(); // Constructeur Par Défaut
    void aff(MaFenetre &fenetre);


  public slots : // Slot Perso

    void enregistrerFormulaire(); // Fonction qui enregistrera le nom, prenom, ville et âge
                                 // de l'utilisateur grâce au formulaire


  signals : // Signals Perso



  protected : // Attributs

   // Création Des Boutons

    QPushButton *m_bouton;
    QPushButton *m_bouton2;
    QPushButton *m_bouton3;

  // Création Du Formulaire

    QFormLayout *formlayout; // Attributs permettant de créer le formulaire

    // Case du Formulaire

    QLineEdit *nom;
    QLineEdit *prenom;
    QLineEdit *ville;
    QLineEdit *age;

  // Création des mises en pages

   QVBoxLayout *layoutPrimaire; // Contiendra "formLayout" et "layoutSecondaire"
   QHBoxLayout *layoutSecondaire;   // Orgranisera les boutons en horizontal aligné

};

#endif // MAFENETRE_H_INCLUDED



MaFenetre.cpp

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include "MaFenetre.h"

// Inialisation des attributs grâce au constructeur


MaFenetre::MaFenetre() : QWidget() // Constructeur de QWidget puis de MaFenetre
{
    setFixedSize(333, 180);

    // Allocation Dynamique des attributs du formulaire ...

    formlayout = new QFormLayout;

    nom = new QLineEdit;
    prenom = new QLineEdit;
    ville = new QLineEdit;
    age = new QLineEdit;

    // ... Fin

    // Initialisation du Formulaire ...

    formlayout->addRow("Nom", nom);
    formlayout->addRow("Prenom", prenom);
    formlayout->addRow("Ville", ville);
    formlayout->addRow("Age", age);

    // ... Fin

    // Allocation Dynamique des attributs de la mise en page ...

    m_bouton = new QPushButton("Rechercher...", this);
    m_bouton2 = new QPushButton("A propos de Qt", this);
    m_bouton3 = new QPushButton("Quitter", this);

    m_bouton->setCursor(Qt::PointingHandCursor);
    m_bouton2->setCursor(Qt::PointingHandCursor);
    m_bouton3->setCursor(Qt::PointingHandCursor);

    layoutPrimaire = new QVBoxLayout;
    layoutSecondaire = new QHBoxLayout;

    // ... Fin


    // Initialisation de la mise en page ...

    layoutPrimaire->addLayout(formlayout);

    layoutSecondaire->addWidget(m_bouton);
    layoutSecondaire->addWidget(m_bouton2);
    layoutSecondaire->addWidget(m_bouton3);

    layoutPrimaire->addLayout(layoutSecondaire);

    // ... Fin


    // Initialisation des Slots et Signaux ...

    QObject::connect(m_bouton, SIGNAL(clicked()), this, SLOT(enregistrerFormulaire()));
    QObject::connect(m_bouton2, SIGNAL(clicked()), qApp, SLOT(aboutQt()));
    QObject::connect(m_bouton3, SIGNAL(clicked()), qApp, SLOT(quit()));
}


//Initialisation du Signal Personnalisé

void MaFenetre::enregistrerFormulaire()
{
    QString fichier = QFileDialog::getOpenFileName(this, "Obtenir Chemin d'un fichier");

    if(!fichier.isEmpty())
    {
        QMessageBox::information(this, "Information", "Chemin Du Fichier : \n" + fichier);
    }

    else
    {
        QMessageBox::warning(this, "Attention", "Attention : Aucun fichier sélectionné");
    }
}


void MaFenetre::aff(MaFenetre &fenetre)
{
    fenetre.setLayout(layoutPrimaire);
}
Hors ligne Angels19 # Posté le 19/04/2011 à 02:53:58
Avatar

Citation
QLayout est ce qu'on appelle une classe abstraite. Je ne vous en ai pas trop parlé jusqu'ici.


M@teo: Petite précision, en fait, tu avais déjà parlé plusieurs fois des classes abstraites, comment les déclarer, et à quoi elles servent(dans les tutos précédents de Qt et si je dis pas de bêtises, dans les tutos des classes aussi)...
donc ce serait pas trop mal de modifier les quelques lignes qui suivent la citation (c'est comme si c'est la première fois qu'on tombe sur une classe abstraite mais on en a déjà eu quelques unes ;) ) Juste 2-3 tournures des phrases à modifier ^^

Sinon, supers ces tutos !! Un grand merci pour tout le travail réalisé :D
Hors ligne lacoste-313 # Posté le 16/11/2011 à 08:24:46
J'men fou.
Avatar

Ville : Pont-à-celles
Pays : Belgique

Bonjour, superbe tuto, comme d'habitude.

Cependant, j'ai une petite chose à souligné (et peut être que quelqu'un trouvera utile de le modifier.)

Dans une des précédente partie du cours, M@teo disait qu'il était très rare de devoir donner la valeur 0 au parent d'un QPushButton (ou autres)

Code : C++
1
QPushButton ( QWidget * parent = 0 )


A savoir que si l'ont aurait mit un QWidget en paramètre, le bouton (QPushButton) serait affiché dans ce widget.

Au début de ce chapitre, ont nous donne ce code:

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <QApplication>
#include <QPushButton>


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget fenetre;

    QPushButton bouton("Bonjour", &fenetre); // C'est cette ligne qui nous intéresse
    bouton.move(70, 60);

    fenetre.show();

    return app.exec();
}


Et on passe directement 'un peut plus loin à ça:

Code : C++
1
2
3
QPushButton *bouton1 = new QPushButton("Bonjour");
QPushButton *bouton2 = new QPushButton("les");
QPushButton *bouton3 = new QPushButton("Zéros");


La différence, c'est qu'ici, on ne met plus de "QWidget *parent".

Sachant ça, je pense qu'il serait bon d'expliqué:

-Pourquoi il ne faut plus mettre de QWidget *parent (vu que M@théo avait dit que c'était plutôt rare je pense que ça mérite une explication)

-D'expliqué que les layout ce charge d'affiché les diffèrent widget qu'il contient quand on utilise "fenetre.setLayout(layout);" (et que donc c'est pour ça qu'il ne faut plus utilisé les QWidget *parent)

---

J'en suis arrivé à ça En codant à coté (pendant que je lisait le tuto) j'en suis arrivé a un code qui ne fessait pas ce que je lui avait demander. Simplement du faite que j'avais déclarer (?) le Qwidget *parent. Du coup les layout n'était pas pris en compte.

Bref, continue comme ça M@théo, t'es cours sont vraiment très bon et utile à plus d'un d'entre nous. 15/20 (pour ce que j'ai cité)
Hors ligne vorpal # Posté le 31/01/2012 à 03:28:49

Bravo pour tes tutos.

Je voudrait savoir quand ont crée un layout 'de type bouton' celle-si est automatiquement centrer dans le centre de la fenêtre lors d'une redimention.

Alors pourquoi sur le dernier exemple Le bouton n'est pas centrer il reste en bas de la fenêtre.


Image utilisateur
Hors ligne unickk # Posté le 11/02/2012 à 13:42:18

Pour ma part j'imagine que le dernier layout appelé est celui qui se trouve en bas et le premier est celui du haut histoire qu'il n'y ai pas confusion

Voir tous les commentaires