Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zéro > Les tutoriels > Officiels > Programmation > Apprenez à programmer en C++ ! > [Pratique] Créez vos propres fenêtres avec Qt > Personnaliser les widgets > Lecture du tutoriel

Personnaliser les widgets

Avatar
Auteur : M@teo21
Note : 19 / 20 (15 votes)
Visualisations : 58 107

Plus d'informations Plus d'informations
La "fenêtre-bouton" que nous avons réalisée dans le 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 sur la fenêtre ?
Comment modifier les propriétés du bouton ? 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 des tonnes 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 n'aurez aucun mal à le faire pour les autres widgets.

Enfin et surtout, nous reparlerons d'héritage dans ce chapitre. 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 !

Pimp mon bouton

Allez hop, vous allez me personnaliser ce bouton tout gris !

"Yo man, on va te cus-to-mi-ser ton vieux bouton à la sauce west coast ! Aujourd'hui sur le Site du Zéro, c'est Pimp mon bouton !"
Pardonnez ce petit délire, je promets à l'avenir de ne plus regarder MTV avant de rédiger un tutoriel. Promis promis.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

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 classe QPushButton.

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

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 ! o_O

Hé, mais tu avais parlé d'un truc à un moment je crois... Les accesseurs, c'est pas ça ?

Ah... J'aime les gens qui ont de la mémoire :D
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 (get et set ça vous dit rien ?).


Les accesseurs avec Qt



Justement, les gens qui ont créé Qt chez Trolltech 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 bibliothèque devienne un véritable foutoir !

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


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 à ça, 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%.

Barre de progression


Voyons voir 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 un grand nombre de propriétés éditables pour chaque widget, y compris le bouton. Nous n'allons pas toutes les voir ici, ni même plus tard d'ailleurs, je vous apprendrai à lire la doc 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 présent 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 le texte du bouton dès sa création.

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

Pour chaque attribut, la documentation de Qt nous dit à quoi il sert et quels sont ses accesseurs. Voyez par exemple ce que ça donne pour l'attribut text des boutons.

On vous indique de quel type est l'attribut. Ici, text est de type QString, comme tous les attributs qui stockent du texte avec Qt. En effet, Qt n'utilise pas la classe "string" standard du C++ mais sa propre version de la gestion des chaînes de caractères. En gros, QString c'est un string amélioré.

Puis, on vous explique en quelques mots à quoi sert cet attribut (in english of course, il n'est jamais trop tard pour reprendre des cours d'anglais quel que soit votre âge :p ).

Enfin, on vous indique les accesseurs qui permettent de lire et de modifier l'attribut. Dans le cas présent, il s'agit de :


A la longue, vous ne devriez pas avoir besoin de la doc pour savoir quels sont les accesseurs d'un attribut. Ca suit toujours le même schéma :
attribut() : permet de lire l'attribut.
setAttribut() : permet de modifier l'attribut.


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

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#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();
}

Vous aurez noté que la méthode setText attend un QString et qu'on lui envoie une bête chaîne de caractères entre guillemets. En fait, ça fonctionne comme la classe string : les chaînes de caractères entre guillemets sont automatiquement converties en QString. Heureusement d'ailleurs, sinon ça serait lourd de devoir créer un objet de type QString juste pour ça !

Résultat :

Un bouton dont le texte a été modifié


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

  1. On crée le bouton et on lui donne le texte "Salut les Zéros, la forme ?" à l'aide du constructeur.
  2. On modifie le texte présent 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 :

Code : C++
1
2
3
int x = 1;
x = 2;
cout << x;


... Lorsqu'on affiche x, il vaut 2.
C'est pareil pour le bouton. Au final, c'est le tout dernier texte qui sera 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 ! Bah vous voyez, c'est facile quand on a compris comment Qt était organisé ;)

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#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();
}


Infobulle
Une infobulle


font : la police



Avec la propriété font, les choses se compliquent. En effet, jusqu'ici on avait juste eu à envoyer une chaîne de caractères en paramètres, qui était en fait convertie en objet de type QString.

