• 50 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 25/03/2019

Personnalisez les widgets

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

La « fenêtre-bouton » que nous avons réalisée au chapitre précédent était un premier pas. Toutefois, nous avons passé plus de temps à expliquer les mécanismes de la compilation qu'à modifier le contenu de la fenêtre.
Par exemple, comment faire pour modifier la taille du bouton ? Comment placer le bouton où on veut dans la fenêtre ?
Comment changer la couleur, le curseur de la souris, la police, l'icône…

Dans ce chapitre, nous allons nous habituer à modifier les propriétés d'un widget : le bouton. Bien sûr, il existe plein d'autres widgets (cases à cocher, listes déroulantes…) mais nous nous concentrerons sur le bouton pour nous habituer à éditer les propriétés d'un widget.
Une fois que vous saurez le faire pour le bouton, vous saurez le faire pour les autres widgets.

Enfin et surtout, nous reparlerons dans ce chapitre d'héritage. Nous apprendrons à créer un widget personnalisé qui « hérite » du bouton. C'est une technique extrêmement courante que l'on retrouve dans toutes les bibliothèques de création de GUI !

Modifier les propriétés d'un widget

Comme tous les éléments d'une fenêtre, on dit que le bouton est un widget.
Avec Qt, on crée un bouton à l'aide de la classeQPushButton.

Comme vous le savez, une classe est constituée de 2 éléments :

  • des attributs : ce sont les « variables » internes de la classe ;

  • des méthodes : ce sont les « fonctions » internes de la classe.

La règle d'encapsulation dit que les utilisateurs de la classe ne doivent pas pouvoir modifier les attributs : ceux-ci doivent donc tous être privés.

Or, je ne sais pas si vous avez remarqué mais nous sommes justement des utilisateurs des classes de Qt. Ce qui veut dire… que nous n'avons pas accès aux attributs puisque ceux-ci sont privés !

Hé, mais tu avais parlé d'un truc à un moment, je crois… Les accesseurs, est-ce que c'est cela ?

Ah… J'aime les gens qui ont de la mémoire ! Effectivement oui, j'avais dit que le créateur d'une classe devait rendre ses attributs privés mais, du coup, proposer des méthodes accesseurs, c'est-à-dire des méthodes permettant de lire et de modifier les attributs de manière sécurisée (getetset, cela ne vous rappelle rien ?).

Les accesseurs avec Qt

Justement, les gens qui ont créé Qt sont des braves gars : ils ont codé proprement en respectant ces règles. Et il valait mieux qu'ils fassent bien les choses s'ils ne voulaient pas que leur immense bibliothèque devienne un véritable bazar !

Du coup, pour chaque propriété d'un widget, on a :

    • Un attribut : il est privé, on ne peut ni le lire ni le modifier directement.

Exemple :text

    • Un accesseur pour le lire : cet accesseur est une méthode constante qui porte le même nom que l'attribut (personnellement j'aurais plutôt mis ungetdevant pour ne pas confondre avec l'attribut, mais bon). Je vous rappelle qu'une méthode constante est une méthode qui s'interdit de modifier les attributs de la classe. Ainsi, vous êtes assurés que la méthode se contente de lire l'attribut et qu'elle ne le modifie pas.

Exemple :text()

    • Un accesseur pour le modifier : c'est une méthode qui se présente sous la formesetAttribut(). Elle modifie la valeur de l'attribut.

Exemple :setText()

