La libraire Qt - 4
Les layouts

Le mois dernier nous avons appris à créer nos propres widgets, et au passage une méthode pour organiser automatiquement les widgets dans une fenêtre. Je vous propose de découvrir aujourd’hui une autre méthode, un peu plus délicate, mais plus souple et plus puissante.

1. Un peu de couleurs

2. Personnaliser le redimensionnement

3. Marges et espacements

4. Élastiques

5. Imbriquation des layouts

6. Le retour de la grille

7. Conclusion

codes sources

L’inconvénient de QGrid, QHBox ou QVBox est que si ces classes permettent de disposer les widgets, elles ne s’occupent guère de l’espace qu’ils occupent - notamment, en cas de redimensionnement. Essayez d’agrandir ou réduir la fenêtre du convertisseur euro du mois dernier : ce n’est pas très esthétique.

1. Un peu de couleurs

Je vous propose un exemple coloré, pour bien voir ce qui se passe. Nous allons afficher une série de boutons, chacun d’une certaine couleur, et disposés « harmonieusement ». Les couleurs dans Qt sont décrites par la classe QColor, qui propose quelques outils permettant de les manipuler (éclaircir, assombrir, composantes RGB ou HSV, etc.). Voici un premier programme :

01: #include <qlayout.h>
02: #include <qwidget.h>
03: #include <qpushbutton.h>
04: #include <qcolor.h>
05: #include <qapplication.h>
06: #include <qsizepolicy.h>
07: typedef struct
08: { char* Nom ;
09:   QColor Couleur ;
10: } NomCouleur ;
11: int main (int argc, char* argv[])
12: { QApplication app(argc, argv) ;
13:   QWidget w ;
14:   NomCouleur tab[] =
15:   {{"rouge", Qt::red}, {"vert", Qt::green}, {"bleu", Qt::blue},
16:    {"jaune", Qt::yellow}, {"cyan", Qt::cyan}} ;
17:   QHBoxLayout * hbox_1 = new QHBoxLayout(&w) ;
18:   QPushButton* bouton = 0 ;
19:   int i ;
20:   for (i = 0 ; i < 3 ; i++)
21:   { bouton = new QPushButton(tab[i].Nom, &w) ;
22:     bouton->setPaletteBackgroundColor(tab[i].Couleur) ;
23:     hbox_1->addWidget(bouton) ;
24:   }
25:   app.setMainWidget(&w) ;
26:   w.show() ;
27:   return app.exec() ;
28: }


lignes 1 à 6 : les fichiers d’en-têtes dont nous avons besoin. Remarquez en particulier <qlayout.h> et <qcolor.h>, qui sont nouveaux ce mois-ci.

Compilez, exécutez, voyez :

Si vous étendez la fenêtre horizontalement, les boutons changent de taille :

Par contre, si vous l’étendez verticalement, ils ne suivent pas le mouvement :

Remarquez que vous ne pouvez plus réduire la fenêtre de manière à masquer une partie de son contenu. Personnalisons un peu tout cela.

2. Personnaliser le redimensionnement

Si les boutons ne s’étendent pas verticalement, c’est que la classe QPushButton définie une politique de redimensionnement (size policy) précisant cela : extension autorisée en largeur mais pas en hauteur, et réduction interdite si elle risque de masquer le texte. Les zones de textes QLabel autorisent par contre l’extension verticale.

Cette politique de redimensionnement est stockée dans la classe QSizePolicy, dont tout widget possède une instance. Les classes de la famille layouts utilisent beaucoup ces informations. Cette classe est déclarée dans <qsizepolicy.h>.

Insérez ces trois lignes juste après la boucle for :

1:     QSizePolicy size_pol = bouton->sizePolicy() ;
2:     size_pol.setVerData(QSizePolicy::MinimumExpanding) ;
3:     bouton->setSizePolicy(size_pol) ;

Nous modifions la politique de redimensionnement verticale du dernier bouton créé dans la boucle (le bleu en l’occurrence), pour qu’il utilise tout l’espace qu’on lui donne. Compilez et exécutez :

Cette fois la fenêtre a une hauteur par défaut, et le bouton occupe cet espace. Si vous redimensionner la fenêtre, le bouton bleu suit les changements. Consultez la documentation de QSizePolicy pour voir quelles sont les possibilités. La politique horizontale d’un widget peut être obtenue par QWidget::horData(), et définie par QWidget::setHorData().

3. Marges et espacements

Peut-être trouvez-vous que les boutons sont trop collés l’un à l’autre, et qu’il faudrait les séparer des bords de la fenêtre (qui sont aussi les bords du widget principal). Changez la ligne 17, pour donner un nouvel argument au constructeur de QHBoxLayout :