La propriété font est un peu plus complexe car elle contient 3 informations :


La signature de la méthode setFont est :

void setFont ( const QFont & )

Cela veut dire que setFont attend un objet de type QFont !

Je rappelle, pour ceux qui auraient oublié la signification des symboles, que :
  • const : signifie que l'objet que l'on envoie en paramètre ne sera pas modifié par la fonction
  • & : signifie que la fonction attend une référence vers l'objet. En C, il aurait fallu envoyer un pointeur, mais comme en C++ on dispose des références (qui sont plus simples à utiliser), on en profite :)

Bon, comment on fait pour lui donner un objet de type QFont nous ?
Eh bien c'est simple : il... suffit de créer un objet de type QFont !

La doc nous indique tout ce que nous avons besoin de savoir sur QFont, en particulier les informations qu'il faut donner à son constructeur. Je n'attends pas de vous encore que vous soyez capable de lire la doc de manière autonome, je vais donc vous mâcher le travail (mais profitez-en parce que ça ne durera pas éternellement :pirate: ).

Pour faire simple, le constructeur de QFont attend 4 paramètres. Voici son prototype :

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

En fait, avec Qt il y a rarement un seul constructeur par classe. Les développeurs de Qt profitent des fonctionnalités du C++ et ont donc tendance à beaucoup surcharger les constructeurs. Certaines classes possèdent même plusieurs dizaines de constructeurs différents !
Pour QFont, celui que je vous montre là est néanmoins le principal et le plus utilisé. Et le plus simple aussi, tant qu'à faire.


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 donc nous ne sommes pas obligés de les indiquer.
Dans l'ordre, les paramètres signifient :


On va faire quelques tests. Tout d'abord, il va falloir créer un objet de type QFont :

Code : C++
1
QFont maPolice("Courier");


J'ai appelé cet objet maPolice.
Maintenant, je dois envoyer l'objet maPolice de type QFont à la méthode setFont de mon bouton (suivez, suivez !) :

Code : C++
1
bouton.setFont(maPolice);


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

Code : C++
1
2
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, c'est de définir l'objet de type QFont au moment de l'appel à la méthode setFont. Ca nous évite d'avoir à donner un nom bidon à l'objet comme on l'a fait ici (maPolice), c'est plus court, ça va plus vite, bref c'est mieux en général :p

Code : C++
1
bouton.setFont(QFont("Courier"));


Voilà, en imbriquant comme ça ça marche très bien. La méthode setFont veut un objet de type QFont ? Qu'à cela ne tienne, on lui en crée un à la volée !

Voici le résultat :

Un bouton écrit avec la police Courier


Maintenant, on peut exploiter un peu plus le constructeur de QFont en utilisant une autre police plus fantaisiste et en augmentant la taille des caractères :

Code : C++
1
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 !

Code : C++
1
bouton.setFont(QFont("Comic Sans MS", 20, QFont::Bold, true));


Un bouton en Comic Sans MS en grand, gras, italique


Bref, si vous avez compris le principe des paramètres par défaut (et j'espère que vous avez compris depuis le temps ! :pirate: ), ça ne devrait vous poser aucun problème.

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 d'utiliser une des constantes de curseurs prédéfinis parmi la liste qui s'offre à vous.

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

Code : C++
1
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éthode setIcon attend juste un objet de type QIcon.
Un QIcon peut se construire très facilement en donnant le nom du fichier image à charger.

Prenons par exemple ce petit smiley souriant : :)
Il s'agit d'une image au format PNG que sait lire Qt.

Code : C++
1
bouton.setIcon(QIcon("smile.png"));


Attention, sous Windows pour que cela fonctionne, votre icône smiley.png doit se trouver dans le même dossier que l'exécutable (ou dans un sous-dossier si vous écrivez "dossier/smiley.png").
Sous Linux, il faut que votre icône soit dans votre répertoire HOME. Si vous voulez utiliser le chemin de votre application, comme cela se fait sous Windows par défaut, écrivez :
Code : C++
1
QIcon(QCoreApplication::applicationDirPath() + "/smile.jpg");