Cette technique, même si elle paraît un peu lourde (parce qu'il faut créer 2 méthodes pour chaque attribut) a l'avantage d'être parfaitement sûre. Grâce à cela, Qt peut vérifier que la valeur que vous essayez de donner est valide.
Cela permet d'éviter, par exemple, que vous ne donniez à une barre de progression la valeur « 150% », alors que la valeur d'une barre de progression doit être comprise entre 0 et 100% (figure suivante).

Barre de progression

Voyons sans plus tarder quelques propriétés des boutons que nous pouvons nous amuser à modifier à l'aide des accesseurs.

Quelques exemples de propriétés des boutons

Il existe pour chaque widget, y compris le bouton, un grand nombre de propriétés que l'on peut éditer. Nous n'allons pas toutes les voir ici, ni même plus tard d'ailleurs, je vous apprendrai à lire la documentation pour toutes les découvrir.
Cependant, je tiens à vous montrer les plus intéressantes d'entre elles pour que vous puissiez commencer à vous faire la main et surtout, pour que vous preniez l'habitude d'utiliser les accesseurs de Qt.

text: le texte

Cette propriété est probablement la plus importante : elle permet de modifier le texte figurant sur le bouton.
En général, on définit le texte du bouton au moment de sa création car le constructeur accepte que l'on donne un intitulé dès ce moment-là.

Toutefois, pour une raison ou une autre, vous pourriez être amenés à modifier au cours de l'exécution du programme le texte du bouton. C'est là qu'il devient pratique d'avoir accès à l'attributtextdu bouton via ses accesseurs.

Pour chaque attribut, la documentation de Qt nous dit à quoi il sert et quels sont ses accesseurs.

On vous indique de quel type est l'attribut. Ici,textest de typeQString, comme tous les attributs qui stockent du texte avec Qt. En effet, Qt n'utilise pas la classestringstandard du C++ mais sa propre version de la gestion des chaînes de caractères. En gros,QStringest unstringamélioré.

Puis, on vous explique en quelques mots à quoi sert cet attribut. Enfin, on vous indique les accesseurs qui permettent de lire et modifier l'attribut. Dans le cas présent, il s'agit de :

  • QString text () const: c'est l'accesseur qui permet de lire l'attribut. Il renvoie unQString, ce qui est logique puisque l'attribut est de typeQString. Vous noterez la présence du mot-cléconstqui indique que c'est une méthode constante ne modifiant aucun attribut.

  • void setText ( const QString & text ): c'est l'accesseur qui permet de modifier l'attribut. Il prend un paramètre : le texte que vous voulez mettre sur le bouton.

Essayons donc de modifier le texte du bouton après sa création :

#include <QApplication>
#include <QPushButton>
 
 
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
 
    QPushButton bouton("Salut les Zéros, la forme ?");
    bouton.setText("Pimp mon bouton !");
 
    bouton.show();
 
    return app.exec();
}

Le résultat est présenté à la figure suivante.

Un bouton dont le texte a été modifié

Le résultat n'est peut-être pas très impressionnant mais cela montre bien ce qui se passe :

  1. On crée le bouton et, à l'aide du constructeur, on lui associe le texte « Salut les Zéros, la forme ? » ;

  2. On modifie le texte figurant sur le bouton pour afficher « Pimp mon bouton ! ».

Au final, c'est « Pimp mon bouton ! » qui s'affiche.
Pourquoi ? Parce que le nouveau texte a « écrasé » l'ancien. C'est exactement comme si on faisait :

int x = 1;
x = 2;
cout << x;

Lorsqu'on affichex, il vaut 2.
C'est pareil pour le bouton. Au final, c'est le tout dernier texte qui est affiché.

Bien entendu, ce qu'on vient de faire est complètement inutile : autant donner le bon texte directement au bouton lors de l'appel du constructeur. Toutefois,setText()se révèlera utile plus tard lorsque vous voudrez modifier le contenu du bouton au cours de l'exécution. Par exemple, lorsque l'utilisateur aura donné son nom, le bouton pourra changer de texte pour dire « Bonjour M. Dupont ! ».

toolTip: l'infobulle

Il est courant d'afficher une petite aide sous la forme d'une infobulle qui apparaît lorsqu'on pointe sur un élément avec la souris.

L'infobulle peut afficher un court texte d'aide. On la définit à l'aide de la propriététoolTip.
Pour modifier l'infobulle, la méthode à appeler est donc…setToolTip! Vous voyez, c'est facile quand on a compris comment Qt est organisé !

#include <QApplication>
#include <QPushButton>
 
 
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
 
    QPushButton bouton("Pimp mon bouton !");
    bouton.setToolTip("Texte d'aide");
 
    bouton.show();
 
    return app.exec();
}
Une infobulle
font: la police

Avec la propriétéfont, les choses se compliquent. En effet, jusqu'ici, on a seulement eu à envoyer une chaîne de caractères en paramètre et celle-ci était en fait convertie en objet de typeQString.

La propriétéfontest un peu plus complexe car elle contient trois informations :

  • le nom de la police de caractères utilisée (Times New Roman, Arial, Comic Sans MS…) ;

  • la taille du texte en pixels (12, 16, 18…) ;

  • le style du texte (gras, italique…).

La signature de la méthodesetFontest :

void setFont ( const QFont & )

Cela veut dire quesetFontattend un objet de typeQFont!

Je rappelle, pour ceux qui auraient oublié la signification des symboles, que :

  • const : signifie que l'objet passé en paramètre ne sera pas modifié par la fonction ;

  • & : signifie que la fonction récupère une référence vers l'objet, ce qui lui évite d'avoir à le copier.

Bon, comment fait-on pour lui donner un objet de typeQFont?
Eh bien c'est simple : il… suffit de créer un objet de typeQFont!

La documentation nous indique tout ce que nous avons besoin de savoir surQFont, en particulier les informations qu'il faut fournir à son constructeur. Je n'attends pas encore de vous que vous soyez capables de lire la documentation de manière autonome, je vais donc vous mâcher le travail (mais profitez-en parce que cela ne durera pas éternellement !).

Pour faire simple, le constructeur deQFontattend quatre paramètres. Voici son prototype :

