Developpez.com

Qt

Choisissez la catégorie, puis la rubrique :

Viadeo Twitter Facebook Share on Google+   
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Gestion de la disposition

Le système de disposition de Qt fournit un système d'agencement automatique des widgets à l'intérieur d'un autre, de manière simple et puissante, afin d'assurer qu'ils occupent l'espace disponible de manière optimale.

  

Introduction

Qt inclut un ensemble de classes gérant la disposition (layout). Elles sont utilisées pour décrire la manière dont les widgets sont disposés dans l'interface utilisateur de l'application. Ces dispositions repositionnent et redimensionnent automatiquement les widgets dès que l'espace disponible pour ces derniers change, afin qu'ils soient placés de façon cohérente et que l'interface utilisateur reste, dans son ensemble, utilisable.

Toutes les classes héritées de QWidget peuvent utiliser les dispositions pour gérer leurs enfants. La méthode QWidget::setLayout() applique une disposition au widget. Lorsqu'une disposition est appliquée de cette manière, elle prend en charge les tâches suivantes :

  • le positionnement des widgets enfants ;
  • une taille par défaut raisonnable des fenêtres ;
  • une taille minimum raisonnable des fenêtres ;
  • la gestion du redimensionnement ;
  • la mise à jour automatique lorsque le contenu change :
    • la taille de la police, le texte ou tout autre contenu des widgets enfants,
    • le masquage ou le démasquage d'un widget enfant,
    • la suppression d'un widget enfant.

Les classes de disposition de Qt

Les classes de disposition de Qt ont été conçues pour le code C++ écrit « à la main », permettant pour simplifier de spécifier les mesures en pixels. Ils sont donc faciles à comprendre et à utiliser. Le code généré pour des formulaires créés via Qt Designer utilise aussi ces classes de disposition. Qt Designer est utile pour expérimenter un nouveau design. Cela permet d'éviter d'avoir à compiler, lier et lancer l'application, un cycle souvent répété lors du développement d'une interface utilisateur.

QBoxLayout Aligne les widgets enfants horizontalement ou verticalement.
QButtonGroup Un conteneur pour organiser des groupes de widgets boutons.
QFormLayout Gère des formulaires, associant des widgets de saisie avec leur étiquette.
QGraphicsAnchor Représente une ancre entre deux objets dans un QGraphicsAnchorLayout.
QGraphicsAnchorLayout Disposition dans laquelle on peut ancrer ensemble des widgets à l'intérieur d'une vue graphique (Graphics View).
QGridLayout Dispose les widgets dans une grille.
QGroupBox Emplacement avec un titre regroupant des boutons (cases à cocher, boutons radio, etc.).
QHBoxLayout Aligne les widgets horizontalement.
QLayout La classe de base pour la gestion de la disposition.
QLayoutItem Objet abstrait manipulé par QLayout.
QSizePolicy Un attribut des dispositions décrivant la taille horizontale et verticale de la police.
QSpacerItem Un espace vide dans une disposition.
QStackedLayout Une pile de widgets où un seul widget est visible à la fois.
QStackedWidget Une pile de widgets où un seul widget est visible à la fois.
QVBoxLayout Aligne les widgets verticalement.
QWidgetItem Élément d'une disposition représentant un widget.

Les dispositions horizontale, verticale, en grille et en formulaire