QHBoxLayout * hbox_1 = new QHBoxLayout(&w, 8) ;

Maintenant les boutons sont décollés de la fenêtre et espacés ! Les espaces sont, vous l’aurez deviné, de 8 pixels exactement. En fait ce paramètre défini la marge extérieure placée autour du layout, et s’il n’est suivi d’aucune autre valeure, il défini aussi l’espacement intérieur. Essayez ceci :

QHBoxLayout * hbox_1 = new QHBoxLayout(&w, 8, 30) ;

Les boutons sont beaucoup plus espacés ! Et si vous redimensionnez la fenêtre, les boutons s’agrandissent également, marges et espacements demeurants fixes.

Les valeurs données au constructeur du layout sont globales. Vous pouvez insérer un espacement quelconque entre deux éléments à l’aide de la méthode insertSpace :

QHBoxLayout * hbox_1 = new QHBoxLayout(&w) ;
QPushButton* bouton = 0 ;
hbox_1->addSpacing(10) ;
bouton = new QPushButton(?rouge?, &w) ;
bouton->setPaletteBackgroundColor(Qt::red) ;
hbox_1->addWidget(bouton) ;
hbox_1->addSpacing(20) ;
bouton = new QPushButton(?vert?, &w) ;
bouton->setPaletteBackgroundColor(Qt::green) ;
hbox_1->addWidget(bouton) ;
hbox_1->addSpacing(30) ;

Ce qui donne ce résultat :

Plus de marges en haut et en bas, mais des marges à gauche et à droite. Si vous étendez la fenêtre horizontalement, remarquez que les espaces restent fixes, tandis que les boutons s’agrandissent.

4. Élastiques

Ce comportement n’est pas toujours souhaitable, parfois on préfère que ce soient les espacements qui varient avec la taille de la fenêtre. Qt permet cela, en définissant des élastiques entre widgets :

01: QHBoxLayout * hbox_1 = new QHBoxLayout(&w) ;
02: QPushButton* bouton = 0 ;
03: hbox_1->addStretch(1) ;
04: bouton = new QPushButton(?rouge?, &w) ;
05: bouton->setPaletteBackgroundColor(Qt::red) ;
06: hbox_1->addWidget(bouton) ;
07: hbox_1->addStrech(2) ;
08: bouton = new QPushButton(?vert?, &w) ;
09: bouton->setPaletteBackgroundColor(Qt::green) ;
10: hbox_1->addWidget(bouton) ;
11: hbox_1->addStretch(1) ;

Maintenant les boutons ne changent pas de taille, c’est l’espacement qui varie :

Remarquez que l’espacement entre les deux boutons est toujours double de l’espacement contre les bords : c’est du au fait que nous avons donné un facteur d’élasticité double à cette espace, ligne 7. Si nous avions indiqué 3, l’espacement serait triple. Si nous donnons 2 en lignes 3 et 11, et 3 en ligne 7, alors l’espacement intérieur sera égale à 3/2 de l’espacement extérieur... Le facteur d’élasticité est donc relatif.

Naturellement, rien n’empêche de combiner espacement fixe et espacement élastique. C’est même la seule façon de garantir qu’un certain espace ne sera pas réduit à 0. Par exemple, pour avoir un espacement de 10 à gauche, de 20 entre les boutons, et l’espace à droite extensible mais avec un minimum de 10 pixels, il suffit de changer la ligne 3 en :

hbox_1->addSpacing(10) ;

...de changer la ligne 7 en :

hbox_1->addSpacing(20) ;

...et de changer la ligne 11 en ces deux lignes :

hbox_1->addSpacing(10) ;
hbox_1->addStretch(1) ;

Ainsi les boutons restent espacés, et collés sur la gauche.

Si vous voulez néammoins que les widgets demeurent extensibles, il suffit de donner un paramètre supplémentaire à la méthode addWidget(), précisément un facteur d’élasticité (toujours relatif donc). Par exemple, si je veux que mon bouton vert s’étende du double de l’espacement à droite, il suffit de changer la ligne 10 ainsi :

hbox_1->addWidget(bouton, 2) ;

À vous de trouver la bonne combinaison pour avoir une présentation harmonieuse !

5. Imbriquation des layouts

Nous n’avons jusqu’ici utilisé qu’un layout horizontal, pour n’organiser que des widgets. Mais voyez ceci :