QFont ( const QString & family, int pointSize = -1, int weight = -1, bool italic = false )

Seul le premier argument est obligatoire : il s'agit du nom de la police à utiliser. Les autres, comme vous pouvez le voir, possèdent des valeurs par défaut. Nous ne sommes donc pas obligés de les indiquer.
Dans l'ordre, les paramètres signifient :

  • family: le nom de la police de caractères à utiliser.

  • pointSize: la taille des caractères en pixels.

  • weight: le niveau d'épaisseur du trait (gras). Cette valeur peut être comprise entre 0 et 99 (du plus fin au plus gras). Vous pouvez aussi utiliser la constanteQFont::Boldqui correspond à une épaisseur de 75.

  • italic: un booléen, pour indiquer si le texte doit être affiché en italique ou non.

On va faire quelques tests. Tout d'abord, il faut créer un objet de typeQFont:

QFont maPolice("Courier");

J'ai appelé cet objetmaPolice.
Maintenant, je dois envoyer l'objetmaPolicede typeQFontà la méthodesetFontde mon bouton (suivez, suivez !) :

bouton.setFont(maPolice);

En résumé, j'ai donc dû écrire 2 lignes pour changer la police :

QFont maPolice("Courier");
bouton.setFont(maPolice);

C'est un peu fastidieux. Il existe une solution plus maligne si on ne compte pas se resservir de la police plus tard : elle consiste à définir l'objet de typeQFontau moment de l'appel à la méthodesetFont. Cela nous évite d'avoir à donner un nom bidon à l'objet, comme on l'a fait ici (maPolice), c'est plus court, cela va plus vite, bref c'est mieux en général pour les cas les plus simples comme ici.

bouton.setFont(QFont("Courier"));

Voilà, en imbriquant ainsi, cela marche très bien. La méthodesetFontveut un objet de typeQFont? Qu'à cela ne tienne, on lui en crée un à la volée !

Voici le résultat à la figure suivante.

Un bouton écrit avec la police Courier

Maintenant, on peut exploiter un peu plus le constructeur deQFonten utilisant une autre police plus fantaisiste et en augmentant la taille des caractères (figure suivante) :

bouton.setFont(QFont("Comic Sans MS", 20));
Un bouton en Comic Sans MS en grand

Et voilà le même avec du gras et de l'italique (figure suivante) !

bouton.setFont(QFont("Comic Sans MS", 20, QFont::Bold, true));
Un bouton en Comic Sans MS en grand, gras, italique
cursor: le curseur de la souris

Avec la propriétécursor, vous pouvez déterminer quel curseur de la souris doit s'afficher lorsqu'on pointe sur le bouton.
Le plus simple est de choisir l'une des constantes de curseurs prédéfinies dans la liste qui s'offre à vous.

Ce qui peut donner par exemple, si on veut qu'une main s'affiche (figure suivante) :

bouton.setCursor(Qt::PointingHandCursor);
Curseur de la souris modifié sur le bouton
icon: l'icône du bouton

Après tout ce qu'on vient de voir, rajouter une icône au bouton va vous paraître très simple : la méthodesetIconattend juste un objet de typeQIcon.
UnQIconpeut se construire très facilement en donnant le nom du fichier image à charger.

Prenons par exemple un smiley. Il s'agit d'une image au format PNG que sait lire Qt.

bouton.setIcon(QIcon("smile.png"));

Si vous avez fait ce qu'il fallait, l'icône devrait alors apparaître comme sur la figure suivante.

Un bouton avec une icône

Qt et l'héritage

On aurait pu continuer à faire joujou longtemps avec les propriétés de notre bouton mais il faut savoir s'arrêter au bout d'un moment et reprendre les choses sérieuses.

Quelles choses sérieuses ?
Si je vous dis « héritage », cela ne vous rappelle rien ? J'espère que cela ne vous donne pas des boutons en tout cas (oh oh oh) parce que, si vous n'avez pas compris le principe de l'héritage, vous ne pourrez pas aller plus loin.

De l'héritage en folie

L'héritage est probablement LA notion la plus intéressante de la programmation orientée objet. Le fait de pouvoir créer une classe de base, réutilisée par des sous-classes filles, qui ont elles-mêmes leurs propres sous-classes filles, cela donne à une bibliothèque comme Qt une puissance infinie (voire plus, même).

En fait… quasiment toutes les classes de Qt font appel à l'héritage.

Pour vous faire une idée, la documentation vous donne la hiérarchie complète des classes. Les classes les plus à gauche de cette liste à puces sont les classes de base et toute classe décalée vers la droite est une sous-classe.

Vous pouvez par exemple voir au début :

  • QAbstractExtensionFactory

    • QExtensionFactory

  • QAbstractExtensionManager

    • QExtensionManager