Le meilleur moyen de bien disposer vos widgets est d'utiliser les dispositions toutes prêtes : QHBoxLayout, QVBoxLayout, QGridLayout et QFormLayout. Ces classes héritent toutes de QLayout, qui elle-même dérive de QObject (et non de QWidget). Elles s'occupent de gérer la disposition d'un ensemble de widgets. Pour créer des dispositions plus complexes, vous pouvez imbriquer les dispositions les unes dans les autres.

  • Un QHBoxLayout dispose les widgets sur une ligne horizontale, de gauche à droite (ou de droite à gauche pour les langues s'écrivant de droite à gauche).

image

  • Un QVBoxLayout dispose les widgets sur une colonne verticale, de haut en bas.

image

  • Un QGridLayout dispose les widgets dans une grille à deux dimensions. Les widgets peuvent occuper plusieurs cellules.

image

  • Un QFormLayout dispose les widgets en deux colonnes : pour les descriptions d'une part et pour les champs de saisie d'autre part.

image

Positionnement des widgets dans le code

Le code suivant crée un QHBoxLayout qui s'occupe de la disposition de cinq QPushButtons, tel que montré dans la première capture d'écran ci-dessus.

     QWidget *window = new QWidget;
     QPushButton *button1 = new QPushButton("One");
     QPushButton *button2 = new QPushButton("Two");
     QPushButton *button3 = new QPushButton("Three");
     QPushButton *button4 = new QPushButton("Four");
     QPushButton *button5 = new QPushButton("Five");
 
     QHBoxLayout *layout = new QHBoxLayout;
     layout->addWidget(button1);
     layout->addWidget(button2);
     layout->addWidget(button3);
     layout->addWidget(button4);
     layout->addWidget(button5);
 
     window->setLayout(layout);
     window->show();

Le code pour un QVBoxLayout est identique, à l'exception de la ligne où la disposition est créée. Le code pour un QGridLayout est légèrement différent car nous avons besoin de préciser la ligne et la colonne pour positionner le widget enfant :

     QWidget *window = new QWidget;
     QPushButton *button1 = new QPushButton("One");
     QPushButton *button2 = new QPushButton("Two");
     QPushButton *button3 = new QPushButton("Three");
     QPushButton *button4 = new QPushButton("Four");
     QPushButton *button5 = new QPushButton("Five");
 
     QGridLayout *layout = new QGridLayout;
     layout->addWidget(button1, 0, 0);
     layout->addWidget(button2, 0, 1);
     layout->addWidget(button3, 1, 0, 1, 2);
     layout->addWidget(button4, 2, 0);
     layout->addWidget(button5, 2, 1);
 
     window->setLayout(layout);
     window->show();

Le troisième QPushButton couvre deux colonnes. Cela se fait en précisant une valeur de 2 pour le cinquième argument de QGridLayout::addWidget().

QFormWidget va ajouter deux widgets par ligne, souvent un QLabel et un QLineEdit, pour créer un formulaire. Ajouter un QLabel et un QLineEdit sur la même ligne va définir le QLabel comme l'associé du QLineEdit. Le code suivant va utiliser un QFormLayout pour placer trois QPushButtons et leur QLineEdit respectif sur une ligne.

     QWidget *window = new QWidget;
     QPushButton *button1 = new QPushButton("One");
     QLineEdit *lineEdit1 = new QLineEdit();
     QPushButton *button2 = new QPushButton("Two");
     QLineEdit *lineEdit2 = new QLineEdit();
     QPushButton *button3 = new QPushButton("Three");
     QLineEdit *lineEdit3 = new QLineEdit();
 
     QFormLayout *layout = new QFormLayout;
     layout->addRow(button1, lineEdit1);
     layout->addRow(button2, lineEdit2);
     layout->addRow(button3, lineEdit3);
 
     window->setLayout(layout);
     window->show();

Conseils pour l'utilisation des dispositions

Quand vous utilisez une disposition, vous n'avez pas besoin de préciser le parent lors de la construction des widgets enfants. La disposition va automatiquement modifier ce paramètre (en utilisant QWidget::setParent()) de sorte qu'ils deviennent des enfants du widget sur lequel est installée la disposition.

Note. Les widgets contenus dans une disposition sont les enfants du widget sur lequel est installée cette disposition, non directement ceux de la disposition elle-même. Les parents des widgets doivent être d'autres widgets, et non pas des dispositions.

Vous pouvez imbriquer des dispositions en utilisant addLayout() sur une disposition. La disposition intérieure devient alors un enfant de la disposition dans laquelle elle est insérée.

Ajout de widgets à une disposition

Lorsque vous ajoutez un widget dans une disposition, le processus est le suivant :

  1. On alloue initialement à tous les widgets un certain espace en fonction de leur politique de dimensionnement (QWidget::sizePolicy()) et de leur taille recommandée (QWidget::sizeHint()).
  2. Si des facteurs d'étirement ont été appliqués à des widgets avec une valeur supérieure à zéro, alors on leur alloue un espace proportionnel à ce facteur (expliqué ci-dessous).
  3. Si le facteur d'étirement a été configuré à zéro pour certains widgets, on leur allouera plus d'espace seulement si aucun autre widget ne demande cet espace. Cet espace va être alloué en priorité aux widgets dont la politique de dimensionnement est QSizePolicy::Expanding (extensible).
  4. Tout widget auquel est alloué un espace inférieur à sa taille minimum (ou sa taille minimum recommandée si aucune taille minimum n'est spécifiée) va récupérer cet espace qui lui est nécessaire (les widgets n'ont pas forcément de taille minimum ou de taille minimum recommandée ? dans ce cas, c'est leur facteur d'étirement qui est déterminant).
  5. Tout widget auquel est alloué un espace supérieur à sa taille maximum va libérer ce surplus d'espace (les widgets n'ont pas forcément de taille maximum ? dans ce cas, c'est leur facteur d'étirement qui est déterminant).