01: 1.QVBoxLayout * vbox = new QVBoxLayout(&w) ;
02: 2.QPushButton* bouton = 0 ;
03: 3.int i ;
04: 4.QHBoxLayout * hbox_1 = new QHBoxLayout(vbox) ;
05: 5.for (i = 0 ; i < 3 ; i++)
06: 6.{ bouton = new QPushButton(tab[i].Nom, &w) ;
07: 7.  bouton->setPaletteBackgroundColor(tab[i].Couleur) ;
08: 8.  hbox_1->addWidget(bouton) ;
09: 9.}
10: 10.QHBoxLayout * hbox_2 = new QHBoxLayout(vbox) ;
11: 11.for (i = 3 ; i < 5 ; i++)
12: 12.{ bouton = new QPushButton(tab[i].Nom, &w) ;
13: 13.  bouton->setPaletteBackgroundColor(tab[i].Couleur) ;
14: 14.  hbox_2->addWidget(bouton) ;
15: 15.}

Dans le détail :

Ce code donne le résultat suivant, où j’ai un peu étendu la fenêtre dans les deux directions :

Dans chaque layout horizontal, les widgets se comportent comme précédemment : ils s’étendent dans une direction (parce que QPushButton est contrainte dans l’autre). Les layouts horizontaux sont eux-mêmes organisés par le layout vertical, comme s’il s’agissait de widgets « normaux ». Vous pouvez librement utiliser les méthodes addSpacing() et addStretch() pour organiser tout cela comme bon vous semble.


Dernier mot concernant ces layouts linéraires : vous pouvez insérer un widget entre deux autres à l’aide de la méthode insertWidget(), qui prend en paramètre la position d’insertion et le widget (et un facteur d’élasticité optionnel). Par exemple, pour insérer un bouton entre les deux widgets du bas :

bouton = new QPushButton("nouveau", "w) ;
hbox_2->insertWidget(1, bouton) ;

La position 0 correspondant à « avant le premier widget ».

Pour être complet, notons que QHBoxLayout et QVBoxLayout dérivent de QBoxLayout, dont le constructeur prend en deuxième paramètre la direction d’organisation : de gauche à droite, de haut en bas, de bas en haut... La déclaration suivante est parfaitement équivalente à un QHBoxLayout tel que nous l’avons utilisé ici :

QBoxLayout box(&w, QBoxLayout::LeftToRight) ;

Les autres paramètres (optionnels) sont la marge et l’espacement.

Les autres valeurs possibles pour ce deuxième paramètres sont RightToLeft (droite à gauche), TopToBottom (haut en bas, celui de QVBoxLayout), BottomToTop (bas en haut), Down = TopToBottom et Up = BottomToTop.

6. Le retour de la grille

Il existe dans les layouts l’équivalent du QGrid que nous avons vu le mois dernier : QGridLayout. Voici un exemple :

01: QGridLayout * grid = new QGridLayout(&w) ;
02: QPushButton* bouton = 0 ;
03: bouton = new QPushButton("rouge", &w) ;
04: bouton->setPaletteBackgroundColor(Qt::red) ;
05: grid->addWidget(bouton, 0, 0) ;
06: bouton = new QPushButton("vert", &w) ;
07: bouton->setPaletteBackgroundColor(Qt::green) ;
08: grid->addWidget(bouton, 1, 0) ;
09: bouton = new QPushButton("bleu", &w) ;
10: bouton->setPaletteBackgroundColor(Qt::blue) ;
11: grid->addWidget(bouton, 2, 0) ;
12: bouton = new QPushButton("jaune", &w) ;
13: bouton->setPaletteBackgroundColor(Qt::yellow) ;
14: grid->addWidget(bouton, 2, 1) ;
15: bouton = new QPushButton("cyan", &w) ;
16: bouton->setPaletteBackgroundColor(Qt::cyan) ;
17: grid->addWidget(bouton, 2, 2) ;
18: bouton = new QPushButton("vert", &w) ;
19: bouton->setPaletteBackgroundColor(Qt::green) ;
20: grid->addWidget(bouton, 1, 0) ;
21: QLabel * label = new QLabel("grand blanc", &w) ;
22: label->setAlignment(Qt::AlignCenter) ;
23: label->setPaletteBackgroundColor(Qt::white) ;
24: grid->addMultiCellWidget(label, 0, 1, 1, 2) ;

Ce qui produit :

Vous pouvez insérer des espacements entre lignes avec addRowSpacing() et entre colonnes avec addColSpacing(), en donnant l’indice et la valeur de l’espacement. Il s’agit dans ce cas d’un espacement minimum, qui peut changer si le widget qui contient la grille change de taille.

7. Conclusion

Vous voilà équipé pour construire des interfaces complexes, qui s’organisent (presque) toutes seules et réagissent correctement aux changements de taille. Le mois prochain, nous aborderons des widgets plus sophistiqués, comme les zones de défilement.

Yves Bailly

http://www.kafka-fr.net



Article publié dans LinuxMagazine 40 de juin 2002