QAbstractExtensionFactoryetQAbstractExtensionManagersont des classes dites « de base ». Elles n'ont pas de classes parentes.
En revanche,QExtensionFactoryetQExtensionManagersont des classes-filles, qui héritent respectivement deQAbstractExtensionFactoryetQAbstractExtensionManager.

Sympathique, n'est-ce pas ?

Descendez plus bas sur la page de la hiérarchie, à la recherche de la classeQObject.
Regardez un peu toutes ses classes filles.
Descendez.
Encore.
Encore.
Encore.

C'est bon vous n'avez pas trop pris peur ?
Vous avez dû voir que certaines classes étaient carrément des sous-sous-sous-sous-sous-classes.

Wouaw, mais comment je vais m'y retrouver là-dedans moi ? Ce n'est pas possible, je ne vais jamais m'en sortir !

C'est ce qu'on a tendance à se dire la première fois. En fait, vous allez petit à petit comprendre qu'au contraire, tous ces héritages sont là pour vous simplifier la vie. Si ce n'était pas aussi bien architecturé, alors vous ne vous en seriez jamais sortis ! ;-)

QObject: une classe de base incontournable

QObjectest la classe de base de tous les objets sous Qt.
QObject ne correspond à rien de particulier mais elle propose quelques fonctionnalités « de base » qui peuvent être utiles à toutes les autres classes.

