Gestion de la dispositionLe 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. IntroductionQt 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 :
Les classes de disposition de QtLes 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.
Les dispositions horizontale, verticale, en grille et en formulaireLe 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.
Positionnement des widgets dans le codeLe 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 dispositionsQuand 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 dispositionLorsque vous ajoutez un widget dans une disposition, le processus est le suivant :
Facteurs d'étirementLes 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 : 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 : Widgets personnalisés dans les dispositionsQuand 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 dispositionsL'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 manuelleSi 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 :
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
[ Précédent : Widgets et Layouts ] [ Suivant : Styles ] RemerciementsMerci à K?vin P?rais pour la traduction, ainsi qu'à Ilya Diallo, David Taralla et Claude Leloup pour la relecture ! |
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 © 2024 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 ? Un bug ? 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 ! |