Cela aura pour effet d'afficher l'icône à condition que celle-ci se trouve dans le même répertoire que l'exécutable.


Si vous avez fait ce qu'il fallait, l'icône devrait alors apparaître comme ceci :

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", ça ne vous rappelle rien ? J'espère que ça 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, ça 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. Chaque classe "à gauche" de cette liste à puces est une classe de base, et les classes qui sont décalées vers la droite sont des sous-classes.

Vous pouvez par exemple voir au début :



QAbstractExtensionFactory et QAbstractExtensionManager sont des classes dites "de base". Elles n'ont pas de classes parentes.
En revanche, QExtensionFactory et QExtensionManager sont des classes-filles, qui héritent respectivement de QAbstractExtensionFactory et QAbstractExtensionManager.

Sympa hein ? :D

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

C'est bon vous avez pas trop pris peur ? :lol:
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 ? C'est pas possible je 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



QObject est 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 dans QObject une méthode objectName() 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 comme QObject est indispensable pour réaliser le mécanisme des signaux et des slots qu'on verra dans le prochain chapitre. Ce mécanisme permet de faire en sorte par exemple que si un bouton est cliqué, 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. 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 juste mis quelques exemples, mais s'il fallait faire le schéma complet ça prendrait une place énorme vous vous en doutez !

On voit sur ce schéma que QObject est 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 comme QSound (gestion du son) héritent directement de QObject.

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 base QWidget pour tous les widgets. Elle contient énormément de propriétés communes à tous les widgets, comme :


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


Les classes abstraites



Vous avez pu remarquer sur mon schéma que j'ai écrit la classe QAbstractButton en rouge... Pourquoi ?
Il existe en fait un grand nombre de classes abstraites sous Qt, qui contiennent toutes le mot "Abstract" dans leur nom.

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'objet à partir d'elles. Ainsi, on ne peut pas faire :

Code : C++
1
QAbstractButton bouton(); // Interdit car classe abstraite


Mais alors... à quoi ça sert 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, QAbstractButton dé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 :


Bref, encore une fois tout ça n'est défini qu'une fois dans QAbstractButton, et on le retrouve ensuite automatiquement dans QPushButton, QCheckBox, etc.

Dans ce cas, pourquoi QObject et QWidget ne sont pas des classes abstraites elles aussi ? Après tout, elles ne représentent rien de particulier et servent juste 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 type QWidget.

Si on affiche un QWidget, 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, pas très compliquée, 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 (un QWidget) peut contenir 3 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 :

Widgets conteneurs


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


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


Les widgets sont donc imbriqués les uns dans les autres de cette manière :


Attention : ne confondez pas ceci avec l'héritage ! Dans cette partie, je suis en train de vous montrer qu'un widget peut en contenir d'autres. Le gros schéma qu'on a vu un peu plus haut n'a rien à voir avec la notion de widget conteneur.
Ici, on découvre qu'un widget peut en contenir d'autres, indépendamment du fait que ce soit une classe mère ou une classe fille.


Créer une fenêtre contenant un bouton



On ne va pas commencer par faire une fenêtre aussi compliquée que celle que nous venons 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... c'est pas ce qu'on a fait tout le temps jusqu'ici ? o_O

Non, ce qu'on a fait jusqu'ici c'était juste 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 :


Voilà comment il faut faire :

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
#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 "fenetre"
    QPushButton bouton("Pimp mon bouton !", &fenetre);
    // Customisation du bouton
    bouton.setFont(QFont("Comic Sans MS", 14));
    bouton.setCursor(Qt::PointingHandCursor);
    bouton.setIcon(QIcon("smiley.png"));
 
    // Affichage de la fenêtre
    fenetre.show();
 
    return app.exec();
}