Cela peut surprendre d'avoir une classe de base qui ne sait rien faire de particulier mais, en fait, c'est ce qui donne beaucoup de puissance à la bibliothèque. Par exemple, il suffit de définir une fois dansQObjectune méthodeobjectName()qui contient le nom de l'objet et ainsi, toutes les autres classes de Qt en héritent et possèderont donc cette méthode.
D'autre part, le fait d'avoir une classe de base commeQObjectest indispensable pour réaliser le mécanisme des signaux et des slots qu'on verra au prochain chapitre. Ce mécanisme permet par exemple de faire en sorte que, si on clique sur un bouton, alors une autre fenêtre s'ouvre (on dit qu'il envoie un signal à un autre objet).

Bref, tout cela doit vous sembler encore un peu abstrait et je le comprends parfaitement.
Je pense qu'un petit schéma simplifié des héritages de Qt s'impose (figure suivante). Cela devrait vous permettre de mieux visualiser la hiérarchie des classes.

Héritage sous Qt

Soyons clairs : je n'ai pas tout mis. J'ai simplement présenté quelques exemples mais, s'il fallait faire le schéma complet, cela prendrait une place énorme, vous vous en doutez !

On voit sur ce schéma queQObjectest la classe mère principale dont héritent toutes les autres classes. Comme je l'ai dit, elle propose quelques fonctionnalités qui se révèlent utiles pour toutes les classes, mais nous ne les verrons pas ici.

Certaines classes commeQSound(gestion du son) héritent directement deQObject.

Toutefois, comme je l'ai dit, on s'intéresse plus particulièrement à la création de GUI, c'est-à-dire de fenêtres. Or dans une fenêtre, tout est considéré comme un widget (même la fenêtre est un widget).
C'est pour cela qu'il existe une classe de baseQWidgetpour tous les widgets. Elle contient énormément de propriétés communes à tous les widgets, comme :

  • la largeur ;

  • la hauteur ;

  • la position en abscisse (x) ;

  • la position en ordonnée (y) ;

  • la police de caractères utilisée (eh oui, la méthodesetFontest définie dansQWidgetet commeQPushButtonen hérite, il possède lui aussi cette méthode) ;

  • le curseur de la souris (pareil, rebelote,setCursorest en fait défini dansQWidgetet non dansQPushButtoncar il est aussi susceptible de servir sur tous les autres widgets) ;

  • l'infobulle (toolTip)

  • etc.

Vous commencez à percevoir un peu l'intérêt de l'héritage ?
Grâce à cette technique, il leur a suffit de définir une fois toutes les propriétés de base des widgets (largeur, hauteur…). Tous les widgets héritent deQWidget, donc ils possèdent toutes ces propriétés. Vous savez donc par exemple que vous pouvez retrouver la méthodesetCursordans la classeQProgressBar.

Les classes abstraites

Vous avez pu remarquer sur mon schéma que j'ai écrit la classeQAbstractButtonen rouge… Pourquoi ?
Il existe en fait un grand nombre de classes abstraites sous Qt, dont le nom contient toujours le mot « Abstract ». Nous avons déjà parlé des classes abstraites dans un chapitre précédent.

Petit rappel pour ceux qui auraient oublié. Les classes dites « abstraites » sont des classes qu'on ne peut pas instancier. C'est-à-dire… qu'on n'a pas le droit de créer d'objets à partir d'elles. Ainsi, on ne peut pas faire :

QAbstractButton bouton; // Interdit car classe abstraite

Mais alors… à quoi cela sert-il de faire une classe si on ne peut pas créer d'objets à partir d'elle ?

Une classe abstraite sert de classe de base pour d'autres sous-classes. Ici,QAbstractButtondéfinit un certain nombre de propriétés communes à tous les types de boutons (boutons classiques, cases à cocher, cases radio…). Par exemple, parmi les propriétés communes on trouve :

  • text: le texte affiché ;

  • icon: l'icône affichée à côté du texte du bouton ;

  • shortcut: le raccourci clavier pour activer le bouton ;

  • down: indique si le bouton est enfoncé ou non ;

  • etc.

Bref, encore une fois, tout cela n'est défini qu'une fois dansQAbstractButtonet on le retrouve ensuite automatiquement dansQPushButton,QCheckBox, etc.

Dans ce cas, pourquoiQObjectetQWidgetne sont-elles pas des classes abstraites elles aussi ? Après tout, elles ne représentent rien de particulier et servent simplement de classes de base !

Oui, vous avez tout à fait raison, leur rôle est d'être des classes de base.
Mais… pour un certain nombre de raisons pratiques (qu'on ne détaillera pas ici), il est possible de les instancier quand même, donc de créer par exemple un objet de typeQWidget.

Si on affiche unQWidget, qu'est-ce qui apparaît ? Une fenêtre !
En fait, un widget qui ne se trouve pas à l'intérieur d'un autre widget est considéré comme une fenêtre. Ce qui explique pourquoi, en l'absence d'autre information, Qt décide de créer une fenêtre.

Un widget peut en contenir un autre

Nous attaquons maintenant une notion importante, mais heureusement assez simple, qui est celle des widgets conteneurs.

Contenant et contenu

Il faut savoir qu'un widget peut en contenir un autre. Par exemple, une fenêtre (unQWidget) peut contenir trois boutons (QPushButton), une case à cocher (QCheckBox), une barre de progression (QProgressBar), etc.

Ce n'est pas là de l'héritage, juste une histoire de contenant et de contenu.
Prenons un exemple, la figure suivante.

Widgets conteneurs

Sur cette capture, la fenêtre contient trois widgets :

  • un bouton OK ;

  • un bouton Annuler ;

  • un conteneur avec des onglets.

Le conteneur avec des onglets est, comme son nom l'indique, un conteneur. Il contient à son tour des widgets :

  • deux boutons ;

  • une case à cocher (checkbox) ;

  • une barre de progression.

Les widgets sont donc imbriqués les uns dans les autres suivant cette hiérarchie :

  • QWidget (la fenêtre)

    • QPushButton

    • QPushButton

    • QTabWidget (le conteneur à onglets)

      • QPushButton

      • QPushButton

      • QCheckBox

      • QProgressBar

Créer une fenêtre contenant un bouton

On ne va pas commencer par faire une fenêtre aussi compliquée que celle qu'on vient de voir. Pour le moment, on va s'entraîner à faire quelque chose de simple : créer une fenêtre qui contient un bouton.

Mais… n'est-ce pas ce qu'on a fait tout le temps jusqu'ici ?

Non, ce qu'on a fait jusqu'ici, c'était simplement afficher un bouton. Automatiquement, Qt a créé une fenêtre autour car on ne peut pas avoir de bouton qui « flotte » seul sur l'écran.

L'avantage de créer une fenêtre puis de mettre un bouton dedans, c'est que :

  • on pourra mettre ultérieurement d'autres widgets à l'intérieur de la fenêtre ;

  • on pourra placer le bouton où on veut dans la fenêtre, avec les dimensions qu'on veut (jusqu'ici, le bouton avait toujours la même taille que la fenêtre).

Voilà comment il faut faire :

#include <QApplication>
#include <QPushButton>
 
 
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
 
    // Création d'un widget qui servira de fenêtre
    QWidget fenetre;
    fenetre.setFixedSize(300, 150);
 
    // Création du bouton, ayant pour parent la "fenêtre"
    QPushButton bouton("Pimp mon bouton !", &fenetre);
    // Personnalisation du bouton
    bouton.setFont(QFont("Comic Sans MS", 14));
    bouton.setCursor(Qt::PointingHandCursor);
    bouton.setIcon(QIcon("smile.png"));
 
    // Affichage de la fenêtre
    fenetre.show();
 
    return app.exec();
}

… et le résultat en figure suivante :

Fenêtre avec bouton

Qu'est-ce qu'on a fait ?

  1. On a créé une fenêtre à l'aide d'un objet de type QWidget.

  2. On a dimensionné notre widget (donc notre fenêtre) avec la méthode setFixedSize. La taille de la fenêtre sera fixée : on ne pourra pas la redimensionner.

  3. On a créé un bouton mais avec cette fois une nouveauté au niveau du constructeur : on a indiqué un pointeur vers le widget parent (en l'occurence la fenêtre).

  4. On a personnalisé un peu le bouton, pour la forme.

  5. On a déclenché l'affichage de la fenêtre (et donc du bouton qu'elle contenait).

