Le Gui Avec QT La Suite
Le Gui Avec QT La Suite
Le Gui Avec QT La Suite
suite
Par Amnell
et Olivier Strebler (shareman)
www.openclassrooms.com
2/69
Sommaire
Sommaire ...........................................................................................................................................
Partager ..............................................................................................................................................
Le GUI avec Qt, la suite .....................................................................................................................
Partie 1 : Notions avances ................................................................................................................
2
2
4
5
16
16
20
23
23
24
24
24
25
25
45
45
46
47
48
48
49
50
50
50
50
50
www.openclassrooms.com
55
55
56
56
56
Sommaire
3/69
Le positionnement .....................................................................................................................................................................................................
La suppression ..........................................................................................................................................................................................................
La Z-Value .................................................................................................................................................................................................................
La rotation .................................................................................................................................................................................................................
58
59
59
60
www.openclassrooms.com
61
63
63
63
67
Sommaire
4/69
Par
Amnell
Ce cours est la suite de la partie Crez vos propres fentres avec Qt du tutoriel
officiel de M@teo21 sur le C++
Il est ncessaire d'avoir lu au moins jusqu'au chapitre :
L'architecture MVC avec les widgets complexes
Le framework Qt propose encore normment de fonctionnalits et de classes trs intressantes que vous
n'avez pas encore appris matriser. Ainsi, si vous voulez dvelopper des programmes robustes et
complets ou tout simplement des programmes "de pros", il vous faut tudier Qt sous toutes ses facettes.
Comme tous les cours, le tutoriel de M@teo21 n'est pas exhaustif, reste superficiel (ce qui est une bonne
chose ici) et donc vous enseigne les bases de la programmation GUI avec Qt. Ces bases sont
indispensables.
Le but de ce tutoriel est d'aller encore plus loin et d'enrichir vos connaissances. Ce cours vient donc complter (et approfondir)
celui de M@teo21. Nous aborderons des notions plus pousses comme la gestion avance des principaux widgets, la gestion
des donnes et sans oublier les nouvelles notions comme la 2D gre par Qt !
Histoire de vous maintenir en haleine, au menu de ce tuto : accs aux bases de donnes, gestion adapte des donnes, gestion
de la date, de l'heure et du chrono, etc.
Comme c'est un peu le cas pour tous les tutoriels sur un sujet aussi vaste que Qt, notre objectif n'est pas d'tre exhaustif, chose
qui serait impossible moins d'y passer un temps fou. Il faudra donc de temps en temps aller chercher les informations dont
vous avez besoin dans la fameuse documentation Qt.
Dans ce cours, nous tenterons dans la mesure du possible de prsenter les nouvelles notions en partant de zr0. prsent, je
vous souhaite tous une bonne lecture !
www.openclassrooms.com
5/69
C'est ce que m'a un jour demand un zro lorsque je dbutais dans la programmation avec Qt.
Sur le coup, je me suis dit que je ne savais pas grand chose, mis part comment les dfinir et comment les placer dans une
fentre, ainsi que s'en servir avec un slot nomm ouvrir_Dialogue si vous voyez de quoi je veux parler.
Dit autrement, on croit souvent savoir normment de choses mais par moment, ce n'est pas le cas. Je ne suis pas pass au
travers de cela. Logiquement, ce chapitre aura pour objectif de renforcer vos connaissances en la matire des QPushButton.
Comme un peu de rvision n'a jamais fait de mal personne (enfin, je crois :-) nous allons donc rcapituler ici tout ce que nous
savons des QPushButton, maintenant que nous avons fini de travailler sur le tuto de M@teo21.
Je ne prendrai mme pas la peine de vous expliquer ce code puisque vous le connaissez dj...
Il est tout de mme peut-tre ncessaire, bien que j'en doute, de vous rappeler que le this sert voir apparatre le bouton dans
votre fentre, dans le cas o le widget concern par une telle dclaration ne serait pas englob par un autre widget qui aurait reu
cela (comme un layout par exemple).
Pour placer votre bouton, on se sert de move(abscisse, ordonnee); et pour le placer et le dimensionner, on se sert de
setGeometry(abscisse, ordonnee, largeur, hauteur); , et ce de la manire suivante :
Code : C++
nomDuBouton->setGeometry(abscisse, ordonnee, largeur, hauteur); //
www.openclassrooms.com
6/69
Comme autres types de placement, vous devez aussi connatre les layouts. Vous savez galement vous servir de show(), qu'on
a tous vu au dbut du tuto sur Qt.
Eh bien jusque l c'tait une pure perte de temps de la rigolade pour vous.
setEnabled()
La fonction setEnabled() est assez importante. Pour tre franc, c'est de celle-ci que dpendent en partie les autres
fonctions, les signaux en particuliers. Pour tre plus prcis, setEnabled est une fonction de QWidget : QPushButton hrite
donc de cette fonction.
setEnabled(true)
Imaginons que nous avons cr un projet avec dedans uniquement un main.cpp pour simplifier les choses.
Nous y mettons ce code pour faire en sorte que notre bouton soit utilisable :
Code : C++
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton *bouton = new QPushButton("Exemple de bouton utilisant
la fonction setEnabled(true)");
bouton->setEnabled(true);
bouton->show();
return app.exec();
}
www.openclassrooms.com
7/69
setEnabled(false)
Dans un autre cas, nous avons un QGroupBox cochable contenant notre bouton. Nous faisons en sorte qu'il ne soit pas coch
et donc que le bouton ne soit pas utilisable (on va faire simple pour ne pas s'embarrasser avec des slots et on utilise la mme
fonction pour le bouton et pour le QGroupBox) :
Code : C++
#include <QApplication>
#include <QtGui>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget fenetre;
QGroupBox *groupe = new QGroupBox("Exemple de groupe non coch");
groupe->setCheckable(true);
groupe->setChecked(false);
groupe->setEnabled(false);
QPushButton *bouton = new QPushButton("Exemple de bouton utilisant
la fonction setEnabled(false)");
bouton->setEnabled(false);
QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(bouton);
groupe->setLayout(vbox);
QVBoxLayout *vboxPrincipal = new QVBoxLayout(&fenetre);
vboxPrincipal->addWidget(groupe);
fenetre.show();
return app.exec();
Et voici le rsultat :
En bref, vous souhaitez que votre bouton soit utilisable, utilisez setEnabled(true) , bien que dans le cas d'une utilisation
toute simple comme ici, ce ne soit pas utile de mettre ce code. Si vous souhaitez qu'il ne le soit pas, utilisez
setEnabled(false) .
setFlat()
www.openclassrooms.com
8/69
setFlat() est une fonction permettant de retirer les bordures d'un bouton, laissant visible le texte seulement.
Pour setFlat(true), le bouton n'aura pas de bordures, tandis que pour false, il s'affichera normalement.
Voici un exemple de code illustrant ce que je viens de dire :
Code : C++
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton *bouton = new QPushButton("Exemple de bouton utilisant
la fonction setFlat(true)");
bouton->setFlat(true);
bouton->show();
return app.exec();
}
Et le rsultat :
Il y a tout de mme un dfaut : si on veut vraiment planquer le bouton, il faut dsactiver l'animation car lorsqu'on clique sur le
bouton, on le voit apparatre pour s'animer. Nous allons voir tout de suite cette fonction qui est la suivante.
setUpdatesEnabled()
setFlat() servait rendre le bouton transparent mais, d'un point de vue esthtique, voir un bouton transparent apparatre
pour s'enfoncer et se relcher puis disparatre n'est pas trs professionnel.
Heureusement, setUpdatesEnabled() existe.
Nous allons donc immdiatement corriger ce petit dfaut avec cette fonction mais juste avant, j'ai une petite remarque vous
faire ce sujet :
setUpdatesEnabled() peut tre utilis par presque tous les widgets, tout comme setEnabled() , que nous
venons de voir, et setCheckable() que je ne vais pas tarder vous expliquer.
www.openclassrooms.com
9/69
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton *bouton = new QPushButton("Exemple de bouton utilisant
la fonction setFlat(true)");
bouton->setFlat(true); // Cette ligne n'est pas vraiment utile...
bouton->setUpdatesEnabled(false); // ... celle-ci ayant une
efficacit plus ample.
bouton->show();
return app.exec();
}
C'est tout !
Testez le code, cliquez sur le bouton. Que se passe-t-il ? Rien. Absolument rien. Le bouton n'a mme pas bronch !
C'est parfait tout a...
a vous intresserait une dernire info ? Pas cher !
Eh bien, pour vous donner une petite astuce, on peut facilement planquer un bouton sur une image (une carte par exemple) pour
faire croire l'utilisateur qu'il clique sur l'image et non sur ce bouton dont il ne connat mme pas l'existence.
Eh oui, tous les coups sont permis dans la programmation.
Mais
chuu
ut
setCheckable()
setCheckable() ... encore setCheckable() vous devez dj la connatre cette petite bte !
Eh bien oui, vous en avez srement dj entendu parler, entre ZeroClassGenerator, la Fentre principale avec les actions des
menus, etc.
Mais le saviez-vous ?
On peut aussi s'en servir pour les boutons pour pouvoir les laisser enfoncs aprs un clic dessus.
Bref, c'est comme les autres fonctions, on met true si on veut que ce soit possible ou false si on n'en veut pas ! Mais dans
ce cas, ne perdez pas votre temps crire une ligne, c'est automatiquement false si vous ne mettez rien.
Je n'entre pas plus dans les dtails...
setChecked()
Cette fonction est associe la prcdente et elle sert faire en sorte que le bouton soit coch ou non.
On met true pour qu'il soit coch et false pour qu'il ne le soit pas.
Il est possible de faire en sorte que le bouton soit enfonc mais que l'utilisateur ne puisse pas le dcocher ou cliquer
dessus : ce sont les fonctions setCheckable() et setEnabled() qui sont utilises dans ce cas de figure.
www.openclassrooms.com
10/69
toggled(bool) est un signal qui est envoy quand le bouton est coch.
Il a tout de mme besoin d'une chose pour son activation : que le bouton soit cochable (donc que setCheckable() soit
true ).
Voici un exemple de connexion l'aide de ce signal :
Code : C++
connect(bouton, SIGNAL(toggled(bool)), this, SLOT(ouvrirFenetre()));
Mais en quoi ce slot pourrait-il nous servir ? Eh bien vous avez cr un jeu avec une option "afficher le graphisme" par exemple.
Pour une raison x, vous avez dcid de mettre un bouton cochable et vous voulez faire en sorte que le graphisme soit activ ds
que l'utilisateur coche le bouton. Dans ce cas prcis, vous aurez besoin de ce slot, pratique, non ?
hide() et show()
hide() et show() sont des slots permettant de masquer et d'afficher un widget (comme un QPushButton ou un QLabel par
exemple). Ces fonctions sont quivalentes setVisible(false) pour hide() et setVisible(true) pour
show() .
Voici les utilisations les plus courantes de ces deux slots :
Code : C++ - Utilisation courante : la connexion
/*
On a un QPushButton *bouton et un QLabel *label dj crs...
*/
QObject::connect(bouton, SIGNAL(clicked()), label, SLOT(hide())); //
On peut aussi mettre show()
Une seule petite remarque faire pour show() : si vous l'utilisez normalement, il ouvrira une nouvelle fentre.
www.openclassrooms.com
11/69
Des cas plus rares d'utilisations s'ajoutent. Je vous en dtaillerai un en particulier si vous voulez bien vous donner la peine de
passer la sous-partie suivante de ce chapitre sur les QPushButton.
Dcortiquons ce code :
void QPushButton : c'est assez normal comme code mais il y a un petit sous-entendu : il faut crer un
QPushButton. Eh oui, tout ne se fera pas d'un claquement de doigts que je sache !
setMenu : c'est tout aussi logique, c'est assez similaire au "addMenu()" des menus mais a ne marche pas de la mme
manire...
( QMenu * menu ) : ce bout de code demande de manire implicite ce qu'un menu soit dj dfini.
Maintenant que je vous ai expliqu tout cela, vous devez tre capable de crer un bouton-menu tout seul.
Non, non, je ne plaisante pas, vous devez en tre capable !
Vous avez 5 minutes montre en main !
.....
....
...
C'est termin, posez vos claviers !
www.openclassrooms.com
12/69
return app.exec();
Dans le FenPrincipale.h :
Code : C++
#ifndef HEADER_FENPRINCIPALE
#define HEADER_FENPRINCIPALE
#include <QtGui>
class FenPrincipale : public QMainWindow
{
Q_OBJECT // On le met pour la suite
public:
FenPrincipale();
public slots:
private:
};
#endif
www.openclassrooms.com
13/69
FenPrincipale::FenPrincipale()
{
setFixedSize(210, 60);
QMenu *menuFichier = menuBar()->addMenu("&Fichier");
QPushButton *boutonFichier = new QPushButton("Fichier", this);
boutonFichier->setGeometry(5, 25, 200, 30);
}
On compile et sous vos yeux bahis, une fentre s'ouvre... pas ! Eh bien non vous avez oubli de mettre les .dll : dommage, hein
?
Ce n'est pas un problme, on les met (du moins, les utilisateurs d'un OS les ncessitant) et cette fois-ci, la fentre s'ouvre pour de
bon :
www.openclassrooms.com
14/69
Joli non ?
P our info, je n'ai pas fait exprs de mettre "Quitter" en transparent : mon outil capture d'cran fait des siennes mais le rsultat est beau, c'est le principal...
www.openclassrooms.com
15/69
menuFichier->setVisible(false);
QPushButton *boutonFichier = new QPushButton("Fichier", this);
boutonFichier->setMenu(menuFichier);
boutonFichier->setGeometry(5, 25, 200, 30);
}
Non, plus srieusement, il existe un moyen bien plus simple : il s'agit de crer nos menus sans les mettre dans une QMenuBar et
de les associer au boutons-menus. Je vous fais confiance et je vous laisse effectuer ces petits changements trs simples. Sachez
qu'il est aussi possible de crer des actions (QAction donc) et de les associer notre bouton-menu.
Maintenant, le QCM !
C'est fini pour ce chapitre : vous tes tous devenus des experts en matire de boutons !
Si le zro dont je parlais en dbut de chapitre vient vous poser la mme question qu' moi il y a longtemps, vous saurez quoi lui
rpondre maintenant que nous avons bien travaill.
Je vous dis donc bientt (ou tout de suite si vous souhaitez continuer maintenant) dans la suite du cours !
www.openclassrooms.com
16/69
En gros, supposons qu'il nous servira stocker les rsultats du jeu du pendu. Au fil de notre programme, le QVector (qui est une
sorte de tableau ressemblant fortement std::vector, mais en version Qt) change de taille, de contenu, etc. Toutes ces
modifications concernent le tableau mais malheureusement pour nous, ce dernier est stock dans la RAM. En clair, il vous sera
impossible de retrouver vos rsultats dans une autre instance du programme (bah oui). C'est idiot mais c'est ainsi, ce n'est pas
fait pour cela. Mais en ralit, ce n'est pas si idiot que a, la mmoire vive a, elle aussi, une utilit, comme toute chose : elle
acclre l'excution du programme qui lit les donnes qui y sont stockes.
Mais comment faut-il s'y prendre pour stocker les donnes de manire permanente ? Et c'est l que le disque dur entre en jeu. Le
disque dur (ou HDD pour hard drive disk en anglais) permet de stocker les donnes de manire permanente, si l'on veut y
supprimer quelque chose, il faut le faire manuellement et explicitement (vous connaissez : clic droit puis "supprimer"). C'est bien
beau, mais comment accder cet espace de stockage partir de notre programme ? Comment y lire les donnes ou y crire ? Ce
qu'il faut savoir, c'est que les OS organisent le disque dur (en premier lieu en partitions d'accord) en fichiers. Un fichier sur le
disque dur est comparable un objet sur la RAM : c'est un bout de mmoire o l'on stocke ses donnes. Je pourrais vous citer
par exemple un fichier texte, qui sert stocker... des chanes de caractres.
Pour satisfaire nos attentes, les programmeurs de Qt ont cr la classe QFile. C'est la classe indispensable pour manipuler les
fichiers et dans ce tutoriel, je vais justement vous apprendre l'utiliser correctement. C'est parti !
Anticipons. Il faut savoir que l'on rcupre le contenu d'un fichier le plus couramment dans une instance de QString (mais dans
certains cas plus rares, celui de la manipulation des fichiers binaires, l'aide de QByteArray). Certains programmeurs passent
mme parfois par un objet flux QTextStream, mais on peut s'en passer (cela dit, je vous prsenterai les deux mthodes).
Commenons par la mthode la plus simple qui consiste passer par un QString uniquement. On va tout de mme dj inclure
QTextStream afin de l'utiliser plus tard. Les inclusions ncessaires sont donc :
Code : C++
#include <QApplication>
www.openclassrooms.com
17/69
).
Tel que prvu, j'utiliserai une simple fentre avec un QTextEdit pour afficher le contenu du fichier. Voici donc la structure du main
:
Code : C++
int main(int argc, char **argv)
{
QApplication a(argc, argv);
QTextEdit zoneTexte;
zoneTexte.setGeometry(100,100,400,200);
zoneTexte.setReadOnly(true);
QString texte;
// [CODE]
zoneTexte.setText(texte);
zoneTexte.show();
return a.exec();
la ligne 4, je dclare le fameux QTextEdit. la ligne 5, je le redimensionne en lui donnant des dimensions raisonnables tout en
prenant garde ce que la fentre ne colle pas au bord de votre cran lors de son ouverture. N'oublions pas la ligne 6 qui
paramtre la zone de texte de telle sorte qu'on ne puisse rien y modifier directement. la ligne 8, je dclare un objet QString qui
va se charger de stocker le texte du fichier (on va voir cela dans un instant) ou un message d'erreur si le fichier n'a pu tre ouvert.
Enfin, on place la ligne 10 tout le code concernant l'ouverture et la rcupration des donnes.
Ce que nous allons faire prsent, c'est dclarer un objet QFile en lui prcisant le chemin du fichier (code placer la ligne 10) :
Code : C++
QFile fichier("poeme.txt");
Dans le dossier de l'excutable, j'ai donc un fichier que j'aimerais lire partir de la fentre. En dclarant ainsi un objet QFile, le
fichier reste ferm. Pour l'ouverture, on utilisera la fonction QFile::open() et on enverra en paramtre un flag de QIODevice
pour dfinir le mode d'ouverture. Il en existe huit :
Constante
Valeur correspondante
Description
QIODevice::ReadOnly
0x0001
QIODevice::WriteOnly
0x0002
QIODevice::ReadWrite
0x0001 | 0x0002
Ouverture en lecture/criture.
QIODevice::Truncate
0x0008
www.openclassrooms.com
18/69
QIODevice::Truncate
0x0008
QIODevice::Append
0x0004
QIODevice::Text
0x0010
QIODevice::NotOpen
0x0000
QIODevice::Unbuffered
0x0020
La fonction open revoie true si le fichier a pu tre ouvert et dans le cas contraire, false. Nous allons prsent ouvrir le
fichier poeme.txt en lecture seule. Pour cela, nous allons utiliser une structure if - else en attribuant un code spcifique pour
chaque valeur de open() retourne. On obtient ceci :
Code : C++
if(fichier.open(QIODevice::ReadOnly)) //...
else //...
Supposons prsent que le fichier ait pu tre ouvert, il faut pouvoir rcuprer son contenu avant de fermer le fichier l'aide de la
fonction membre close(). La mthode la plus simple consiste tout simplement utiliser la fonction readAll() et stocker
la chane renvoye dans le QString (prcdemment nomm "texte"). Il faut aussi prvoir le coup si jamais le fichier n'a pas pu tre
ouvert : nous allons affecter au QString texte un message d'erreur. On obtient le code suivant :
Code : C++
if(fichier.open(QIODevice::ReadOnly | QIODevice::Text))
{
texte = fichier.readAll();
fichier.close();
}
else texte = "Impossible d'ouvrir le fichier !";
www.openclassrooms.com
19/69
texte = fichier.readAll();
fichier.close();
}
else texte = "Impossible d'ouvrir le fichier !";
zoneTexte.setText(texte);
zoneTexte.show();
return a.exec();
Si tout se passe bien, lorsque vous compilez, vous devriez vous retrouver avec la fentre suivante (avec le texte de votre fichier)
:
Si par contre, le fichier n'a pas pu tre ouvert, vous aurez le message d'erreur l'cran.
Il existe encore un autre moyen plus prcis de rcuprer le contenu d'un fichier. C'est cette mthode qui est utilise titre
d'exemple dans la doc Qt. Il s'agit cette fois de passer par un objet flux de type QTextStream en procdant ainsi :
Code : C++
if(fichier.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream flux(&fichier);
while(!flux.atEnd())
texte += flux.readLine();
fichier.close();
}
Ici, on dclare un objet flux QTextStream en lui envoyant l'adresse de l'objet QFile "fichier" en lui permettant ainsi de lire son
contenu. Tant qu'on n'est pas arriv au bout du fichier, on continue rcuprer les lignes. Essayez une fois de compiler ce code
et... surprise ! Cela fonctionne, mais les lignes ont t mises la chane sans le moindre retour la ligne. Heureusement, il est trs
simple d'y remdier :
Code : C++
if(fichier.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream flux(&fichier);
while(!flux.atEnd())
texte += flux.readLine() + '\n';
texte.resize(texte.size()-1); // limine le '\n' en trop
fichier.close();
}
www.openclassrooms.com
20/69
Mais en gnral, on n'utilise pas readLine() pour rcuprer tout le contenu ! En effet, son utilisation est plus frquente quand il
s'agit d'appliquer un processus bien dtermin au contenu du fichier ligne par ligne, comme dans cet exemple :
Code : C++
while(!flux.atEnd())
{
texte = flux.readLine();
maFonction(texte);
}
Voil, je crois que nous avons peu prs fait le tour de la lecture, passons prsent l'criture !
Je vais commencer par la mthode la plus simple, c'est--dire la premire dans l'ordre o je les ai crites ci-dessus. Pour vous
montrer comment implmenter et utiliser ceci, je vais m'appuyer sur un exemple concret de programme "fini" qui vous permettra
d'crire dans le fichier de votre choix (chemin au choix) le texte de votre choix. En gros, notre petit programme modeste va se
structurer de la manire suivante :
On demande l'utilisateur le chemin absolu ou relatif du fichier dans lequel crire, si ce dernier n'existe pas dj, on le
cre. On se servira de QInputDialog::getText et de QString.
Si le QString rcupr n'est pas vide, on continue.
On demande l'utilisateur de saisir dans un QInputDialog::getText (encore) le texte crire dans le fichier.
On cre un objet de type QFile pour l'criture dans le fichier.
On ouvre le fichier en criture en utilisant les flags QIODevice::WriteOnly et QIODevice::Text.
On cre un objet flux de type QTextStream pour manipuler le flux entrant du fichier.
On crit le texte saisi par l'utilisateur dans le fichier en nous servant de l'objet flux prcdemment dclar.
On ferme le fichier, on quitte le programme.
Premire rflexion : qu'est-ce qu'on va devoir inclure ? Si je suis ce que je viens d'crire, j'obtiens bien :
Code : C++
#include
#include
#include
#include
#include
#include
#include
<QApplication>
<QTextStream>
<QInputDialog>
<QIODevice>
<QString>
<QFile>
<QMessageBox>
Notez bien que j'ai aussi inclus QMessageBox tant donn que j'affiche un message d'erreur si le chemin spcifi ou le texte
crire est rest vide. Bon, je vous donne dj le code de base du main, qui sera une fois de plus notre seule fonction :
Code : C++
int main(int argc, char** argv)
www.openclassrooms.com
21/69
Certains se demanderont certainement pourquoi j'utilise exit() et ils ont bien raison de demander. Et bien, c'est pour la simple
et bonne raison qu'autrement, le programme ne quittera pas normalement ou ne quittera pas du tout. C'est la seule solution
efficace et pas trop barbare. Pour ceux qui y auraient pens, QApplication::quit() n'est pas appropri ici. Pour la saisie
du chemin du fichier, voici le code que je propose :
Code : C++
QString chemin;
while((chemin = QInputDialog::getText(NULL,"Fichier","Quel est le
chemin du fichier ?")).isEmpty())
QMessageBox::critical(NULL,"Erreur","Aucun chemin n'a t
spcifi !");
Le fonctionnement de cette boucle est si simple que je ne vais rien dtailler. Pour la saisie du texte, on obtient peu prs la mme
chose :
Code : C++
QString texte;
while((texte = QInputDialog::getText(NULL, "Texte", "Que voulez-vous
crire dans "+chemin.toLatin1())).isEmpty())
QMessageBox::critical(NULL,"Erreur","Aucun texte n'a t
spcifi !");
Pareil, le code est trs simple comprendre. C'est partir de ce moment l que a devient un peu plus intressant, maintenant,
nous allons ouvrir le fichier spcifi par l'utilisateur. Pour cela, je me servirai des flags de QIODevice mentionns tout l'heure.
J'obtiens ce code :
Code : C++
QFile fichier(chemin);
fichier.open(QIODevice::WriteOnly | QIODevice::Text);
Maintenant que le fichier est ouvert en criture seule, on peut crer notre objet flux QTextStream et lui envoyer en paramtre du
constructeur l'adresse de l'objet QFile :
Code : C++
QTextStream flux(&fichier);
Ce qu'il nous reste faire est maintenant trs simple : nous allons utiliser le flux dclar et lui envoyer le texte saisi par
l'utilisateur. Enfin, n'oublions pas de fermer le fichier.
Code : C++
flux << texte;
www.openclassrooms.com
22/69
fichier.close();
Et c'est tout !
Avouez que ce n'est pas bien difficile mettre en uvre. En ralit, les flux sont faits pour cela, pour nous
faciliter les choses. Pour ceux qui n'auraient pas bien suivi toutes les tapes (ou pour les paresseux), voici le main complet :
Code : C++
int main(int argc, char** argv)
{
QApplication app(argc, argv);
QString chemin, texte;
while((chemin = QInputDialog::getText(NULL,"Fichier","Quel est
le chemin du fichier ?")).isEmpty())
QMessageBox::critical(NULL,"Erreur","Aucun chemin n'a t
spcifi !");
while((texte = QInputDialog::getText(NULL, "Texte", "Que voulezvous crire dans "+chemin.toLatin1())).isEmpty())
QMessageBox::critical(NULL,"Erreur","Aucun texte n'a t
spcifi !");
QFile fichier(chemin);
fichier.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream flux(&fichier);
flux << texte;
fichier.close();
}
exit(0);
Compilez pour voir ce que cela donne. Voici une petite dmonstration de l'excution du code :
On saisit le chemin du fichier en incluant son nom : Image.
On saisit le texte crire dans le fichier : Image.
Le fichier a bien t cr : Image.
Le texte a bien t crit dans le fichier : Image.
Nous allons ds maintenant nous attaquer l'autre technique d'criture dans les fichiers : en nous servant des fonctions
membres dfinies dans QFile. Nous allons conserver les mmes codes que tout l'heure sauf que nous allons remplacer
l'utilisation de la classe QTextStream par des appels aux fonctions de QFile. Voici trois des plus utilises :
Fonction
Paramtre(s)
Description
write
write
putChar
char c
Cette fois, la fonction n'crit qu'un seul caractre dans le fichier. Le curseur
est plac aprs le caractre crit.
Pour crire le texte spcifi par l'utilisateur du programme l'aide de la fonction write() par exemple, on fait tout simplement :
Code : C++
www.openclassrooms.com
23/69
fichier.write(texte.toLatin1(),texte.size());
En plus, si vous allez faire un tour dans la doc Qt, vous vous rendrez compte que la mthode toLatin1() de QString renvoie
un objet QByteArray. Et enfin, voici une petite dmonstration illustrant le fonctionnement de putChar :
Code : C++
fichier.putChar(texte.at(0).toLatin1());
Ne confondez pas, la mthode toLatin1() de QChar renvoie une variable de type char. Pour crire tout un texte caractre par
caractre, il n'y pas de secret :
Code : C++
for(QString::iterator it=texte.begin(); it!=texte.end(); it++)
fichier.putChar((*it).toLatin1());
Et hop ! Vous venez donc d'apprendre lire et crire dans les fichiers. Je l'ai dj dit mais je me rpte : la manipulation des
fichiers est une fonctionnalit indispensable et il ne faut pas hsiter l'utiliser en cas de besoin, et ces cas-l, on les rencontre
souvent, que ce soit pour stocker les scores d'un jeu, pour implmenter une persistance d'objet (cf Persistance et Srialisation)
ou autres. Mais vous n'avez pas encore tout vu ! En effet, il est possible de copier des fichiers, de dplacer les fichiers et mme
de les renommer ! Eh bien c'est exactement ce qui est au programme de la sous-partie suivante.
Copier un fichier
La copie d'un fichier ncessite l'utilisation de la fonction membre copy() de QFile. Cette fonction prend en paramtre le nom du
fichier de copie. Ce n'est pas un scoop, mais je le souligne quand mme : c'est enfantin utiliser !
Code : C++
QFile fichier("poeme.txt");
if(!fichier.copy("copie.txt"))
www.openclassrooms.com
24/69
QMessageBox::critical(NULL,"Erreur","Impossible de copier
poeme.txt");
La copie peut chouer, par exemple s'il existe dj un fichier du nom de copie.txt.
Ou plus simplement :
Code : C++
if(!QFile::exists("poeme.txt"))
QMessageBox::critical(NULL,"Erreur","Le fichier spcifi
n'existe pas !");
else QMessageBox::information(NULL,"Information","Le fichier
spcifi existe !");
Effacer un fichier
Ici, on va se servir de QFile::remove(). Blabla, voici un exemple de code :
Code : C++
QFile fichier("poeme.txt");
if(!fichier.remove())
QMessageBox::critical(NULL,"Erreur","Impossible de supprimer le
fichier !");
Renommer un fichier
www.openclassrooms.com
25/69
www.openclassrooms.com
26/69
Ce chapitre est spar en trois parties ("Un peu de culture", "Thorie" et "Pratique") ainsi que d'une annexe vous permettant
d'utiliser les diffrentes mthodes donnes travers ce tuto en fonction de l'OS de l'utilisateur.
A l'issu de la premire partie, vous en saurez un peu plus sur la fonction main en gnral. Dans la seconde partie, nous verrons
les trois manires de procder avec Qt (une ne sera aborde que de nom), et dans la dernire partie, nous appliquerons la thorie
avec du code pouvant tre rutilis dans vos projets.
Allons-y !
Un peu de culture
Nous aborderons ici le sujet de la fonction main. Pourquoi en parler ? Parce que ce tutoriel prend pour base cette fonction que
nous allons voir dans cette partie.
La fonction main est la premire fonction appele par le systme d'exploitation lors de l'ouverture d'une application. Son nom doit
imprativement tre main car c'est celle que doit trouver le compilateur. Elle est la fonction marquant le dbut de chaque
programme, en appelant d'autres ou non.
Elle est constitue comme toute fonction d'une en-tte (cf le code donn la fin de cette sous-partie) et d'un bloc pouvant en
contenir d'autres (un bloc est en fait ce qui se trouve entre les accolades { et } que l'on retrouve dans tous les programmes).
Dans les annes 1980 1990 (aucune information ne prcise la date exacte), cette fonction ne renvoyait rien, elle tait donc de
type void, ce qu'acceptent encore quelques compilateurs. Plus rcemment, des amliorations y ont introduit les arguments qui
sont en fait dans la norme actuelle de mettre "int argc" et "char *argv[]". Elle s'est mise renvoyer des informations sous la
forme d'un int qui informait du bon droulement du programme (d'o return 0 et return 1).
Note : Il est prfrable de mettre return EXIT_SUCCESS dans vos programmes au cas o la norme serait amene changer...
En rsum, l'ordre logique du droulement d'un programme serait tout d'abord le lancement de l'application, donc l'appel de la
fonction main et des fonctions qu'elle appelle si c'est le cas, le droulement de l'excution puis le renvoi d'une information
annonant si oui ou non l'application s'est droule correctement.
Aujourd'hui, nous en sommes cette forme-ci :
Code : C++
int main(int argc, char *argv[])
Thorie
Nous voici donc la partie thorique du tutoriel.
Dans de ce cours sur les paramtres de la fonction main en C, l'auteur prsente un schma du tableau de pointeurs argv (dont la
taille est dfinie par la valeur de argc) :
www.openclassrooms.com
27/69
En rgle gnrale, les premiers lments d'argv ne sont pas censs correspondre ce type de chaine mais plutt au nom du
programme et/ou au chemin relatif ou absolu du programme, le tout dpendant du systme d'exploitation et du mode d'excution
du programme. Par exemple, Mac ne va pas renseigner dans les arguments une ouverture par double-clic sur un fichier.
L'utilit de cela est par exemple de pouvoir ouvrir un fichier sans avoir besoin de passer par une ventuelle fonction
d'ouverture.
Rsultat :
1. L'utilisateur ne perd pas de temps passer par la fonction d'ouverture prsume existante ;
2. Quand il double-clique sur un fichier s'ouvrant avec le programme concern, le programme ne s'ouvre pas comme on
l'ouvrirait normalement par le biais de l'excutable ;
3. a fait plus "pro" d'avoir un systme de rcupration des arguments rcuprs par la fonction main.
Si la taille d'argv est strictement suprieure 1, les arguments de la fonction main contiennent quelque chose de plus que
simplement le chemin de l'excutable mais galement... le chemin du fichier qui a ouvert ce programme !
Le rapport avec Qt
Le rapport avec Qt est la disposition de deux classes permettant de rcuprer les arguments : QCoreApplication avec la fonction
statique QCoreApplication::arguments() et QFileOpenEvent (utilis avec Mac pour le double-clic sur un fichier). Lors de
l'introduction, je parlais de trois moyens diffrents de rcuprer les arguments. En effet, il en existe une troisime solution qui est
devenue dprcie et qui a apparemment disparue de la documentation : une combinaison de QCoreApplication::argc() et de
QCoreApplication::argv() utilisable avec qApp.
La premire solution donne de rcupration des arguments avec QCoreApplication tant la plus pratique et la plus efficace,
nous ne verrons pas la combinaison donne ci-dessus.
Nous pouvons maintenant passer la pratique !
Pratique
Rcupration avec Mac
Comme je vous le disais plus tt, le systme d'exploitation Macintosh ne fonctionne pas parfaitement comme les autres OS, mais
ce n'est pas pour autant qu'on ne peut pas rcuprer les arguments car la classe QFileOpenEvent existe, bien qu'elle ne soit
utilisable que sous Mac (ventuel problme de portabilit donc).
Cette classe utilise les vnements, il suffit donc de se servir de la fonction installEventFilter() sur les instances du programme
pour pouvoir se servir de la classe et pour pouvoir par la mme occasion rcuprer ce qu'on recherche :
Code : C++
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
FenPrincipale fenetre;
QApplication::instance()->installEventFilter(&fenetre); // C'est
cette ligne qui prpare le tout
fenetre.show();
}
return app.exec();
www.openclassrooms.com
28/69
Il ne nous reste plus qu' rcuprer le tout avec notre classe et sa fonction file() :
Code : C++
bool FenPrincipale::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::FileOpen)
{
/*
Si l'event FileOpen a t appel, on effectue nos oprations...
*/
chargerFichier(((QFileOpenEvent*)event)->file());
return true;
}
else
{
/*
Sinon, le programme s'est excut partir de son icne et
non de celle d'un autre fichier s'ouvrant avec lui
*/
return QObject::eventFilter(obj, event);
}
}
Dans le cas o une variable de type QString avait t dfinie, on aurait ici cr une fonction chargerFichier() dont le constructeur
serait le suivant :
Code : C++
void FenPrincipale::chargerFichier(QString &nomFichier)
Voyez-vous dj une condition se mettre en place ? Non ? Peu importe, nous allons commencer par analyser la fonction
arguments() de QCoreApplication afin d'y voir plus clair :
Code : C++
QStringList QCoreApplication::arguments ()
Cette fonction renvoie un tableau de QString, soit un QStringList, utilisable trs simplement. Pour numrer la taille d'un tableau
de type QStringList, il suffit d'utiliser la fonction count() qui renvoie quant elle un int, et ici, pour tre plus prcis, le const
signifie que cette fonction ne modifie pas les attributs de sa classe.
La condition est donc la suivante :
www.openclassrooms.com
29/69
Code : C++
if(QCoreApplication::arguments().count() > 1)
// On effectue nos oprations
Testons l'efficacit de ce code avec un programme trs simple constitu uniquement d'un main.cpp pour viter d'avoir crer
trop de fichiers pour si peu. Ce programme affichera dans une bote de dialogue le contenu de
QCoreApplication::arguments().at(1) .
Code : C++
#include <QApplication>
#include <QtGui>
class FenPrincipale : public QWidget
{
Q_OBJECT
public :
FenPrincipale()
{
QStringList args = QCoreApplication::arguments();
if(args.count() > 1) // Notre condition
{
// On affiche nos arguments dans une bote de dialogue en
donnant l'emplacement du fichier ayant ouvert le programme :
QMessageBox::information(NULL, "Rcupration des arguments",
"L'argument rcupr est le suivant : \n" + args.at(1));
}
else
{
// Sinon, on informe que le programme a t ouvert par son
excutable :
QMessageBox::information(NULL, "Rcupration des arguments", "Le
programme s'est lanc normalement...");
}
};
};
#include "main.moc"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
FenPrincipale fenetre;
exit(0); // On ferme directement une fois avoir vu la chose
observer...
}
En admettant que nous ayons cr un projet plus complexe, il aurait t possible d'envoyer une fonction chargerFichier() par
exemple de la manire suivante :
Code : C++
FenPrincipale::FenPrincipale()
{
/*
Votre code PUIS la condition :
www.openclassrooms.com
30/69
Annexe
Nous allons voir dans cette dernire partie comment rcuprer le nom de l'OS sous lequel l'utilisateur de votre programme tourne.
En fonction de cela, il sera possible d'utiliser soit la mthode de rcupration des paramtres de la fonction main() avec Mac, soit
la mthode pour les autres systmes d'exploitation. Cela pourra vous servir rendre vos programmes cods avec Qt parfaitement
portables.
Tout ce que je vais vous prsenter dans cette sous-partie est bien sr parfaitement en rapport avec ce framework.
Brves explications
Qt permet travers l'en-tte <QtGlobal> d'utiliser de divers fonctions, types et macros. Dans le cas prsent, nous souhaitons
rcuprer le nom de l'OS sur lequel l'utilisateur fait tourner l'application. Nous allons donc logiquement nous orienter vers les
macros.
Voici un petit tableau listant les macros dont vous allez peut tre (mme srement) avoir besoin un jour dans le cadre d'un projet
quelconque :
Macro
Q_WS_X11
Description
Cette macro est dfinie sous X11.
A cela s'ajoutent des macros caractre moins global telles que Q_OS_WIN32, Q_WS_QWS et bien d'autres encore qui
vrifient la plupart du temps un ou plusieurs paramtre(s) supplmentaire(s).
Pour plus d'informations, vous pouvez toujours consulter cette page : cliquez ici pour y accder !
www.openclassrooms.com
31/69
Maintenant que vous avez saisi le principe, nous pouvons reprsenter cela par un code qui est le suivant :
Code : C++
#if defined(Q_WS_MAC)
ouvertureMac();
// Fonction qui serait spcialement cre pour Mac...
#else
if(QCoreApplication::arguments().count() > 1)
{
ouvertureAutresOS();
// Fonction qui serait cre pour les autres OS...
}
#endif
Voil, ce chapitre est termin.
Je compte sur vous pour raliser de jolis programmes pouvant rcuprer avec Qt tout ce que nous venons de voir et je vous
donne rendez-vous la suite !
www.openclassrooms.com
32/69
Et oui, le style que nous allons raliser ensemble est en quelques sortes un mlange des styles propres Mac et Windows par
le biais d'une petite centaine de lignes. Toutefois, l'aboutissement du chapitre, il n'aura t ralis que le style des boutons et
non des autres types widgets. Si vous vous ennuyez ou que vous cherchez parfaire vos techniques, n'hsitez pas terminer le
style que nous allons crer et peut tre mme l'utiliser dans vos projets ! La majorit des styles situs dans les sources de Qt
psent plus de 100Ko donc ne vous en faites pas s'il vous semble que la taille de votre fichier de style est un peu grosse au
niveau du rapport nombre de lignes/rsultat...
Bonne lecture !
Afin que vous puissiez suivre, je vous fournis le code de base de ces fichiers.
Code : C++ - main.cpp
#include "FenPrincipale.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setStyle(new Style); // Ligne dtaille la suite.
FenPrincipale fenetre;
www.openclassrooms.com
33/69
fenetre.show();
}
return app.exec();
zoneCentrale->setLayout(layout);
www.openclassrooms.com
34/69
Style::Style()
{
}
Dans le main.cpp, j'utilise la fonction setStyle(), sur la variable app, qui permet comme son nom l'indique si bien de dfinir un
style toutes les fentres du programme.
Note : Comme dit dans la description de la fonction, vous ne pourrez pas vous servir de feuilles de style si vous vous servez de
styles personnaliss...
Aprs avoir dit cela, je ne vois plus trop quoi vous annoncer part le fait que j'ai cr des fichiers spcifiques leur utilit :
Style.cpp exclusivement pour ce qui correspond au style personnalis que nous allons crer par la suite, FenPrincipale.h pour les
dclarations, FenPrincipale.cpp pour dfinir des widgets qui serviront avoir un aperu (et je ne pense pas avoir vous dire
quoi sert le main.cpp, sinon je ne sais plus quoi vous dire
).
A propos d'aperu, voici le rsultat obtenu :
Cette fonction va prendre pour arguments un PrimitiveElement, indiquant plus ou moins le type de widget que nous allons
avoir styliser, un QStyleOption, indiquant quant lui les options de style en fonction du type d'option, un QPainter qui nous
servira dessiner nos widgets et enfin un QWidget dont vous n'aurez que rarement l'utilit.
Je vous conseille vivement de jeter un coup d'il au tableau contenant de multiples informations sur les diffrents arguments
possibles de cette fonction.
A l'aide du pointeur option de type QStyleOption, nous allons pouvoir rcuprer plusieurs informations sur le widget dont trois
qui s'avrent capitales pour pouvoir dessiner nos widgets. Je vous fais un petit tableau de ce qui me semble le plus utile (le
quatrime est le moins utile de la liste) :
Membre
Description rapide
rect
state
www.openclassrooms.com
35/69
Pas besoin de dtailler, les mots parlent d'eux-mmes !
Cela ne vous tonnera donc pas si le code suivant rcupre les valeurs x, y, height et width (soit les quatre valeurs d'un QRect)
du widget :
Code : C++
int rectX, rectY, rectLong, rectLarg;
option->rect.getRect(&rectX, &rectY, &rectLong, &rectLarg);
Note : Par exprience, nous savons que nous devons retirer un pixel ou plus de la longueur et de la largeur afin que le rectangle
puisse entrer dans la zone du widget.
J'ai choisi d'en retirer deux en dcalant les coordonnes de x et de y vers l'intrieur du widget :
Code : C++
rectLong = rectLong - 2;
rectLarg = rectLarg - 2;
rectX = rectX + 1;
rectY = rectY + 1;
Maintenant, nous devons mettre une condition pour vrifier que nous allons bien dessiner un bouton l'aide de l'argument
element. Si vous avez regard le tableau (pas celui que j'ai fait mais celui de la doc de la fonction drawPrimitive()), et bien vous
avez d constater que le choix ne se fait pas tellement pour les boutons, c'est de PE_PanelButtonCommand que nous allons
avoir besoin. En rsum, si element est quivalent PE_PanelButtonCommand, et bien c'est que nous dessinons un bouton.
Code : C++
if(element == PE_PanelButtonCommand)
{
// La suite du code se situera ici...
}
C'est l qu'intervient un autre membre de QStyleOption : state. Cela nous permettra par exemple de vrifier si le bouton est activ
ou non, vrifier si la souris survole le widget (trait dans la sous-partie suivante pour cause de ncessit de deux autres
fonctions), vrifier que le bouton est press, etc...
Voici un tableau des principaux statuts offerts par QStyle par le biais de option->state dans notre code :
Constante
Valeur
correspondante
Description
QStyle::State_None
0x00000000
QStyle::State_Active
0x00010000
QStyle::State_Editing
0x00400000
QStyle::State_Enabled
0x00000001
QStyle::State_HasFocus
0x00000100
QStyle::State_MouseOver
0x00002000
www.openclassrooms.com
36/69
QStyle::State_NoChange
0x00000010
QStyle::State_Off
0x00000008
QStyle::State_On
0x00000020
QStyle::State_Raised
0x00000002
QStyle::State_ReadOnly
0x02000000
QStyle::State_Selected
0x00008000
QStyle::State_Sunken
0x00000004
L'ordination de votre code est importante dans le cas o vous voudriez effectuer comme moi des conditions les plus
courtes possibles.
Une question doit srement vous tracasser : comment vrifier plusieurs lments dans la mme condition ? Et bien de la manire
suivante :
Code : C++
if(option->state & (State_Sunken | State_Enabled)) { ... }
if(option->state & (State_Sunken & State_Enabled)) { ... }
www.openclassrooms.com
37/69
Je ne prendrai pas le temps de vous dtailler plus ce que j'ai fait car ce n'est pas tellement le but du tutoriel que de vous
enseigner QPainter et QGradient.
Concluons la sous-partie avec la fonction complte suivie d'une capture d'cran du rsultat :
Code : C++
void Style::drawPrimitive(PrimitiveElement element, const
QStyleOption *option, QPainter *painter, const QWidget *widget)
const
{
int rectX, rectY, rectLong, rectLarg;
option->rect.getRect(&rectX, &rectY, &rectLong, &rectLarg);
rectLong = rectLong - 2;
rectLarg = rectLarg - 2;
rectX = rectX + 1;
rectY = rectY + 1;
if(element == PE_PanelButtonCommand)
{
if(option->state & State_Enabled)
{
QLinearGradient gradient;
gradient.setStart(0, rectLarg + 2);
gradient.setFinalStop(0, 0);
gradient.setSpread(QGradient::ReflectSpread);
gradient.setColorAt(0, Qt::white);
gradient.setColorAt(0.5, QColor(233, 233, 233));
gradient.setColorAt(1, Qt::white);
painter->setRenderHint(QPainter::Antialiasing);
painter->save();
painter->setPen(QColor(Qt::black));
painter->setBrush(QBrush(gradient));
painter->drawRoundedRect(rectX, rectY, rectLong, rectLarg, 5, 5,
Qt::AbsoluteSize);
painter->restore();
painter->setPen(QColor(250, 250, 250));
painter->drawRoundedRect(rectX + 1, rectY + 1, rectLong,
rectLarg, 5, 5, Qt::AbsoluteSize);
}
else
{
painter->save();
www.openclassrooms.com
38/69
2);
}
if(option->state & (State_Sunken | State_On))
{
QLinearGradient gradient;
gradient.setStart(0, rectLarg + 2);
gradient.setFinalStop(0, 0);
gradient.setSpread(QGradient::ReflectSpread);
gradient.setColorAt(0, QColor(196, 209, 219));
gradient.setColorAt(0.25, QColor(147, 177, 200));
gradient.setColorAt(0.5, QColor(105, 149, 183));
gradient.setColorAt(0.75, QColor(137, 175, 200));
gradient.setColorAt(1, QColor(178, 209, 219));
painter->setRenderHint(QPainter::Antialiasing);
painter->save();
painter->setPen(QColor(Qt::black));
painter->setBrush(QBrush(gradient));
painter->drawRoundedRect(rectX + 1, rectY + 1, rectLong - 1,
rectLarg - 1, 5, 5, Qt::AbsoluteSize);
painter->restore();
painter->setPen(QColor(150, 150, 150));
painter->drawRoundedRect(rectX + 1, rectY + 2, rectLong - 1,
rectLarg - 1, 5, 5, Qt::AbsoluteSize);
}
}
}
Et comme promis, voici le rsultat (en retirant le bouton survol car le survol n'a pas t trait pour le moment) :
Survol du bouton
Afin de pouvoir nous servir correctement de QStyle::State_MouseOver, il va tre ncessaire de passer par les fonctions polish()
et unpolish(). Nous allons utiliser qobject_cast<QPushButton *>(widget) dans une condition, une simple
vrification permettant d'viter des erreurs. Dans le cas o cela retournerait quelque chose, et bien on entrerait dans la condition
pour attribuer ou dsattribuer au widget Qt::WA_Hover et dans le cas o cela retournerait 0, on ne ferait rien.
Voici le code de ces deux fonctions :
www.openclassrooms.com
39/69
Code : C++
void Style::polish(QWidget *widget)
{
if (qobject_cast<QPushButton *>(widget))
widget->setAttribute(Qt::WA_Hover, true);
}
void Style::unpolish(QWidget *widget)
{
if (qobject_cast<QPushButton *>(widget))
widget->setAttribute(Qt::WA_Hover, false);
}
Il est maintenant temps d'ajouter une condition juste avant la condition if(option->state & (State_Sunken |
State_On)) dans la rimplmentation de drawPrimitive(), celle du survol :
Code : C++
if(option->state & State_MouseOver)
{
QLinearGradient gradient;
gradient.setStart(0, rectLarg + 2);
gradient.setFinalStop(0, 0);
gradient.setSpread(QGradient::ReflectSpread);
gradient.setColorAt(0, QColor(203, 244, 246));
gradient.setColorAt(0.5, QColor(129, 185, 243));
gradient.setColorAt(1, QColor(203, 244, 246));
painter->setRenderHint(QPainter::Antialiasing);
painter->save();
painter->setPen(QColor(Qt::black));
painter->setBrush(QBrush(gradient));
painter->drawRoundedRect(rectX, rectY, rectLong, rectLarg, 5, 5,
Qt::AbsoluteSize);
painter->restore();
painter->setPen(QColor(250, 250, 250));
painter->drawRoundedRect(rectX + 1, rectY + 1, rectLong, rectLarg,
5, 5, Qt::AbsoluteSize);
}
Dessin du texte
Une autre option offerte par QStyle est la fonction drawItemText() dont le prototype est le suivant :
Code : C++ - Prototype de la fonction drawItemText()
void QStyle::drawItemText ( QPainter * painter, const QRect &
rectangle, int alignment, const QPalette & palette, bool enabled,
const QString & text, QPalette::ColorRole textRole =
QPalette::NoRole ) const
Le prototype est lui-mme trs parlant, je ne saurais mieux vous le dtailler. Par contre, je peux vous montrer comment vous en
servir !
Juste aprs sa dfinition dans le projet tel que la fonction soit vide, nous obtenons une fentre comme celle-ci :
www.openclassrooms.com
40/69
Et juste aprs la compilation, vous ouvrez le programme qui s'ouvre sous vos yeux bahis...
Les plus attentifs auront remarqu une petite flemme d'ajouter une souris manuellement...
Hum... ce n'est pas encore tout fait a.
Et bien forcment, quand on ne prend en compte ni l'alignement, ni le statut du bouton (ce qui influera sur la couleur du texte), on
ne va pas bien loin, vous pouvez me faire confiance.
Corrigeons a en vitesse :
Code : C++
void Style::drawItemText(QPainter *painter, const QRect &rectangle,
int alignment, const QPalette &palette, bool enabled, const QString
&text, QPalette::ColorRole textRole) const
{
if(enabled)
painter->setPen(QPen(QColor(Qt::black)));
else
www.openclassrooms.com
41/69
Tlchargement
Dans le cas o vous voudriez tester, amliorer ou autres le programme, j'ai plac ci-dessous un lien permettant de tlcharger les
sources du projet (quatre fichiers) :
www.openclassrooms.com
42/69
Partie 2 : La 2D avec Qt
Vous allez dcouvrir dans cette partie une chose que vous n'avez peut tre jamais vue : la 2D entirement avec des classes de Qt.
En effet, Qt possde ses propres classes, et elles sont parfaitement suffisantes pour raliser un logiciel de dessin ou mme un jeu
digne de ce nom !
Eh oui, selon vous, comment t conu Photoshop Element ? Comme pas mal de logiciels d'Adobe : avec Qt. Si Qt n'avait pas
ses classes 2D, Adobe ne se serait pas forcment tourn vers lui. Bref, je vous apprendrai ici utiliser la 2D de Qt. L'objectif final
de cette partie correspond la cration d'un jeu de Snake : qu'attendez-vous pour lire tous les chapitres ?
Commenons...
Il est trs frquent que des zros posent des questions dans le forum C++ comme : "Comment utiliser la SDL avec Qt ?". Qt
possde tout de mme de quoi raliser des programmes utilisant des lments graphiques, et nous allons apprendre ensemble
en crer.
Au menu, voici les tapes de ce chapitre :
Prsentation rapide des classes.
Le traditionnel "Hello World !".
QGraphicsScene
La classe QGraphicsScene a pour rle de pouvoir contenir plusieurs lments en 2D de type graphique. C'est en quelques sortes
un conteneur assez parallle la classe QWidget, bien que ces deux classes n'aient pas de rapport l'une l'autre, car
QGraphicsScene peut contenir des lments comme QWidget peut en contenir d'autres.
QGraphicsScene a tout de mme une particularit : il ne peut pas s'afficher seul, c'est--dire qu'il ncessite une autre classe pour
pouvoir tre visible, QGraphicsView.
Pour l'hritage, QGraphicsScene hrite de QObject.
QGraphicsView
QGraphicsView a pour fonction d'afficher le contenu d'un QGraphicsScene. Comme il hrite de QAbstractScrollArea, il peut
logiquement possder une barre de dfilement, ce qui peut se rvler trs pratique dans certains cas de figure.
Cette classe sera particulirement utile pour nous tout au long de cette partie donc attendez-vous d'autant plus la revoir par la
suite.
QPainter
Cette classe nous servira pour tout ce qui est coloration, formatage de texte, effets, etc...
www.openclassrooms.com
Partie 2 : La 2D avec Qt
43/69
Et bien d'autres
Il reste encore un bon nombre de classes que nous verrons par la suite comme QBrush par exemple, je ne vous les dtaille donc
pas ici.
Maintenant que nous en avons termin des prsentations rapides des classes, nous pouvons passer la pratique.
Je vous donne rendez-vous la suite du cours
C'est par mesure de simplicit que nous avons mis QtGui : nous n'avons pas pour objectif de nous attarder sur une liste
d'includes.
Nous pouvons dsormais entrer dans la partie la plus intressante de la cration de notre premier programme utilisant des
lments graphiques. Il s'agit de crer un QGraphicsScene, de lui mettre dedans du texte et de l'afficher. Pour cela, nous allons
procder dans l'ordre. Crons donc notre QGraphicsScene !
Voici le constructeur (le plus simple) que nous donne la documentation :
Code : C++
QGraphicsScene::QGraphicsScene ( QObject * parent = 0 )
Et ce constructeur en utilisation :
Code : C++
QGraphicsScene scene;
Nous avons ici cr une variable de type QGraphicsScene nomme "scene". Son rle sera d'accueillir le texte ("Hello World !").
Mais comment faire pour mettre du texte ? On a le droit de crer un QLabel ?
Quel besoin de crer un QLabel ? Nous allons nous servir de addText() tout simplement :
Code : C++
scene.addText("Hello world !");
Si vous avez retenu ce que je vous ai dit plus tt, compiler et lancer le programme ne sert actuellement rien car nous n'affichons
pas notre QGraphicsScene. Il faut crer un QGraphicsView pour afficher notre scne :
Code : C++
www.openclassrooms.com
Partie 2 : La 2D avec Qt
44/69
QGraphicsView vue(&scene);
vue.show();
return app.exec();
www.openclassrooms.com
Partie 2 : La 2D avec Qt
45/69
Il est toutefois bon de savoir qu'il y a chaque fois deux ou plusieurs mthodes de cration de figures ou d'ouverture d'images,
chacune aussi utile que ses collgues.
Une fois que nous aurons vu en dtail tout cela, nous verrons comment enregistrer votre scne de manire pouvoir pater vos
amis !
Nous allons voir par la suite les diffrentes mthodes de cration de figures gomtriques, quelles qu'elles soient, dans l'ordre
croissant de difficult.
Les points
Les points sont la base de toutes les figures gomtriques. Une ligne est constitue de points, tout comme un triangle, un
rectangle et ainsi de suite. Il est donc fort probable que vous ayez un jour vous en servir dans le cadre d'un projet personnel.
Voyons ensemble les diffrentes manires de crer un point avec, tout d'abord, la mthode la plus simple soit la cration d'un
QPoint :
Code : C++
QPoint point(10, 10); // Avec ici 10 pour la valeur de x et de y.
Note : un QPoint ne peut possder que des valeurs de x et de y tant entires (comme 1, 32, 97...) et non des valeurs de x et de y
dcimales. Si vous avez besoin d'utiliser des valeurs dcimales, servez-vous de QPointF comme par exemple dans le code suivant
:
Code : C++
QPointF point(10.5, 10.5); // Pensez mettre un point et non une
virgule pour chaque chiffre dcimal.
Un point, n'ayant ni longueur ni largeur, ne se voit pas. De plus, le code que je vous ai donn ne permet pas d'afficher un point
mais uniquement d'en crer un. La mthode pour afficher un point (mme s'il ne se verrait pas) serait de passer par les QPolygon
(ou par les QPolygonF) de la manire suivante :
www.openclassrooms.com
Partie 2 : La 2D avec Qt
46/69
Code : C++
QPolygon point;
point << QPoint(100, 100);
scene.addPolygon(point); // scene tant notre QGraphicsScene.
Les lignes
Pour crer une ligne, nous avons bien plus de possibilits que pour crer un point, et vous n'allez pas tarder dcouvrir
pourquoi.
En effet, nous pouvons compter un minimum de cinq classes servant toutes la cration de lignes.
QLine
QLine, comme QPoint, n'accepte pas les nombres dcimaux.
Voici un exemple de code se servant de QLine :
Code : C++
QLine ligne(50, 50, 200, 200);
scene.addLine(ligne);
On peut bien sr se servir de addLine() et de QPen pour grossir notre ligne. Je vous remets le code d'au-dessus avec une
ligne plus paisse :
Code : C++
QPen pen(Qt::blue, 5, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
QLine ligne(50, 50, 200, 200);
scene.addLine(ligne, pen);
www.openclassrooms.com
Partie 2 : La 2D avec Qt
47/69
QLineF
QLineF a exactement la mme utilit pour crer une ligne que QPointF l'a pour crer un point : ces deux classes grent les chiffres
dcimaux.
part cela, le mode de cration est quasi-identique.
QGraphicsLineItem
C'est encore une autre manire de crer une ligne. Nous ne nous y attarderons pas.
Je vous donne un exemple de cration :
Code : C++
QGraphicsLineItem ligne(50, 50, 200, 200);
QPolygon et QPolygonF
Ces deux classes reviennent un peu partout si vous avez remarqu. Eh bien oui, toutes les figures gomtriques sont la base
des polygones.
Un petit bout de code vous montrant comment crer une ligne avec un QPolygon serait le bienvenu, n'est-ce pas ?
Code : C++
QPolygon polygon;
polygon << QPoint(100, 100) << QPoint(100, 200);
scene.addPolygon(polygon);
Les rectangles
Une fois de plus, les possibilits de cration de rectangles ne manquent pas.
Nous allons tenter de faire le tour plus ou moins rapidement, en fonction de l'intrt des classes.
www.openclassrooms.com
Partie 2 : La 2D avec Qt
48/69
Note : je vais dsormais me mettre traiter frquemment dans cette fin de chapitre deux classes la fois car, vous l'ayant dj
rpt plusieurs fois, les classes comme QRect et QRectF par exemple sont presque identiques et vous connaissez dj la
diffrence entre les deux.
QRect et QRectF
Ces classes incontournables dans le cas de la cration de rectangles, la fois simples et efficaces, sont celles que nous allons
voir en premier.
Nous pouvons les utiliser travers deux mthodes : soit on utilise directement addRect() de QGraphicsScene, soit on cre un
QRect puis on l'affiche.
Code : C++
scene.addRect(10, 10, 100, 50); // Avec dans l'ordre la valeur de x,
de y, de la longueur et de la largeur.
Cette premire mthode, la plus simple, a tout de mme un dfaut : on ne peut pas rcuprer le rectangle pour le modifier, le
remettre quelque part ou autre. Je vous recommande donc de passer par la seconde mthode qui est la suivante :
Code : C++
QRect rectangle(10, 10, 100, 50);
scene.addRect(rectangle);
QGraphicsRectItem
Cette classe permet aussi de crer des rectangles mais je ne vous la dtaillerai pas ici.
QPolygon et QPolygonF
Et encore ces classes.
Je ne vous mets pas d'exemple, nous allons enfin voir juste en-dessous ces classes.
Note : nous pouvons faire cela pour presque toutes les figures gomtriques.
Je vous invite d'autant plus aller regarder dans la doc de QBrush comment charger des textures, etc.
Partie 2 : La 2D avec Qt
49/69
C'est ici que le choix se fait moins important. Nous avons QGraphicsPolygonItem (que je ne dtaillerai pas), QPolygon et
QPolygonF.
Le mode de construction que je vous donnais au-dessus correspondait en fait un systme particulirement pratique et simple
d'utilisation. Je vous remets un de ces codes, je vous l'explique puis je vous donne des exemples d'utilisation de QPolygon pour
la cration de diffrentes figures, a vous va ?
Code : C++
QPolygon polygon;
polygon << QPoint(100, 100) << QPoint(100, 200) << QPoint(200, 200)
<< QPoint(200, 100);
scene.addPolygon(polygon);
Comme certains ont pu le remarquer, le polygone qui sera cr par ce code sera un carr.
Dans l'ordre, voici ce que nous faisons :
1. Nous crons une variable de type QPolygon.
2. Nous construisons le polygone en lui donnant la position de ses sommets.
3. Nous ajoutons le polygone la scne.
Un losange
Code : C++
QPolygon polygon;
polygon << QPoint(100, 80) << QPoint(50, 150) << QPoint(100, 220) <<
QPoint(150, 150);
scene.addPolygon(polygon);
Les ellipses
Pour crer une ellipse, nous n'avons que peu de solutions et nous n'allons en voir que deux. Une ellipse peut tout fait tre un
ovale ou un cercle, un rectangle peut tre un carr. Un cercle est un cas particulier d'ovale, un carr est un cas particulier de
rectangle. Vous devez srement vous demander pour quelle raison je fais le rapprochement entre une ellipse et un rectangle. Eh
bien aussi trange que cela puisse paratre, nous avons besoin de crer un rectangle pour crer une ellipse.
Voici la premire mthode de cration d'une ellipse :
Code : C++
www.openclassrooms.com
Partie 2 : La 2D avec Qt
50/69
Et la seconde :
Code : C++
QRect rectangle(10, 10, 100, 50);
scene.addEllipse(rectangle);
Les images
Par chance pour nous, il est possible de charger des images (bien que nous puissions faire d'innombrables autres choses avec ce
que je vais vous donner en fin de phrase) dans nos scnes l'aide de addPixmap().
Voici une des manires de chargement d'image :
Code : C++
scene.addPixmap(QPixmap("image.png"));
Cette manire de procder montre aussi des dfauts : on ne peut pas rcuprer le pixmap, le modifier, etc.
Je vous conseille donc de crer une variable de type QPixmap et de l'ajouter la scne. Voici la mthode :
Code : C++
QPixmap monPixmap("image.png");
scene.addPixmap(monPixmap);
www.openclassrooms.com
Partie 2 : La 2D avec Qt
51/69
Non ce n'est pas tellement a mais ce n'est pas entirement faux. Quand je vous dis enregistrer une scne, c'est l'enregistrer sous
le format d'une image. Ce n'est donc pas entirement faux dans le sens o l'on peut toujours charger la scne enregistre plus tt
l'aide de addPixmap() que nous avons vu au-dessus.
Si vous voulez avoir le mme rsultat que moi, je vous conseille tout d'abord de tlcharger cette image :
return app.exec();
Eh bien il est incorrect car si vous enregistrez votre scne, comment le programme peut-il choisir la couleur de fond de votre
scne ?
De plus, les dimensions de la scne ne sont pas spcifies, comment voulez-vous qu'il devine ses dimensions ? (On peut corriger
a sans avoir besoin de fixer les dimensions, je vous expliquerai aprs.)
part cela, le code est correct.
Pour mettre une couleur de fond, il n'est pas ncessaire de passer par les feuilles de style, pour ceux qui connaissent.
En effet, les programmeurs ont tout prvu et ont conu setBackgroundBrush(). Il est vivement conseill de faire un
www.openclassrooms.com
Partie 2 : La 2D avec Qt
52/69
saut dans la doc pour vous renseigner. Je vous donne juste un indice : setBackgroundBrush() pourrait
s'appliquer sur notre variable vue.
Pour les dimensions fixes, soit on y remdiera plus tard, soit on fixe les dimensions. J'ai prfr la seconde solution avec
les dimensions 250x250px.
return app.exec();
www.openclassrooms.com
Partie 2 : La 2D avec Qt
53/69
On passe la suite :
Code : C++
painter.setRenderHint(QPainter::Antialiasing);
vue.render(&painter);
painter.setRenderHint(QPainter::Antialiasing);
On met ici le type de qualit du rendu que l'on va faire. Nous ne manquerons pas l'habitude en se servant de
QPainter::Antialiasing qui est trs utilis.
vue.render(&painter);
Et l, c'est render() que nous utilisons. Il s'agit d'une fonction de QGraphicsView qui sert capturer le contenu de la vue,
dans le cas actuel videmment.
Il ne reste plus que la sauvegarde. Nous n'aurons pas besoin de quoi que ce soit de plus que save() de QPixmap qui va,
comme son nom l'indique, sauvegarder le pixmap.
Voici le code complet de la sauvegarde de la scne :
Code : C++
#include <QApplication>
#include <QtGui>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene scene;
QPixmap monPixmap("Note.png");
scene.addPixmap(monPixmap);
QRect rectangle(-8, -4, 100, 100);
QPen pen(Qt::blue, 1, Qt::SolidLine);
scene.addRect(rectangle, pen);
scene.setBackgroundBrush(Qt::white);
QGraphicsView vue(&scene);
vue.setFixedSize(250, 250);
vue.show();
QPixmap pixmap(vue.width(), vue.height());
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
vue.render(&painter);
pixmap.save("image.png");
painter.end();
www.openclassrooms.com
Partie 2 : La 2D avec Qt
54/69
return app.exec();
A priori, vous devriez voir un fichier nomm image.png apparatre ct de votre programme une fois que vous aurez lanc
celui-ci.
Cette image devrait ressembler cela :
www.openclassrooms.com
Partie 2 : La 2D avec Qt
55/69
Notions supplmentaires
Il est possible que vous ayez t confront une ou plusieurs difficult(s) durant les exercices non corrigs que je vous ai
donns prcdemment. Dans le cas prcis, je parle de placement et autres de vos figures dans votre scne ( peu prs tout ce qui
touche les coordonnes et les dimensions de vos figures gomtriques).
Cette partie de chapitre a pour objectif de vous prparer rapidement pour pouvoir observer le fonctionnement des
QGraphicsItem en tous genres, ne la ngligez donc pas !
Comme vous pouvez le voir, nous avons un rectangle (un carr plus prcisment mais ce dtail ne nous intresse pas) avec
dedans une ellipse (un cercle mais ce dtail est expliqu la suite) et en haut gauche de cette figure, nous avons le point aux
coordonnes (0; 0). Cette dernire chose reprsente les coordonnes de la scne o se trouveraient nos deux figures.
Tout d'abord, tudions la cration du rectangle. Je vous rappelle tout hasard que la cration s'effectue en donnant les
coordonnes d'un point puis une longueur et une largeur. Ce point est le gros point rouge aux coordonne 0, 0. C'est le point
duquel partent la longueur et la largeur du rectangle, reprsentes par les flches bleues. Or, une fois que nous avons une
www.openclassrooms.com
Partie 2 : La 2D avec Qt
56/69
longueur et une largeur d'un rectangle, nous avons de quoi tracer le rectangle, c'est donc suffisant.
Rcapitulatif : la cration d'un rectangle se fait l'aide des coordonnes du sommet en haut gauche (sans prendre en
compte de rotation ou autre) du rectangle, ainsi que des mesures de la longueur et de la largeur qui partent de ce
sommet.
Nous avons sur ce schma deux points rouges lgends. Lors de la cration d'un QLine (ne prenez pas ici pour base QRect :
nous avons chang de partie de sous-partie de chapitre), nous avons tout d'abord dfinir les coordonnes du point 1 puis
celles du point 2. A partir de ces deux coordonnes, nous obtenons une ligne. C'est pour cela que je l'ai mise en pointills : car
nous ne donnons pas de dimensions prcises notre ligne, mis part les coordonnes des extrmits.
Je ne vais pas faire l'affront de vous remettre le mme schma pour QPolygon tant donn que le principe de cration est
exactement le mme puisqu'on ne fait que dfinir des points.
Les GraphicsItem
QGraphcisItem est un des lments majeurs de la 2D avec Qt. Il peut servir un bon nombre de choses une fois associ la
classe QGraphicsScene.
Dfinir un QGraphicsItem
Le principe de dfinition d'un QGraphicsItem est relativement simple, tout comme la dfinition d'objets appartenant d'autres
classes.
www.openclassrooms.com
Partie 2 : La 2D avec Qt
57/69
En admettant avoir cr les variables scene et rectangle respectivement de type QGraphicsScene, un code courant serait celui-ci
:
Code : C++
QGraphicsItem *item;
item = scene.addRect(rectangle);
Il est fort possible que certains se demandent pourquoi j'ai introduit la fonction addRect() ici mais peut-tre aussi quel est le
rapport entre les classes QGraphicsScene et QGraphicsItem, mis part le prfixe QGraphics.
Regardons ensemble un des prototypes de la fonction en question :
Citation : Prototype de la fonction
QGraphicsRectItem * QGraphicsScene::addRect ( ... )
Nos connaissances sur la fonction et ce prototype, coup volontairement aux informations qui nous intressent, montrent qu'elle
ajoute un rectangle dans un lment de QGraphicsScene (donc dans scene pour nous) et retourne un pointeur de type
QGraphicsRectItem. Voici un petit arbre montrant la hirarchie de cette classe et de ses principales classes surs par rapport
QGraphicsItem dont ils hritent :
Par contre, on ne pourra pas utiliser d'autres classes hritant de QGraphicsItem pour effectuer cette opration.
www.openclassrooms.com
Partie 2 : La 2D avec Qt
58/69
Une autre faon de dfinir un QGraphicsItem dans une scne est de le crer et de l'afficher de la manire suivante :
Code : C++
QGraphicsLineItem *ligne = new QGraphicsLineItem(0, 0, 230, 230);
scene.addItem(ligne);
Comme avec QGraphicsItem, nous pouvons rcuprer l'objet retourn par addRect(), addPixmap(), addLine(), etc... et ce
sans avoir besoin de changer le type du pointeur en tant contraint d'en crer un autre, nous allons nous servir
uniquement de cette classe partir de maintenant dans le chapitre.
Les utilits de rcuprer un pointeur, aprs excution de l'une de ces fonctions, sont en fait de pouvoir le supprimer (ce qui
retirera de la scne la forme quelconque associe au pointeur), de dplacer par son intermdiaire l'objet graphique sur lequel il
pointe, le modifier, recevoir des vnements en fonction des actions de l'utilisateur, et bien d'autres choses encore ! Cela peut
d'avrer trs utile dans le cas o vous raliseriez un logiciel du type Paint pour l'historique des actions ayant t effectues et
donc pour permettre pleins de choses supplmentaires.
Ah oui, j'oubliais, QGraphicsItem dtient une fonction, setZValue(), qui permet de rgler les problmes de superposition
d'lments graphiques. Nous verrons cela dans ce chapitre.
Le positionnement
A partir de maintenant, nous allons prendre pour base le code suivant :
Code : C++
#include <QApplication>
#include <QtGui>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene scene;
QGraphicsItem *item;
QPixmap image;
image.load("Note.png", 0, Qt::AutoColor);
item = scene.addPixmap(image);
QGraphicsView vue(&scene);
vue.setFixedSize(250, 250);
vue.setWindowTitle("J'utilise QGraphicsItem !");
vue.show();
}
return app.exec();
Ce code n'a rien d'extraordinaire, on ne fait que des oprations courantes, peut tre que ce type d'assignement d'image une
variable de type QPixmap vous chappait jusqu' maintenant, mais j'en doute...
En tant que widget de haut niveau, vue dispose ici de setFixedSize(), utilis pour que vous puissiez voir la progression au fil de
votre avance dans le chapitre, et de setWindowTitle() pour faire joli.
Pour le moment, le rsultat est le suivant (cliquez pour agrandir !) :
www.openclassrooms.com
Partie 2 : La 2D avec Qt
59/69
Note : Il est possible de retrouver les coordonnes d'un lment l'aide des fonctions x() et y() en crivant par exemple :
"int x = item->x(); ". Il est aussi possible de tout rcuprer l'aide de pos() comme ici : "QPointF
point(item->pos()); ".
La suppression
Il est presque certain que vous aurez besoin de supprimer un moment ou un autre un QGraphicsItem d'une scne. La mthode
la moins brutale est de passer par la fonction removeItem() de QGraphicsScene de la manire suivante :
Code : C++
scene.removeItem(item);
Notez que le pointeur n'est pas dtruit : la fentre affichera votre image si vous refaites "item =
scene.addPixmap(image); " !
C'est d'ailleurs pour cette raison qu'il faut passer par "delete item; " pour viter une fuite de mmoire.
La Z-Value
La Z-Value, aussi appele z-index, permet de dfinir l'lvation des objets de QGraphicsItem afin d'viter des problmes de
superposition trs peu prvisibles. Par dfaut, la Z-Value est dfinie 0 (ce qui pose donc un problme car il faut bien choisir
quel sera l'lment situ au-dessus de l'autre dans le cas o ils seraient disposs au mme endroit). Pour modifier cette valeur,
nous utilisons la fonction setZValue() de la manire suivante :
Code : C++
item->setZValue(5);
www.openclassrooms.com
Partie 2 : La 2D avec Qt
60/69
Note : Comme le paramtre de la fonction est un rel et qu'un rel peut trs bien tre ngatif, il est possible de faire ceci :
Code : C++
item->setZValue(-4);
La rotation
Tout d'abord, je vous conseille de garder en tte que la fonction rotate() existe, vous en aurez besoin si vous passez au chapitre
suivant.
La fonction permet donc de faire roter un lment graphique sur le point sur lequel il est bas de x degrs avec x tant un rel
(donc possibilit de faire une rotation avec une valeur d'angle ngative). Utiliser setPos() dans un cas comme celui-ci s'avrerait
donc tre une judicieuse ide !
Code : C++
item->rotate(90);
www.openclassrooms.com
Partie 2 : La 2D avec Qt
61/69
Maintenant que j'ai pris mes prcautions cela a t dit, nous pouvons commencer le TP qui s'organisera sur trois parties :
Prparatifs ;
Correction ;
Ides d'amlioration.
Commenons ds maintenant avec les prparatifs !
Prparatifs
Notre commanditaire est une riche jeune fille clibataire de 19 ans personne dsirant vous embaucher pour lui raliser un rveil
qui lui servira tous les jours. Voici sa commande :
Citation : Notre commanditaire
Bonjour,
Je m'adresse vous en tant qu'employeur qui vous embauchera, si vous l'acceptez, en contrat dure dtermine (le temps
que vous ayez fini de concevoir ce dont j'ai besoin) pour un total de 200 styllards fournis l'expiration du contrat. Pour tre
franche avec vous, je suis relativement souvent en retard. Le temps que je m'habille, que je me frise les cheveux, que je me
poudre le visage, que je me vernisse les ongles, [..], et bien j'ai dj une heure et demie de retard. Je vous vois dj en train de
me dire de me lever plus tt ! Et bien je n'aime pas le "drrriiiiiiinnnng" strident de mon rveil qui me met en rogne le matin
quand il sonne avant 5h. Comme je me suis achet un magnifique ordinateur portable digne de ma beaut personnelle (qui
reste allum en permanence, sa fonction de chaine Hifi tant excellente), et bien je me suis dit que ce serait bien de lire un son
provenant de ce bel objet et non de cet immonde rveil sorti des latrines de mon voisin. J'ai rcemment eu vent que vous tes
en train d'approfondir vos connaissances avec "Qt". Peu importe ce que c'est, ralisez le rveil avec, si cela le permet.
Dans mon rveil, je voudrais avoir plusieurs options : voir l'heure de mon ordinateur (qui s'actualisera toutes les secondes),
pouvoir modifier l'heure prvue de la sonnerie rapidement (sans passer par un menu-qui-me-fera-perdre-mon-temps), voir les
infos du son que j'ai mis (je ne changerai pas forcment le nom du son donc play.wav, a ira trs bien) comme le nom du son,
l'heure o il se jouera, juste pour le plaisir d'avoir quelque chose dans un menu, autre que l'ternel "Quitter" ou le "A propos"
qui infeste les programmes. Ah, j'y pense, il ne faut pas que le rveil se drgle dans le cas o je rabattrais mon cran. Une
dernire demande : pouvez-vous crer une horloge anime pour faire joli ?
Je vous remercie d'avance,
Laura de Latour.
Comme vous l'avez compris dans cette longue commande, nous allons raliser un rveil avec Qt (et ne me demandez pas ce que
reprsente 200 styllards, je n'en sais rien
). Je vous vois dj me demander le rapport entre un rveil et la 2D : c'est la pendule
qui se trouvera dans la fentre principale (Laura en demande une). Elle sera cre entirement avec les outils qu'on a vus, pas
question de se servir d'un widget tout fait comme on en voit dans Qt Designer et ailleurs.
Je sais que des exemples tels que celui-ci se trouvent dans la documentation.
Comme ils utilisent QPainter, et bien ce ne sera pas en rapport avec le sujet (qui peut donc tre propos en TP)...
Comme une image s'avre ici tre bien plus instructive que des masses d'explications, voici une capture d'cran du programme
termin :
www.openclassrooms.com
Partie 2 : La 2D avec Qt
62/69
A gauche, le widget utilis n'est autre qu'un QGraphicsView affichant un QGraphicsScene, bref, on ne change aucunement nos
habitudes concernant le choix de widget. Pour votre information personnelle, l'affichage se fait en anti-aliasing pour avoir plus de
nettet (regardez setRenderHints()). Je vous laisse vous renseigner sur la procdure suivre pour faire la mme chose que moi.
A droite, en-dessous du label "Heure du systme d'exploitation" et du label "Heure de l'alarme", j'ai utilis la classe QTimeEdit
qui semble parfaitement adapte ce que nous voulons faire. Il est vident que la sonnerie va se dclencher lorsque les deux
QTimeEdit auront la mme valeur.
Je vous conseille de vous servir de QTimer pour la gestion du temps...
Pour le son, j'utilise QSound. Le dfaut est la limitation importante sur l'extension du son mais nous travaillons avec Qt donc le
choix ne se fait malheureusement pas pour moi dans le cas prsent. Si vous voulez utiliser FMOD ou ce que vous voulez d'autre,
n'hsitez pas !
Dans les trois menus, j'ai plac trois actions :
Menu "Fichier" : Quitter ;
Menu "Action" : Sonnerie ;
Menu "Aide" : A propos.
Je vous conseille vivement de recentrer votre scne avant d'y ajouter des objets. Par exemple, si elle tait de 400x400px, on aurait
le code suivant :
Code : C++
scene->setSceneRect(-200, -200, 400, 400);
Au final, on aurait le point (0, 0) centr sur la scne. L'utilit serait d'effectuer un placement de la base de vos aiguilles sur ce
point.
Deux images sont disponibles. La premire est l'icne du programme et la seconde est l'arrire-plan de l'horloge. N'hsitez pas
vous en servir, je les ai faites uniquement pour ce tutoriel (sans prendre en compte mon propre rveil). Cela ne rendra que plus
joli votre rveil...
www.openclassrooms.com
Partie 2 : La 2D avec Qt
63/69
Avec tout ce que nous avons vu jusqu' maintenant et ventuellement quelques recherches du ct de QTimer, de QSound et de
QTimeEdit, vous devriez russir sans peine ce TP.
Bon codage et tout l'heure !
Correction
Vous avez termin de coder votre rveil ?
En voici la correction...
Un peu de maths
Une horloge est un disque. Pour faire un tour complet, une aiguille doit effectuer une rotation de 360.
La petite aiguille fait une rotation de 360 en douze heures ;
La grande aiguille fait une rotation de 360 en une heure ;
La trotteuse fait une rotation de 360 en une minute.
Calcul de la rotation de la petite aiguille (par heure, min et sec) :
12h = 360, donc 1h = 360/12 = 30, 1min = 30/60 = 0.5 et 1sec = 0.5/60 = 0.0083333333.
Ainsi, la petite aiguille parcourt 0.0083333333 en une seconde, 0.5 en une minute et 30 en une heure.
Calcul de la rotation de la grande aiguille (par min et sec) :
1h = 360, donc 1min = 360/60 = 6 et 1sec = 6/60 = 0.1.
Ainsi, la grande aiguille parcourt 0.1 en une seconde et 6 en une minute.
Calcul de la rotation de la trotteuse (par sec) :
1min = 360 donc 1sec = 360/60 = 6.
Ainsi, la petite aiguille parcourt 6 en une seconde.
return app.exec();
Personnellement, j'ai l'habitude de commencer par l'architecture de mes fentres lors de l'entreprise d'un projet. Une fois que j'ai
termin cela, je passe aux fonctionnalits diverses et varies que peut fournir mon programme. Je ne vais donc pas manquer la
rgle et je vais donc commencer par cette architecture. Comme surcharger le constructeur de la classe en la crant ne serait pas
sympa envers les personnes souffrant d'uglysourcecodophobie, et bien nous allons raliser l'architecture en utilisant des
fonctions personnalises.
www.openclassrooms.com
Partie 2 : La 2D avec Qt
64/69
La fonction creationMenus() est une fonction simple que vous avez dj tudie dans le tuto de M@teo21 :
Code : C++ - FenPrincipale.cpp
void FenPrincipale::creationMenus()
{
QMenu *menuFichier = menuBar()->addMenu("Fichier");
menuFichier->addAction("Quitter", qApp, SLOT(quit()));
QMenu *menuActions = menuBar()->addMenu("Actions");
menuActions->addAction("Sonnerie", this, SLOT(infosSonnerie()));
Et voici la fonction creationPendule() que je vais m'empresser d'expliquer tout de suite aprs vous l'avoir donne :
Code : C++ - FenPrincipale.cpp
void FenPrincipale::creationPendule()
{
scene = new QGraphicsScene;
scene->setSceneRect(-200, -200, 400, 400);
QPen penTroteuse(Qt::red, 2, Qt::SolidLine, Qt::RoundCap,
Qt::RoundJoin);
QPen penGrandeAiguille(Qt::darkGreen, 2, Qt::SolidLine,
Qt::RoundCap, Qt::RoundJoin);
QPen penPetiteAiguille(Qt::darkMagenta, 3, Qt::SolidLine,
Qt::RoundCap, Qt::RoundJoin);
grandeAiguille = scene->addLine(QLine(0, 0, 0, -100),
penGrandeAiguille);
petiteAiguille = scene->addLine(QLine(0, 0, 0, -50),
penPetiteAiguille);
troteuse = scene->addLine(QLine(0, 25, 0, -150), penTroteuse);
troteuse->setZValue(3);
grandeAiguille->setZValue(2);
petiteAiguille->setZValue(1);
QPen penPointCentral(Qt::red, 10, Qt::SolidLine, Qt::RoundCap,
Qt::RoundJoin);
QPolygon pointCentral;
pointCentral << QPoint(0, 1) << QPoint(1, 0);
QGraphicsItem *point = scene->addPolygon(pointCentral,
www.openclassrooms.com
Partie 2 : La 2D avec Qt
65/69
penPointCentral);
point->setZValue(4);
QGraphicsItem *fond = scene>addPixmap(QPixmap("fond_pendule.png"));
fond->setPos(-200, -200);
fond->setZValue(-1);
QGraphicsView *vue = new QGraphicsView(scene, this);
vue>setRenderHints(QPainter::Antialiasing|QPainter::TextAntialiasing);
vue->setGeometry(50, 50, 405, 405);
vue->setStyleSheet("background-color: #FFFFFF");
}
reglagesAffichage();
Juste aprs la cration de scene, je me suis servi de la fonction setSceneRect() dont l'effet avec les valeurs donnes est de
dcaler les points de la scne de manire ce que le point (0;0) soit centr et non situ en haut gauche de la scne. Puis je
dfinis des QPen qui serviront la couleur des aiguilles que je cre tout de suite aprs cela en leur appliquant les pinceaux crs.
Cette mthode ne devrait pas vous tre trangre, nous l'avons vue dans les chapitres prcdents. Afin d'viter les
superpositions des aiguilles, j'utilise setZValue() pour empiler les aiguilles dans l'ordre trotteuse, grande aiguille puis petite
aiguille (la trotteuse se situera donc au-dessus des autres aiguilles). Pour un effet de style, j'ajoute un gros point rouge situ au
centre de la scne et se positionnant au-dessus des aiguilles pour simuler le fait que la trotteuse ne soit pas une simple ligne. Je
finis par crer la vue ( laquelle j'applique l'anti-aliasing du contenu et du texte).
Et j'appelle une autre fonction qui permettra de placer les aiguilles au bon endroit dans la scne par rapport l'heure actuelle,
reglagesAffichage().
Mais avant de traiter cette fonction qui ne correspond pas exactement l'architecture, retournons dans le constructeur de la
classe FenPrincipale, il reste des choses faire !
Code : C++ - FenPrincipale.cpp
QLabel *label_titre_timer = new QLabel("<b>Heure du systme d'exploitation :</b>"
label_titre_timer->setGeometry(500, 50, 300, 25);
ordi_time = new QTimeEdit(this);
ordi_time->setGeometry(500, 90, 300, 25);
ordi_time->setTime(QTime::currentTime());
ordi_time->setEnabled(false);
label_time = new QLabel(this);
label_time->setGeometry(500, 115, 300, 50);
QLabel *label_titre_alarme = new QLabel("<b>Heure de l'alarme :</b>", this);
label_titre_alarme->setGeometry(500, 170, 300, 25);
Vous pouvez aisment remarquer que j'ai dfini deux QTimeEdit (ordi_time et heure_alarme) de la mme manire que la
cration de boutons. A votre stade, le fait que le mode de cration de widgets soit identique ne devrait pas vous tonner le
moins du monde.
Code : C++
ordi_time->setTime(QTime::currentTime());
www.openclassrooms.com
Partie 2 : La 2D avec Qt
66/69
FenPrincipale::reglagesAffichage()
h = QTime::currentTime().hour();
min = QTime::currentTime().minute();
sec = QTime::currentTime().second();
// Rotation de la troteuse :
troteuse->resetTransform();
troteuse->rotate(6 * sec);
// Rotation de la grande aiguille :
grandeAiguille->resetTransform();
grandeAiguille->rotate((0.1 * (min * 60)) + (0.1 * sec));
// Rotation de la petite aiguille :
petiteAiguille->resetTransform();
petiteAiguille->rotate((0.0083333333 * sec) + (0.5 * min) + (30 *
h));
}
Dsormais, nos aiguilles se placent correctement l'ouverture du programme, mais ce n'est pas encore le cas une seconde aprs.
Je prcise qu'avec :
Code : C++
int h = QTime::currentTime().hour();
int min = QTime::currentTime().minute();
int sec = QTime::currentTime().second();
Les variables h, min et sec contiennent respectivement la valeur de l'heure, des minutes et des secondes de l'ordinateur.
Je vous propose donc d'ajouter plusieurs choses dans le constructeur de la classe :
Code : C++
son_alarme = new QSound("play.wav", this); // play.wav correspond au son qui sera jou
www.openclassrooms.com
Partie 2 : La 2D avec Qt
67/69
Sur ce, je me permet de vous laisser ajouter un QTimer ayant une intervalle d'une seconde et se connectant la fonction
actualiserFenetre() lors de l'arrive du signal timeout().
Dans cette fonction actualiserFenetre(), nous avons plusieurs choses faire :
Vous vous souvenez du QLabel affichant l'heure ? Et bien le texte ne va pas s'actualiser tout seul, malheureusement.
La principale fonction d'un rveil est de sonner : on doit donc vrifier si c'est le moment d'envoyer le son.
Et en dernier plan, il faut mettre la position des aiguilles jour.
Pour cette dernire opration, nous allons tout simplement nous servir de la fonction reglagesAffichage(). Mais nous allons tout
de mme passer par une nouvelle fonction qui actualisera l'instance de QLabel dont nous parlions.
Code : C++
void FenPrincipale::actualiserFenetre()
{
ordi_time->setTime(QTime::currentTime());
if(ordi_time->text() == heure_alarme->text())
{
son_alarme->setLoops(-1);
son_alarme->play();
}
label_time->setText("Il est actuellement " +
QString::number(QTime::currentTime().hour())
+ " heure(s), " + QString::number(QTime::currentTime().minute())
+ " minute(s) et " +
QString::number(QTime::currentTime().second()) + " seconde(s).");
}
reglagesAffichage();
C'est ainsi que nous terminons la plus grosse partie du rveil, la partie qui sert entre guillemets " quelque chose".
Pour les dernires lignes de codes, nous allons juste crer les fonctions appeles par les menus :
Code : C++
void FenPrincipale::infosSonnerie()
{
QMessageBox::information(this, "Rveil", "Cidessous se trouvent les informations concernant la sonnerie :<br />"
"Nom du son : " + son_alarme->fileName() + "<br />"
"Heure de la sonnerie : " + heure_alarme->time().toString());
}
void FenPrincipale::ouvrirApropos()
{
QMessageBox::information(this, "Rveil", "Merci d'utiliser ce rveil.<br />"
"Il a t cod par Amnell dans le cadre <span style='textdecoration: line-through;'>de son usage personnel</span>"
" du TP sur la gestion de la 2D avec Qt.");
}
Ides d'amlioration
www.openclassrooms.com
Partie 2 : La 2D avec Qt
68/69
Qt's Programmers
L'quipe officielle du tutoriel !
www.openclassrooms.com