... et le résultat :

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 customisé 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 "&fenetre" du constructeur permet donc d'indiquer que la fenêtre est le parent de notre bouton :

Code : C++
1
QPushButton bouton("Pimp mon bouton !", &fenetre);


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

Code : C++
1
bouton.move(60, 50);


Fenêtre avec bouton centré


A noter aussi la méthode setGeometry, qui prend 4 paramètres :

Code : C++
1
bouton.setGeometry(abscisse, ordonnee, largeur, hauteur);


La méthode setGeometry permet donc, en plus de déplacer le widget, de lui donner une dimension 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 pour indiquer quel est le parent.

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

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
#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("smiley.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 ça, mais ça prouve que Qt est très flexible ;)

Des includes "oubliés"



Dans le code source précédent, nous avons utilisé les classes QWidget, QFont et QIcon pour créer des objets.
Normalement, nous devrions faire un include des fichiers headers de ces classes en plus de QPushButton et QApplication pour que le compilateur les connaisse :

Code : C++
1
2
3
4
5
#include <QApplication>
#include <QPushButton>
#include <QWidget>
#include <QFont>
#include <QIcon>


Ah ben oui ! Si on n'a pas inclut le header de la classe QWidget, comment est-ce qu'on a pu créer tout à l'heure un objet "fenetre" de type QWidget sans que le compilateur ne hurle à la mort ?

Coup de bol. En fait, on avait inclut QPushButton. Et comme QPushButton hérite de QWidget, il avait lui-même inclut QWidget dans son header.
Quant à QFont et QIcon, ils étaient inclus eux aussi car indirectement utilisés par QPushButton.

Bref, des fois comme ça ça marche et on a de la chance. Normalement, si on faisait très bien les choses, on devrait faire un include par classe utilisée.

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

Code : C++
1
#include <QtGui>


Le header "QtGui" inclut à son tour toutes les classes du module GUI, donc QWidget, QPushButton, QFont, etc.
Attention toutefois, la compilation sera un peu ralentie du coup.

Hériter un widget

Bon résumons !

Jusqu'ici dans ce chapitre, nous avons :


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 va hériter de QWidget et représenter notre fenêtre. Créer une classe pour gérer la fenêtre va peut-être vous paraître un peu lourd au premier abord, mais c'est pourtant comme ça qu'on fait à chaque fois que l'on crée des GUI en POO. Ca nous donnera une plus grande souplesse par la suite.

L'héritage que l'on va faire sera donc le suivant :

MaFenetre hérite de QWidget


Allons-y :)
Qui dit nouvelle classe dit 2 nouveaux fichiers :


Edition des fichiers



MaFenetre.h



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

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#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 :

Code : C++
1
2
3
4
5
6
#ifndef DEF_MAFENETRE
#define DEF_MAFENETRE
 
// Contenu
 
#endif


Là, nous protégeons le header contre les inclusions infinies grâce à cette bonne vieille méthode du #ifndef.

Code : C++
1
2
3
#include <QApplication>
#include <QWidget>
#include <QPushButton>


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

Code : C++
1
2
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 QWidget" signifie que notre classe hérite de QWidget. Nous récupérons donc automatiquement toutes les propriétés de QWidget.

Code : C++
1
2
3
4
5
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 (MaFenetre()), 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 attribut "m_bouton" de type QPushButton. 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'un "private:" qui interdira les utilisateurs de la classe de modifier directement le bouton.


MaFenetre.cpp



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

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#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("smiley.png"));
    m_bouton->move(60, 50);
}


Quelques explications :

Code : C++
1
#include "MaFenetre.h"


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