Tous les widgets possèdent un constructeur surchargé qui permet d'indiquer quel est le parent du widget que l'on crée. Il suffit de donner un pointeur pour que Qt sache « qui contient qui ».
Le paramètre&fenetredu constructeur permet donc d'indiquer que la fenêtre est le parent de notre bouton :

QPushButton bouton("Pimp mon bouton !", &fenetre);

Si vous voulez placer le bouton ailleurs dans la fenêtre, utilisez la méthodemove:

bouton.move(60, 50);
Fenêtre avec bouton centré

À noter aussi : la méthodesetGeometry, qui prend 4 paramètres :

bouton.setGeometry(abscisse, ordonnee, largeur, hauteur);

La méthodesetGeometrypermet donc, en plus de déplacer le widget, de lui donner des dimensions bien précise.

Tout widget peut en contenir d'autres

… même les boutons !
Quel que soit le widget, son constructeur accepte en dernier paramètre un pointeur vers un autre widget, pointeur qui indique quel est le parent.

On peut faire le test si vous voulez en plaçant un bouton… dans notre bouton !

#include <QApplication>
#include <QPushButton>
 
 
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
 
    QWidget fenetre;
    fenetre.setFixedSize(300, 150);
 
    QPushButton bouton("Pimp mon bouton !", &fenetre);
    bouton.setFont(QFont("Comic Sans MS", 14));
    bouton.setCursor(Qt::PointingHandCursor);
    bouton.setIcon(QIcon("smile.png"));
    bouton.setGeometry(60, 50, 180, 70);
 
    // Création d'un autre bouton ayant pour parent le premier bouton
    QPushButton autreBouton("Autre bouton", &bouton);
    autreBouton.move(30, 15);
 
    fenetre.show();
 
    return app.exec();
}

Résultat : notre bouton est placé à l'intérieur de l'autre bouton !

Un bouton dans un bouton

Cet exemple montre qu'il est donc possible de placer un widget dans n'importe quel autre widget, même un bouton. Bien entendu, comme le montre ma capture d'écran, ce n'est pas très malin de faire cela mais cela prouve que Qt est très flexible.

Desincludes« oubliés »

Dans le code source précédent, nous avons utilisé les classesQWidget,QFontetQIconpour créer des objets.
Normalement, nous devrions faire unincludedes fichiers d'en-tête de ces classes, en plus deQPushButtonetQApplication, pour que le compilateur les connaisse :

#include <QApplication>
#include <QPushButton>
#include <QWidget>
#include <QFont>
#include <QIcon>

Ah ben oui ! Si on n'a pas inclus le header de la classeQWidget, comment est-ce qu'on a pu créer tout à l'heure un objet « fenetre » de typeQWidgetsans que le compilateur ne hurle à la mort ?

C'était un coup de chance. En fait, on avait inclusQPushButtonet commeQPushButtonhérite deQWidget, il avait lui-même inclusQWidgetdans son header.
Quant àQFontetQIcon, ils étaient inclus eux aussi car indirectement utilisés parQPushButton.

Bref, parfois, comme dans ce cas, on a de la chance et cela marche. Normalement, si on faisait très bien les choses, on devrait faire unincludepar classe utilisée.

C'est un peu lourd et il m'arrive d'en oublier. Comme cela marche, en général je ne me pose pas trop de questions.
Toutefois, si vous voulez être sûrs d'inclure une bonne fois pour toutes toutes les classes du module « Qt Widgets », il vous suffit de faire :

#include <QtWidgets>

Le headerQtWidgetsinclut à son tour toutes les classes du module GUI, doncQWidget,QPushButton,QFont, etc.
Attention toutefois car, du coup, la compilation sera un peu ralentie.

Héritez un widget

Bon, résumons !

