Etape 8
Etape 8
Etape 8
1 - Structure d'un projet graphique...................................................................................................................................................2 Cration du projet.................................................................................................................................................................2 La fonction main()................................................................................................................................................................2 2 - Dessin du dialogue......................................................................................................................................................................3 L'diteur graphique...............................................................................................................................................................3 Mise en place des widgets....................................................................................................................................................4 Connexions...........................................................................................................................................................................4 3 - criture du programme...............................................................................................................................................................5 Dclaration de la fonction f_proposer()................................................................................................................................6 Dfinition de la fonction f_proposer()..................................................................................................................................6 La variable m_tirage.............................................................................................................................................................7 Dfinition et appel de la fonction prepareNouvellePartie()..................................................................................................7 Initialisation du gnrateur de nombres pseudo-alatoires...................................................................................................8 4 - Exercice......................................................................................................................................................................................8
Document du 22/11/11
2/8
Nous pouvons dsormais envisager la cration de programmes dont l'interface utilisateur prsente l'aspect attendu par nos contemporains : menus, boutons, icnes etc. L'essentiel du travail de programmation ncessaire a dj t fait (par les programmeurs de Trolltech) et est mis notre disposition sous la forme d'une vaste collection de classes (la librairie Qt). Notre premier programme utilisant une interface graphique sera une nouvelle version de "Devine un nombre". Notre exprience antrieure concernant l'criture de ce programme va nous permettre de nous concentrer sur les spcificits imposes par l'interface graphique.
. et
Remarquez que, dans la "Liste des modules requis", le module "QtGui" que nous avions du cocher nous-mmes lors de l'tape 7 est maintenant obligatoire . La cration d'un projet graphique implique une tape supplmentaire, car l'interface de notre programme va tre dcrite par une classe dont il nous faut maintenant choisir le nom et la nature.
Dans le champ "Nom de la classe :", tapez notreClasse Dans la liste "Classe parent :", choisissiez QDialog .
Nous allons, certes, indiquer certaines des proprits que nous souhaitons voir adopter par notreClasse. Mais nous n'avons pas l'ambition de crer cette classe ex nihilo. Nous allons nous appuyer sur une classe fournie par Qt, laquelle nous allons simplement ajouter les spcificits qui nous intressent. Plusieurs points de dpart sont envisageables, et le choix propos par dfaut (QMainWindow) conduit un projet un peu trop complexe pour ce que nous entendons faire. C'est la raison pour laquelle nous nous contenterons de crer un QDialog.
La fonction main() Par rapport aux projets "console" auxquels nous sommes habitus, un projet graphique se singularise par le fait que la fonction main() instancie notreClasse (4), puis excute une fonction membre au titre de l'instance cre (5). La fonction show() ordonne au dialogue de dessiner sa fentre sur l'cran, de faon ce que l'utilisateur puisse manipuler les lments d'interface qu'elle propose.
La fonction exec(), pour sa part, attend que l'utilisateur mette fin au programme (en cliquant sur la case de fermeture de la fentre, par exemple), tout en veillant ce que ses actions soient prises en compte.
1 2 3 4 5 6 7
int main(int argc, char *argv[]) { QApplication a(argc, argv); notreClasse w; w.show(); return a.exec(); } Dans un projet graphique, il n'est souvent pas ncessaire de modifier la fonction main(). Nos efforts vont plutt viser modifier notreClasse, de faon ce que l'appel de la fonction exec() se traduise par le comportement que nous attendons de notre programme.
Les lignes 4 et 5 de la fonction main() sont deux les seules lignes du programme qui utilisent notreClasse. Tout le code que nous allons crire ne fera que dfinir notreClasse.
3/8
2 - Dessin du dialogue
QtCreator
La premire est trs classique : nous pouvons simplement modifier les fichiers notreClasse.h et notreClasse.cpp pour ajouter notre guise des variables et des fonctions membre, exactement comme nous l'avons fait pour la classe CEtudiant lors de l'tape 7. La seconde est beaucoup plus originale : nous disposons d'un diteur graphique qui nous permet de spcifier l'apparence que nous souhaitons donner notre dialogue.
Le dessin que nous produirons l'aide de l'diteur graphique donnera automatiquement naissance un texte source en C++. La compilation de ce texte gnrera du code dont l'excution se traduira par l'apparition d'une fentre exactement conforme notre dessin.
L'diteur graphique Les projets graphiques comportent un fichier qui n'est ni un .h ni un .cpp, mais un .ui (acronyme de user interface). Ces fichiers sont rangs dans la catgorie " Formulaires" (cf. ci-contre). Ouvrez le fichier notreclasse.ui en double cliquant sur son nom . L'diteur graphique apparat alors l'cran. Il s'agit d'un environnement de travail fortement multifentr et trs configurable, manifestement conu et optimis pour des stations de travail disposant d'un cran de grande taille. Il se prsente typiquement sous la forme suivante :
La surface grise occupant ici la place centrale est le "fond" de la fentre sur lequel nous allons disposer les diffrents lments qui vont composer l'interface de notre programme. La liste qui figure ici gauche propose des lments d'interface (widgets). L'ide gnrale est de venir piocher dans cette liste et de faire glisser sur le fond les widgets dont nous avons besoin. Les autres fentres proposent diffrentes informations dont la nature dpend du widget slectionn.
Si votre cran s'avre trop petit pour afficher confortablement toutes les fentres reprsentes ci-dessus, vous pouvez refermer certaines d'entre elles.
4/8
L'interface de notre programme comporte cinq widgets : - un TextEdit qui servira afficher les messages ("trop grand", "trop petit", "bravo !", etc.) - un Label qui rappelle les rgles du jeu - un LCDNumber qui affichera la valeur envisage par l'utilisateur - un PushButton qui permettra de soumettre la valeur envisage au verdict du programme - un Horizontal Slider qui permettra de modifier la valeur envisage. Positionnez ces cinq widgets et ajustez leur taille de faon reproduire approximativement le dialogue reprsent ci-contre . Veillez bien faire glisser le bon type de widget.
Un TextEdit, par exemple, n'est pas un PlainTextEdit ou un LineEdit...
Pour modifier l'aspect d'un widget, slectionnez-le et modifiez la proprit concerne dans la fentre qui prsente des couples proprit/valeur sur des lignes alternativement plus ou moins teintes. Le code que nous allons crire suppose que l'Horizontal Slider a pour Modifiez ces deux proprits pour leur confrer ces valeurs .
objectName
proposition et pour
maximum
32.
Pour modifier une proprit d'un widget, il faut commencer par le slectionner. Veillez ne JAMAIS changer l'objectName du dialogue lui-mme (sa valeur doit rester notreClasse).
Vous pouvez aussi modifier les proprits suivantes (ces proprits ne sont pas ncessaires au fonctionnement du programme, mais amliorent son apparence) : PushButton : la proprit text vaut "Proposer" TextEdit : la proprit read only est coche (pour empcher l'utilisateur d'insrer du texte dans le widget). Label : la proprit font/Gras est coche (et le text est videmment modifi !) LCDNumber : numDigits vaut 2 et segmentStyle est flat Horizontal Slider : tickPosition vaut TicksBothSides (pour faire apparatre les graduations). Connexions Maintenant que l'aspect visuel de l'interface est spcifi, il convient de commencer s'intresser au comportement du programme. Deux phnomnes nous concernent : - lorsque l'utilisateur dplace le curseur, la valeur affiche par le LCDNumber doit changer. - lorsque l'utilisateur clique sur le bouton, une fonction que nous allons crire doit tre excute.
C'est cette fonction qui devra procder aux comparaisons et affichages qui composaient l'essentiel de notre version "console" du programme "Devine un nombre".
La liaison entre la position du curseur et la valeur affiche par le LCDNumber ne ncessite aucune criture de code : il s'agit simplement d'indiquer qu'un vnement (le dplacement du curseur) doit en dclencher un autre (la modification de la valeur affiche). Les widgets dtectent certains vnements les concernant, et l'diteur graphique permet d'tablir directement ce type de connexion. Dans le menu <Edition>, choisissez la commande <Editer signaux/slots> Pour Qt, les signaux d'un widget sont des fonctions qui sont appeles automatiquement lorsque survient un vnement qui concerne ce widget. Cliquez sur le Horizontal Slider et, tout en tenant le bouton de la souris enfonc, faites glisser son pointeur vers le LCDNumber. Lorsque ce dernier devient rouge, relchez le bouton de la souris . Le dialogue reprsent ci-contre apparat alors. .
5/8
Les deux vnements qui nous intressent sont le changement de la valeur correspondant la position du curseur et le changement de l'affichage propos par le LCDNumber. Le premier de ces vnements se traduit par l'mission du signal valueChanged() par le curseur, alors que le second sera caus par l'excution de la fonction display() au titre du LCDNumber. Slectionnez ces deux fonctions et cliquez sur [OK] .
Ce que nous venons de dire, c'est que lorsque la fonction valueChanged() est excute au titre de proposition, elle doit appeler la fonction display() au titre du LCDNumber.
Pour Qt, les fonctions qui peuvent tre lies des signaux s'appellent des slots.
Nous venons donc de connecter le signal valueChanged() d'un QHorizontalSlider au slot display() d'un QLCDNumber. Remarquez que ces deux fonctions disposent d'un paramtre, qui permet valueChanged() de recevoir la valeur correspondant la position du curseur et, le moment venu, de la transmettre display().
Nous devons maintenant connecter le bouton [Proposer] une fonction... qui n'existe pas encore puisque nous devrons l'crire. Nous allons donc simplement indiquer comment elle s'appellera, et QtCreator s'arrangera pour la trouver lorsqu'il en aura besoin. Cliquez sur le pushButton [Proposer] et, tout en maintenant le bouton de la souris enfonc, faites glisser son pointeur en dehors du pushButton, sur le fond du dialogue. Relchez alors le bouton de la souris . Le signal qui nous intresse est videmment celui qui est mis lorsque l'utilisateur clique sur le bouton (la fonction clicked()), mais aucun des slots proposs ne correspond au traitement que nous attendons (Qt ne comporte aucune fonction spcifiquement prvue pour jouer "Devine un nombre"). Il faut donc que nous annoncions que notre programme va comporter une fonction nomme f_proposer(). Cliquez sur le bouton [Editer] .
Dans le dialogue suivant (cf. ci-contre), cliquez sur le symbole " +" de la zone "Slots" . Un nouveau slot est cr. Donnez-lui pour nom "f_proposer()" . La liste de slots propose par le dialogue de connexion comporte maintenant notre nouvelle fonction. Slectionner-la (ainsi que l'vnement clicked(), si ce n'est dj fait) et cliquez sur le bouton [OK] .
Nous venons de connecter le signal f_proposer() de notreClasse. clicked() d'un QPushButton au slot
"Editeur de signaux/slots",
un rsum
Cette connexion tait la dernire opration ncessitant l'diteur graphique. Revenez l'diteur de texte en cliquant sur l'icne Editer dans la barre latrale .
3 - criture du programme
Il reste maintenant crire les fonctions membre qui effectueront les traitements ncessaires au fonctionnement du programme. La principale d'entre-elles est videmment celle qui sera appele chaque clic sur le bouton [Proposer], le slot f_proposer().
6/8
Dclaration de la fonction f_proposer() Il s'agit d'une fonction membre de notreClasse, et il faut donc que sa dclaration soit prsente dans la dfinition de cette classe. Il s'agit galement d'un slot, et ceci doit tre prcis en faisant figurer la dclaration dans une section spcialement prvue cet effet :
1 2 3 4 5 6 7 8 9 10 11
class notreClasse : public QDialog { Q_OBJECT public: explicit notreClasse(QWidget *parent = 0); ~notreClasse(); public slots: void f_proposer(); private: Ui::notreClasse *ui; }; Ajoutez les lignes 6 et 7 la dfinition de notreClasse .
Les titres public:, public slots: et protected: dlimitent des sections dans la classe. Si vous devez ajouter d'autres slots, il est inutile de rpter le titre public slots:, il suffit de placer la dclaration de ces fonctions dans la bonne section (ie. entre public slots: et le titre suivant).
Dfinition de la fonction f_proposer() La premire chose que doit faire cette fonction est de rcuprer la valeur slectionne par l'utilisateur au moyen du curseur. Cette opration est ralise en appelant une fonction membre de la classe QHorizontalSlider au titre de l'objet qui reprsente ce curseur. Pour viter de mler les variables qui reprsentent des widgets issus du dessin produit avec l'diteur graphique aux autres variables, QtCreator cre une classe, l'instancie et ajoute notreClasse une variable membre nomme ui qui permet d'accder nos widgets (cf. ligne 11 ci-dessus). Si un widget a t nomm truc dans l'diteur graphique, on le dsigne par ui->truc dans le code. La fonction membre de QHorizontalSlider qui nous intresse ici s'appelle value(). Elle renvoie tout simplement la valeur qui correspond la position du curseur au titre de laquelle elle est appele :
1 2 3
void notreClasse::f_proposer() { //on rcupre la proposition faite par le joueur int valeurProposee = ui->proposition->value(); Une fois cette valeur rcupre dans valeurProposee, nous allons devoir la comparer notre tirage et afficher nos conclusions. Cet affichage va utiliser le QTextEdit que nous avons plac dans notre dialogue et baptis verdict. Il est trs facile d'afficher du texte dans un QTextEdit, condition que ce texte soit stock dans une QString. Tout ce que vous savez des std::string s'applique directement au QString
Il aurait sans doute t prfrable de ne jamais parler de std::string et d'utiliser directement des QString. Malheureusement, les flux standard qui permettent d'utiliser la console (std::cin et std::cout) ignorent tout de Qt et, donc, de la classe QString.
Aprs avoir cr une QString nomme aAfficher (4), le programme utilise une autre classe de la librairie Qt. Cette classe, nomme QTextStream, ressemble beaucoup aux flux std::cin et std::cout, mais offre une possibilit tout fait originale : on peut choisir la destination relle (ou la provenance) du texte insr dans un (ou extrait d'un) QTextStream l'aide de l'oprateur << (ou de l'oprateur >>). Cette destination peut, par exemple, tre une imprimante, un fichier ou, ce qui est plus pertinent ici, une variable de type QString.
4 5
//on prpare une QString dguise en std::cout QString aAfficher; QTextStream out(&aAfficher); Une fois le QTextStream "branch" sur notre QString (5), on peut l'utiliser d'une faon qui devrait vous paratre familire (6-11) :
7/8
6 7 8 9 10 11 12 13
//calcul du message if(valeurProposee < m_tirage) out << valeurProposee << " est trop petit"; if(valeurProposee > m_tirage) out << valeurProposee << " est trop grand"; if(valeurProposee == m_tirage) { out << "Bravo ! Je tire un nouveau nombre..."; prepareNouvellePartie(); //tirage d'une nouvelle valeur et autres prparatifs } Lorsque la partie s'achve, le programme procde immdiatement un nouveau tirage. Cette tche est dlgue une fonction prepareNouvellePartie(), qui devra tre aussi appele en dbut de programme (pour prparer la premire partie) et pourra ventuellement remettre zro des compteurs de coups ou de parties utiliss dans une version future du programme. Il ne reste plus la fonction f_proposer() qu' afficher le texte contenu dans la QString. Les QTextEdit proposent une fonction append() qui ajoute la chane qui lui est passe la fin du texte prcdemment affich par le widget :
14 15
//affichage du message dans le QTextEdit ui->verdict->append(aAfficher); } Ajoutez votre fichier notreClasse.cpp la dfinition de la fonction f_proposer() dcrite ci-dessus
L'usage d'un QTextEdit exige l'ajout d'une directive #include <qtextedit> en tte de fichier.
La variable m_tirage Cette variable devant contenir la solution de la devinette, elle est videmment de type int. Nous savons que sa valeur doit tre fixe par prepareNouvellePartie() et utilise par f_proposer(). Celle-ci ne dispose d'aucun paramtre et n'est pas appele par prepareNouvellePartie() : m_tirage ne peut donc tre une variable locale prepareNouvellePartie() dont la valeur serait transmise f_proposer() lors de l'appel de cette dernire. Nos deux fonctions sont toutefois membre de notreClasse. Elles peuvent donc toutes deux accder aux variables membre de l'instance au titre de laquelle elles sont excutes.
Dans notre cas, il n'existe qu'une instance de notreClasse : la variable w dfinie dans main(). C'est au titre de cette variable que main() appelle show(), c'est donc cette variable qu'appartiennent les widgets qui apparaissent l'cran. Lorsque l'utilisateur clique sur le bouton [Proposer], il est donc logique que le slot f_proposer() soit appel au titre de w. Lorsque f_proposer() appelle son tout prepareNouvellePartie(), c'est donc (implicitement) galement au titre de w (12).
La variable m_tirage doit donc tre membre de notreClasse. Ajoutez sa dclaration dans le fichier notreClasse.h
Une variable membre n'est pas un slot (un slot est une fonction !) et la dclaration de m_tirage ne doit donc pas tre place dans la section public slots:. Placez-la plutt dans la section protected: (la section public: n'a normalement pas vocation contenir des dclarations de variables).
Dfinition et appel de la fonction prepareNouvellePartie() Le rle de cette fonction est trs simple et son code se passe de commentaire :
1 2 3 4
Un dernier dtail doit cependant tre rgl. Cette fonction doit en effet tre excute une premire fois lors du lancement du programme. Une premire faon de procder serait d'ajouter un appel cette fonction dans main() : w.prepareNouvellePartie();
8/8
La fonction main() ne faisant pas partie de notreClasse, elle n'est pas excute au titre d'une instance de celle-ci. Elle ne peut donc accder un membre de notreClasse (ici, la fonction nouvellePartie()) qu'en indiquant explicitement au titre de quelle instance cet accs doit tre fait (ici, la variable w).
Il n'est toutefois pas conseill de confier la responsabilit de la "mise en tat de marche" d'une instance au programmeur qui cre cette instance (et qui ne connait pas forcment tous les dtails de fonctionnement interne de la classe). On prfre donc effectuer ce genre d'oprations dans une fonction membre spciale, qui est excute automatiquement lors de l'instanciation. Cette fonction s'appelle un constructeur, et QtCreator l'a dj prpare pour nous dans le fichier notreClasse.cpp :
1 2 3 4 5 6 7
notreClasse::notreClasse(QWidget *parent) : QDialog(parent), ui(new Ui::notreClasse) { ui->setupUi(this); prepareNouvellePartie(); } Ajoutez la ligne 6 au constructeur de notreClasse .
Initialisation du gnrateur de nombres pseudo-alatoires Si nous ne voulons pas retrouver la mme squence de nombres chaque lancement du programme, nous devons faire en sorte que srand() soit excute une fois et une seule avant le dbut de la premire partie. Placer l'appel srand() dans le constructeur de notreClasse ne garantirait pas absolument que cette instruction ne sera excute qu'une seule fois. En effet, le constructeur va tre excut lors de chaque instanciation de notreClasse. Notre programme n'en comporte pour l'instant qu'une seule (dans main(), nous l'avons vu), mais rien ne prouve qu'il en sera toujours ainsi. Il est donc prfrable d'insrer l'appel srand() dans la fonction main()
1 2 3 4 5 6 7 8
int main(int argc, char *argv[]) { QApplication a(argc, argv); srand(time(0)); notreClasse w; w.show(); return a.exec(); }
L'appel time() peut ncessiter une directive #include <ctime> L'appel srand() peut ncessiter une directive #include <cstdlib>
Si le compilateur met des protestations telles que celles reprsentes cicontre, ouvrez le fichier notreClasse.ui et vrifiez que vous n'avez pas chang par inadvertance l'objectName du dialogue (qui doit tre "notreClasse").
4 - Exercice
Sauriez-vous doter cette version de "Devine un nombre" des fonctionnalits "avances" que nous avions prvues pour la version console (compter le nombre de parties, faire la moyenne du nombre de coups par partie, etc) ?
Vous pouvez commencer par afficher ces informations dans le textEdit. Une fois rgls les problmes de cration de variables et de calcul, vous pouvez essayer de rajouter des widgets dans le dialogue pour obtenir un affichage plus conforme aux normes visuelles actuelles (des LCDNumber, par exemple).