Code : C++
1
2
MaFenetre::MaFenetre() : QWidget()
{


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

Code : C++
1
setFixedSize(300, 150);


Rien d'extraordinaire : 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'écrire fenetre.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 à QWidget, et donc qui appartient aussi à notre classe puisqu'on hérite de QWidget ;)

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 mais ça vous paraîtra plus clair à force de pratiquer. Pas de panique donc si vous vous dites "oh mon dieu j'aurais jamais pu deviner ça :o ". Faites-moi confiance c'est tout ^^

Code : C++
1
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 n'avons fait que créer le pointeur, mais il ne pointait vers rien jusqu'ici !
Le new permet d'appeler le constructeur de la classe QPushButton et 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 ça vous paraît inutile maintenant, ça vous sera indispensable plus tard".

Bonne nouvelle : c'est maintenant que vous découvrez un cas où le mot-clé this nous est indispensable ! En effet, le second paramètre du constructeur doit être un pointeur vers le widget parent. Quand nous faisions tout dans le main, 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 main va être tout petit. Ridiculement petit. Microscopique. Microbique même.

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#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 2 headers car nous n'utilisons que 2 classes : QApplication et MaFenetre.

Le contenu du main est très simple : on crée un objet de type MaFenetre, et 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 MaFenetre est 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 QPushButton dans 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 new implique forcément un delete quelque part. Vous avez bien retenu la leçon.
Normalement, on devrait écrire le destructeur de MaFenetre, qui contiendrait ceci :

Code : C++
1
2
3
4
MaFenetre::~MaFenetre()
{
    delete m_bouton;
}

C'est comme ça qu'on doit faire en temps normal. Toutefois, Qt supprimera automatiquement le bouton lors de la destruction de la fenêtre (à la fin du main).
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 le QPushButton avait pour "parent" la fenêtre. Dès qu'on supprime la fenêtre, hop, Qt supprime tout ce qu'elle contient, et donc fait le delete nécessaire du bouton.

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

Compilation



Pour la compilation, il ne faudra pas se contenter de faire un make comme les autres fois ! En effet, qu'est-ce que je vous avais dit ?
"A chaque fois que la liste des fichiers de votre projet change, vous devez refaire qmake -project et qmake pour que le compilateur sache qu'il doit compiler les nouveaux fichiers".

Pensez donc à taper dans l'ordre :

  1. qmake -project
  2. qmake
  3. make

Si vous ne le faites pas, vous aurez une erreur de linker à coup sûr et la compilation échouera.

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 FAIRE LA MÊME CHOSE AU FINAL ??? :colere2:


Mais non mais non :p
En fait, on vient de créer des fondements beaucoup plus solides pour notre fenêtre en faisant ce qu'on vient de faire. 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 ça, vous le découvrirez... dans le prochain chapitre !

Petit exercice : essayez de modifier (ou de surcharger) le constructeur de la classe MaFenetre pour qu'on puisse lui envoyer en paramètre la largeur et la hauteur de la fenêtre à créer.
Ainsi, vous pourrez alors définir les dimensions de la fenêtre lors de sa création dans le main.

Q.C.M.

Si un widget de Qt possède un attribut nommé "flat", comment s'appellera l'accesseur qui permet de le modifier ?
Quel type de variable est accepté en paramètre de la méthode setFont() ?
Quelle est la classe de base dont héritent, à quelques exceptions près, toutes les autres classes de Qt ?
Une classe abstraite est...

Statistiques de réponses au QCM


Nous avançons dans notre découverte de Qt, c'est bien ! :)
Vous commencez à mieux maîtriser le concept de widget et vous avez appris à organiser votre code de manière modulaire afin de servir de base solide pour les chapitres à venir.

Le programme de la suite ? Les signaux et les slots !
Nous allons faire en sorte que notre programme réagisse lorsqu'on clique sur le bouton !
Chapitre précédent Sommaire Chapitre suivant
Retour en haut Retour en haut


Créé : le 18/09/2007 à 17:13:58
Modifié : le 23/10/2008 à 14:17:50
Avancement : 100%
Licence : Copie non autorisée

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | RSS tutoriels | RSS news
Édité par Simple IT SARL : Nous contacter | Notre blog | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 387 Zéros connectés | Requêtes SQL 8 requêtes | Temps de génération de la page : Total (SQL) 0.0637s (0.0436s)