Facteurs d'étirement

Les widgets sont normalement créés sans qu'aucun facteur d'étirement ne soit configuré. Lorsqu'ils sont placés dans une disposition, ces widgets vont se partager l'espace en respectant la plus grande des deux valeurs entre leur taille minimum recommandée et celle produite par leur politique de dimensionnement (QWidget::sizePolicy()).

Si nous avons trois widgets disposés grâce à un QHBoxLayout sans aucun facteur d'étirement précisé, nous allons obtenir une disposition comme celle-ci :

image Si nous appliquons des facteurs d'étirement à chacun des widgets, ils vont être disposés proportionnellement à ces facteurs (mais jamais moins que leur taille minimum recommandée), par exemple :

image

Widgets personnalisés dans les dispositions

Quand vous créez votre propre classe widget, vous devriez aussi communiquer ses propriétés de disposition. Si le widget utilise une disposition existante de Qt, cela est déjà pris en compte. Si le widget n'a aucun widget enfant ou qu'il utilise une disposition manuelle, vous pouvez changer le comportement du widget en utilisant les mécanismes suivants :

Il faut appeler QWidget::updateGeometry() lorsque la taille recommandée, la taille minimum recommandée ou la politique de dimensionnement changent. Cela va engendrer un réaménagement de la disposition. De multiples appels consécutifs à QWidget::updateGeometry() ne vont causer qu'un seul réaménagement.

Si la hauteur préférée de votre widget dépend de sa largeur réelle (par exemple, une étiquette avec un retour à la ligne automatique), appliquez le drapeau height-for-width à la propriété size policy du widget et réimplémentez QWidget::heightForWidth().

Même si vous implémentez QWidget::heightForWidth(), cela reste une bonne idée de fournir une taille recommandée (QWidget::sizeHint()) raisonnable.

Pour plus de détails sur l'implémentation de ces fonctions, lisez l'article Détermination de la hauteur par rapport à la largeur de Qt Quarterly.

Problèmes des dispositions

L'utilisation de texte enrichi dans un widget étiquette peut apporter quelques problèmes dans l'aménagement de la disposition du widget parent. Ces problèmes surviennent à cause de la manière dont le texte enrichi est géré par les gestionnaires de disposition de Qt lorsque le retour à la ligne automatique est activé dans l'étiquette.