Jusqu'ici, dans ce chapitre, nous avons :

  • appris à lire et modifier les propriétés d'un widget, en voyant quelques exemples de propriétés des boutons ;

  • découvert de quelle façon étaient architecturées les classes de Qt, avec les multiples héritages ;

  • découvert la notion de widget conteneur (un widget peut en contenir d'autres) et créé, pour nous entraîner, une fenêtre dans laquelle nous avons inséré un bouton.

Nous allons ici aller plus loin dans la personnalisation des widgets en « inventant » un nouveau type de widget. En fait, nous allons créer une nouvelle classe qui hérite deQWidgetet représente notre fenêtre. Créer une classe pour gérer la fenêtre vous paraîtra peut-être un peu lourd au premier abord, mais c'est ainsi qu'on procède à chaque fois que l'on crée des GUI en POO. Cela nous donnera une plus grande souplesse par la suite.

L'héritage que l'on va faire sera donc celui présenté sur la figure suivante.

MaFenetre hérite de QWidget
MaFenetre hérite de QWidget

Allons-y !
Qui dit nouvelle classe dit deux nouveaux fichiers :

  • MaFenetre.h: contiendra la définition de la classe ;

  • MaFenetre.cpp: contiendra l'implémentation des méthodes.

Édition des fichiers

MaFenetre.h

Voici le code du fichierMaFenetre.h, nous allons le commenter tout de suite après :

#ifndef DEF_MAFENETRE
#define DEF_MAFENETRE
 
#include <QApplication>
#include <QWidget>
#include <QPushButton>
 
class MaFenetre : public QWidget // On hérite de QWidget (IMPORTANT)
{
    public:
    MaFenetre();
 
    private:
    QPushButton *m_bouton; 
};
 
#endif

Quelques petites explications :

#ifndef DEF_MAFENETRE
#define DEF_MAFENETRE
 
// Contenu
 
#endif

Ces lignes permettent d'éviter que le header ne soit inclus plusieurs fois, ce qui pourrait provoquer des erreurs.

#include <QApplication>
#include <QWidget>
#include <QPushButton>

Comme nous allons hériter deQWidget, il est nécessaire d'inclure la définition de cette classe.
Par ailleurs, nous allons utiliser unQPushButton, donc on inclut aussi le header associé.
Quant àQApplication, on ne l'utilise pas ici mais on en aura besoin au prochain chapitre, je prépare un peu le terrain.

class MaFenetre : public QWidget // On hérite de QWidget (IMPORTANT)
{

C'est le début de la définition de la classe. Si vous vous souvenez de l'héritage, ce que j'ai fait là ne devrait pas trop vous choquer. Le: public QWidgetsignifie que notre classe hérite deQWidget. Nous récupérons donc automatiquement toutes les propriétés deQWidget.

public:
MaFenetre();
 
private:
QPushButton *m_bouton;

Le contenu de la classe est très simple.

Nous écrivons le prototype du constructeur : c'est un prototype minimal, mais cela nous suffira. Le constructeur est public car, s'il était privé, on ne pourrait jamais créer d'objet à partir de cette classe

Nous créons un attributm_boutonde typeQPushButton. Notez que celui-ci est un pointeur, il faudra donc le « construire » de manière dynamique avec l'aide du mot-clénew. Tous les attributs devant être privés, nous avons fait précéder cette ligne d'unprivate:qui interdira aux utilisateurs de la classe de modifier directement le bouton.

MaFenetre.cpp

Le fichier.cppcontient l'implémentation des méthodes de la classe. Comme notre classe ne contient qu'une méthode (le constructeur), le fichier.cppne sera donc pas long à écrire :

#include "MaFenetre.h"
 
MaFenetre::MaFenetre() : QWidget()
{
    setFixedSize(300, 150);
 
    // Construction du bouton
    m_bouton = new QPushButton("Pimp mon bouton !", this);
 
    m_bouton->setFont(QFont("Comic Sans MS", 14));
    m_bouton->setCursor(Qt::PointingHandCursor);
    m_bouton->setIcon(QIcon("smile.png"));
    m_bouton->move(60, 50);
}

Quelques explications :

#include "MaFenetre.h"

C'est obligatoire pour inclure les définitions de la classe.
Tout cela ne devrait pas être nouveau pour vous, nous avons fait cela de nombreuses fois déjà dans la partie précédente du cours.

MaFenetre::MaFenetre() : QWidget()
{

L'en-tête du constructeur. Il ne faut pas oublier de le faire précéder d'unMaFenetre::pour que le compilateur sache à quelle classe celui-ci se rapporte.
Le: QWidget()sert à appeler le constructeur deQWidgeten premier lieu. Parfois, on en profitera pour envoyer au constructeur deQWidgetquelques paramètres mais là, on va se contenter du constructeur par défaut.

setFixedSize(300, 150);

Rien d'extraordinaire dans ce morceau de code : on définit la taille de la fenêtre de manière fixée, pour interdire son redimensionnement.
Vous noterez qu'on n'a pas eu besoin d'écrirefenetre.setFixedSize(300, 150);. Pourquoi ? Parce qu'on est dans la classe. On ne fait qu'appeler une des méthodes de la classe (setFixedSize), méthode qui appartient àQWidgetet qui appartient donc aussi à la classe puisqu'on hérite deQWidget.

J'avoue, j'avoue, ce n'est pas évident de bien se repérer au début. Pourtant, vous pouvez me croire, tout ceci est logique et vous paraîtra plus clair à force de pratiquer. Pas de panique donc si vous vous dites « oh mon dieu je n'aurais jamais pu deviner cela ». Faites-moi confiance !

m_bouton = new QPushButton("Pimp mon bouton !", this);

C'est la ligne la plus délicate de ce constructeur.
Ici, nous construisons le bouton. En effet, dans le header, nous nous sommes contentés de créer le pointeur mais il ne pointait vers rien jusqu'ici !
Le newpermet d'appeler le constructeur de la classe QPushButtonet d'affecter une adresse au pointeur.

Autre détail un tout petit peu délicat : le mot-clé this. Je vous en avais parlé dans la partie précédente du cours, en vous disant « faites-moi confiance, même si cela vous paraît inutile maintenant, cela vous sera indispensable plus tard ».

Bonne nouvelle : c'est maintenant que vous découvrez un cas où le mot-clé thisnous est indispensable ! En effet, le second paramètre du constructeur doit être un pointeur vers le widget parent. Quand nous faisions tout dans lemain, c'était simple : il suffisait de donner le pointeur vers l'objet fenetre. Mais là, nous sommes dans la fenêtre ! En effet, nous écrivons la classe MaFenetre. C'est donc « moi », la fenêtre, qui sers de widget parent. Pour donner le pointeur vers moi, il suffit d'écrire le mot-clé this.

Et toujours…main.cpp

Bien entendu, que serait un programme sans son main?
Ne l'oublions pas celui-là !

La bonne nouvelle c'est que, comme bien souvent dans les gros programmes, notre mainva être tout petit. Ridiculement petit. Microscopique. Microbique même.

#include <QApplication>
#include "MaFenetre.h"
 
 
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
 
    MaFenetre fenetre;
    fenetre.show();
 
    return app.exec();
}

On n'a besoin d'inclure que deux headers car on n'utilise que deux classes :QApplicationetMaFenetre.

Le contenu du mainest très simple : on crée un objet de type MaFenetreet on l'affiche par un appel à la méthode show(). C'est tout.

Lors de la création de l'objet fenetre, le constructeur de la classe MaFenetreest appelé. Dans son constructeur, la fenêtre définit toute seule ses dimensions et les widgets qu'elle contient (en l'occurence, juste un bouton).

La destruction automatique des widgets enfants

Minute papillon ! On a créé dynamiquement un objet de type QPushButtondans le constructeur de la classe MaFenetre… mais on n'a pas détruit cet objet avec un delete!

En effet, tout objet créé dynamiquement avec un newimplique forcément un deletequelque part. Vous avez bien retenu la leçon.
Normalement, on devrait écrire le destructeur de MaFenetre, qui contiendrait ceci :

MaFenetre::~MaFenetre()
{
    delete m_bouton;
}

C'est comme cela qu'on doit faire en temps normal. Toutefois, Qt supprimera automatiquement le bouton lors de la destruction de la fenêtre (à la fin dumain).
En effet, quand on supprime un widget parent (ici notre fenêtre), Qt supprime automatiquement tous les widgets qui se trouvent à l'intérieur (tous les widgets enfants). C'est un des avantages d'avoir dit que leQPushButtonavait pour « parent » la fenêtre. Dès qu'on supprime la fenêtre, hop, Qt supprime tout ce qu'elle contient et donc, fait ledeletenécessaire du bouton.

Qt nous simplifie la vie en nous évitant d'avoir à écrire tous lesdeletedes widgets enfants. N'oubliez pas, néanmoins, que toutnewimplique normalement un delete. Ici, on profite du fait que Qt le fait pour nous.

Compilation

Le résultat, si tout va bien, devrait être le même que tout à l'heure :

Fenêtre avec bouton centré

Quoi ? Tout ce bazar pour la même chose au final ???

Mais non mais non !
En fait, on vient de créer des fondements beaucoup plus solides pour notre fenêtre. On a déjà un peu plus découpé notre code (et avoir un code modulaire, c'est bien !) et on pourra par la suite plus facilement rajouter de nouveaux widgets et surtout… gérer les évènements des widgets !

Mais tout cela, vous le découvrirez… au prochain chapitre !

En résumé

  • Qt fournit des accesseurs pour lire et modifier les propriétés des widgets. Ainsi,text()renvoie la valeur de l'attributtextetsetText()permet de la modifier.

  • La documentation nous donne toutes les informations dont nous avons besoin pour savoir comment modifier les propriétés de chaque widget.

  • Les classes de Qt utilisent intensivement l'héritage. Toutes les classes, à quelques exceptions près, héritent en réalité d'une super-classe appeléeQObject.

  • On peut placer un widget à l'intérieur d'un autre widget : on parle alors de widgets conteneurs.

  • Pour être le plus souple possible, il est recommandé de créer sa propre classe représentant une fenêtre. Cette classe héritera d'une classe de Qt commeQWidgetpour récupérer toutes les fonctionnalités de base Qt.

Exemple de certificat de réussite
Exemple de certificat de réussite