Dans certains cas, la disposition parente est mise en mode de redimensionnement libre (QLayout::SetNoConstraint, signifiant qu'il ne va pas pouvoir adapter la disposition de son contenu lorsque la fenêtre sera trop petite. Il ne pourra même pas prévenir l'utilisateur qu'il a créé une fenêtre trop petite pour être utilisable correctement. Ceci peut être évité en dérivant les classes de widget à problèmes et en implémentant les fonctions appropriées : sizeHint() et minimumSizeHint().

Dans certains cas, le moment où une disposition est ajoutée à un widget est important. Quand vous définissez le widget d'un QDockWidget ou d'une QScrollArea (avec QDockWidget::setWidget() et QScrollArea::setWidget()), la disposition doit obligatoirement avoir déjà été appliquée au widget. Sinon, celui-ci ne sera pas visible.

Disposition manuelle

Si vous souhaitez construire une disposition spéciale, vous pouvez aussi fabriquer un widget personnalisé tel que décrit ci-dessous. Réimplémentez QWidget::resizeEvent() pour calculer la taille requise, et appelez setGeometry() depuis chaque enfant.

Le widget va émettre un événement de type QEvent::LayoutRequest lorsque la disposition doit être recalculée. Réimplémentez QWidget::event() pour gérer les événements QEvent::LayoutRequest.

Comment écrire un gestionnaire de disposition personnalisé

Une variante à la disposition manuelle est d'écrire votre propre gestionnaire de disposition en héritant de QLayout. Les exemples Border Layout et Flow Layout montrent comment l'on peut procéder.

Présentons ici un exemple en détail. La classe CardLayout est inspirée par le gestionnaire de disposition Java du même nom. Il dispose les objets (widgets ou QLayout imbriqués) au-dessus de chacun des éléments, chaque objet étant décalé avec QLayout::spacing().

Pour écrire votre propre disposition, vous devez définir :

  • une structure de données pour stocker les éléments gérés par la disposition. Chaque élément est un QLayoutItem. Nous allons utiliser une QList dans cet exemple ;
  • addItem(), comment ajouter un élément dans la disposition ;
  • setGeometry(), comment représenter la disposition ;
  • sizeHint(), la taille préférée de la disposition ;
  • itemAt(), comment parcourir la disposition ;
  • takeAt(), comment supprimer un élément de la disposition.

Dans la plupart des cas, vous allez aussi implémenter minimumSize().

 #ifndef CARD_H
 #define CARD_H
 
 #include <QtGui>
 #include <QList>
 
 class CardLayout : public QLayout
 {
 public:
     CardLayout(QWidget *parent, int dist): QLayout(parent, 0, dist) {}
     CardLayout(QLayout *parent, int dist): QLayout(parent, dist) {}
     CardLayout(int dist): QLayout(dist) {}
     ~CardLayout();
 
     void addItem(QLayoutItem *item);
     QSize sizeHint() const;
     QSize minimumSize() const;
         QLayoutItem *count() const;
     QLayoutItem *itemAt(int) const;
     QLayoutItem *takeAt(int);
     void setGeometry(const QRect &rect);
 
 private:
     QList<QLayoutItem*> list;
 };
 #endif
 #include "card.h"

Premièrement, nous définissons

count()

qui correspond au nombre d'éléments dans la liste.

 int CardLayout::count() const
 {
         // QList::size() retourne le nombre de QLayoutItems dans la liste
     return list.size();
 }

Maintenant, nous définissons deux fonctions pour naviguer au sein de la disposition : itemAt() et takeAt(). Ces fonctions sont utilisées en interne par le système de disposition pour gérer la suppression de widgets. Elles sont aussi accessibles aux programmeurs.

itemAt() renvoie l'élément à l'index donné. takeAt() supprime l'élément à l'index donné et le retourne. Dans notre cas, nous allons utiliser l'index de la liste en tant qu'index de la disposition. Si nous avions eu à gérer une structure de données plus complexe, nous aurions peut-être eu plus de travail à faire pour définir un ordre linéaire des éléments.

 QLayoutItem *CardLayout::itemAt(int idx) const
 {
     // QList::value() effectue un contrôle de l'index et renvoie 0 si nous sommes en dehors de la plage valide
     return list.value(idx);
 }
 
 QLayoutItem *CardLayout::takeAt(int idx)
 {
     // QList::take ne fait pas de contrôle d'index
     return idx >= 0 && idx < list.size() ? list.takeAt(idx) : 0;
 }

addItem() implémente la stratégie de placement par défaut pour les éléments de la disposition. Cette fonction doit être implémentée. Elle est utilisée par QLayout::add(), par le constructeur de QLayout qui prend un QLayout en tant que parent. Si votre disposition a un système de placement plus avancé qui requiert des paramètres, vous devez fournir des fonctions d'accès supplémentaires, comme des surcharges s'étendant sur plusieurs colonnes et lignes des fonctions QGridLayout::addItem(), QGridLayout::addWidget() et QGridLayout::addLayout().

 void CardLayout::addItem(QLayoutItem *item)
 {
     list.append(item);
 }

La disposition est responsable des éléments ajoutés. Étant donné que QLayoutItem n'hérite pas de QObject, nous devons supprimer les éléments manuellement. Dans le destructeur, nous enlevons donc chaque élément de la liste grâce à takeAt() puis le supprimons ensuite.

 CardLayout::~CardLayout()
 {
      QLayoutItem *item;
      while ((item = takeAt(0)))
          delete item;
 }

La fonction setGeometry() effectue le travail réel de placement. Le rectangle fourni en tant qu'argument n'inclut pas la marge margin(). Le cas échéant, il faut utiliser spacing() en tant que distance entre les éléments.

 void CardLayout::setGeometry(const QRect &r)
 {
     QLayout::setGeometry(r);
 
     if (list.size() == 0)
         return;
 
     int w = r.width() - (list.count() - 1) * spacing();
     int h = r.height() - (list.count() - 1) * spacing();
     int i = 0;
     while (i < list.size()) {
         QLayoutItem *o = list.at(i);
         QRect geom(r.x() + i * spacing(), r.y() + i * spacing(), w, h);
         o->setGeometry(geom);
         ++i;
     }
 }

sizeHint() et minimumSize() sont normalement très similaires dans leur implémentation. La taille retournée par ces deux fonctions devrait inclure spacing() mais pas margin().

 QSize CardLayout::sizeHint() const
 {
     QSize s(0,0);
     int n = list.count();
     if (n > 0)
         s = QSize(100,70); // commence par une taille par défaut correcte
     int i = 0;
     while (i < n) {
         QLayoutItem *o = list.at(i);
         s = s.expandedTo(o->sizeHint());
         ++i;
     }
     return s + n*QSize(spacing(), spacing());
 }
 
 QSize CardLayout::minimumSize() const
 {
     QSize s(0,0);
     int n = list.count();
     int i = 0;
     while (i < n) {
         QLayoutItem *o = list.at(i);
         s = s.expandedTo(o->minimumSize());
         ++i;
     }
     return s + n*QSize(spacing(), spacing());
 }

Notes supplémentaires

  • Cette disposition personnalisée ne gère pas la proportion entre la hauteur et la largeur.
  • Nous avons ignoré QLayoutItem::isEmpty(), ce qui signifie que la disposition va traiter les widgets cachés de la même manière que des widgets visibles.
  • Pour des dispositions plus complexes, la vitesse peut être grandement améliorée en gardant en cache les valeurs calculées. Dans ce cas, implémentez QLayoutItem::invalidate() pour supprimer ou invalider les données en cache.
  • Appeler QLayoutItem::sizeHint(), etc. peut coûter cher. Vous devriez donc stocker la valeur dans une variable locale si vous avez encore besoin d'elle plus tard à l'intérieur de la même fonction.
  • Vous ne devriez pas appeler QLayoutItem::setGeometry() deux fois sur le même élément dans la même fonction. Cet appel peut coûter très cher si l'élément a plusieurs widgets enfants, car le gestionnaire de disposition doit refaire une disposition complète à chaque fois. Calculez plutôt la position d'abord, et appliquez-la ensuite (cela ne s'applique pas seulement aux dispositions, vous devriez faire de même si vous implémentez votre propre resizeEvent(), par exemple).

[ Précédent : Widgets et Layouts ] [ Suivant : Styles ]

Remerciements

Merci à K?vin P?rais pour la traduction, ainsi qu'à Ilya Diallo, David Taralla et Claude Leloup pour la relecture !

Warning: include(): https:// wrapper is disabled in the server configuration by allow_url_include=0 in /home/developpez/www/developpez-com/upload/qt/doc/bs.php on line 4 Warning: include(https://qt.developpez.com/index/rightColumn): failed to open stream: no suitable wrapper could be found in /home/developpez/www/developpez-com/upload/qt/doc/bs.php on line 4 Warning: include(): Failed opening 'https://qt.developpez.com/index/rightColumn' for inclusion (include_path='.:/usr/php53/lib/php') in /home/developpez/www/developpez-com/upload/qt/doc/bs.php on line 4
Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.7
Copyright © 2018 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur, une redirection cassée ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
Responsable bénévole de la rubrique Qt : Thibaut Cuvelier -