Mis à jour le 04/12/2018
  • 50 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

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

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

J'ai tout compris !

TP : ZeroClassGenerator

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

Je pense que le moment est bien choisi de vous exercer avec un petit TP. En effet, vous avez déjà vu suffisamment de choses sur Qt pour être en mesure d'écrire dès maintenant des programmes intéressants.

Notre programme s'intitulera le ZeroClassGenerator… un programme qui génère le code de base des classes C++ automatiquement, en fonction des options que vous choisissez !

Notre objectif

Ne vous laissez pas impressionner par le nom « ZeroClassGenerator ». Ce TP ne sera pas bien difficile et réutilisera toutes les connaissances que vous avez apprises pour les mettre à profit dans un projet concret.

Ce TP est volontairement modulaire : je vais vous proposer de réaliser un programme de base assez simple, que je vous laisserai coder et que je corrigerai ensuite avec vous. Puis, je vous proposerai un certain nombre d'améliorations intéressantes (non corrigées) pour lesquelles il faudra vous creuser un peu plus les méninges si vous êtes motivés.

Notre ZeroClassGenerator est un programme qui génère le code de base des classes C++. Qu'est-ce que cela veut dire ?

Un générateur de classes C++

Ce programme est un outil graphique qui va créer automatiquement le code source d'une classe en fonction des options que vous aurez choisies.

Vous n'avez jamais remarqué que les classes avaient en général une structure de base similaire, qu'il fallait réécrire à chaque fois ? C'est un peu laborieux parfois. Par exemple :

#ifndef HEADER_MAGICIEN
#define HEADER_MAGICIEN

class Magicien : public Personnage
{
    public:
    Magicien();
    ~Magicien();

    protected:

    private:
};

#endif

Rien que pour cela, il serait pratique d'avoir un programme capable de générer le squelette de la classe, de définir les portéespublic,protectedetprivate, de définir un constructeur par défaut et un destructeur, etc.

Nous allons réaliser une GUI (une fenêtre) contenant plusieurs options. Plutôt que de faire une longue liste, je vous propose une capture d'écran du programme final à réaliser (figure suivante).

Le ZeroClassGenerator
Le ZeroClassGenerator

La fenêtre principale est en haut à gauche, en arrière-plan. L'utilisateur renseigne obligatoirement le champ « Nom », pour indiquer le nom de la classe. Il peut aussi donner le nom de la classe mère.

On propose quelques cases à cocher pour choisir des options comme « Protéger le header contre les inclusions multiples ». Cela consiste à placer les lignes qui commencent par un#comme#ifndefet qui évitent que le même fichier.hpuisse être inclus deux fois dans un même programme.

Enfin, on donne la possibilité d'ajouter des commentaires en haut du fichier pour indiquer l'auteur, la date de création et le rôle de la classe. C'est une bonne habitude, en effet, que de commenter un peu le début de ses classes pour avoir une idée de ce à quoi elle sert.

Lorsqu'on clique sur le bouton « Générer » en bas, une nouvelle fenêtre s'ouvre (uneQDialog). Elle affiche le code généré dans unQTextEditet vous pouvez à partir de là copier/coller ce code dans votre IDE comme Code::Blocks ou Qt Creator.

C'est un début et je vous proposerai à la fin du chapitre des améliorations intéressantes à ajouter à ce programme. Essayez déjà de réaliser cela correctement, cela représente un peu de travail, je peux vous le dire !

Quelques conseils techniques

Avant de vous lâcher tels des fauves dans la jungle, je voudrais vous donner quelques conseils techniques pour vous guider un peu.

Architecture du projet

Je vous recommande de faire une classe par fenêtre. Comme on a deux fenêtres et qu'on met toujours lemainà part, cela fait cinq fichiers :

  • main.cpp: contiendra uniquement lemainqui ouvre la fenêtre principale (très court) ;

  • FenPrincipale.h: header de la fenêtre principale ;

  • FenPrincipale.cpp: implémentation des méthodes de la fenêtre principale ;

  • FenCodeGenere.h: header de la fenêtre secondaire qui affiche le code généré ;

  • FenCodeGenere.cpp: implementation de ses méthodes.

Pour la fenêtre principale, vous pourrez hériter deQWidgetcomme on l'a toujours fait, cela me semble le meilleur choix.
Pour la fenêtre secondaire, je vous conseille d'hériter deQDialog. La fenêtre principale ouvrira laQDialogen appelant sa méthodeexec().

La fenêtre principale

Je vous conseille très fortement d'utiliser des layouts. Mon layout principal, si vous regardez bien ma capture d'écran, est un layout vertical. Il contient desQGroupBox.
À l'intérieur desQGroupBox, j'utilise à nouveau des layouts. Je vous laisse le choix du layout qui vous semble le plus adapté à chaque fois.

Pour leQGroupBox« Ajouter des commentaires », il faudra ajouter une case à cocher. Si cette case est cochée, les commentaires seront ajoutés. Sinon, on ne mettra pas de commentaires. Renseignez-vous sur l'utilisation des cases à cocher dans lesQGroupBox.

Pour le champ « Date de création », je vous propose d'utiliser unQDateEdit. Nous n'avons pas vu ce widget au chapitre précédent mais je vous fais confiance, il est proche de laQSpinBoxet, après lecture de la documentation, vous devriez savoir vous en servir sans problème.

Vous « dessinerez » le contenu de la fenêtre dans le constructeur deFenPrincipale. Pensez à faire de vos champs de formulaire des attributs de la classe (lesQLineEdit,QCheckbox…), afin que toutes les autres méthodes de la classe aient accès à leur valeur.

Lors d'un clic sur le bouton « Générer ! », appelez un slot personnalisé. Dans ce slot personnalisé (qui ne sera rien d'autre qu'une méthode deFenPrincipale), vous récupérerez toutes les informations contenues dans les champs de la fenêtre pour générer le code dans une chaîne de caractères (de typeQStringde préférence).
C'est là qu'il faudra un peu réfléchir sur la génération du code mais c'est tout à fait faisable.

Une fois le code généré, votre slot appellera la méthodeexec()d'un objet de typeFenCodeGenereque vous aurez créé pour l'occasion. La fenêtre du code généré s'affichera alors…

La fenêtre du code généré

Beaucoup plus simple, cette fenêtre est constituée d'unQTextEditet d'un bouton de fermeture.

Pour leQTextEdit, essayez de définir une police à chasse fixe (comme « Courier ») pour que cela ressemble à du code. Personnellement, j'ai rendu leQTextEditen modereadOnlypour qu'on ne puisse pas modifier son contenu (juste le copier), mais vous faites comme vous voulez.

Vous connecterez le bouton « Fermer » à un slot spécial de laQDialogqui demande la fermeture et qui indique que tout s'est bien passé. Je vous laisse trouver dans la documentation duquel il s'agit.

Minute euh… Comment je passe le code généré (de typeQStringsi j'ai bien compris) à la seconde fenêtre de typeQDialog?

Le mieux est de passer cetteQStringen paramètre du constructeur. Votre fenêtre récupèrera ainsi le code et n'aura plus qu'à l'afficher dans sonQTextEdit!

Allez hop hop hop, au boulot, à vos éditeurs ! Vous aurez besoin de lire la documentation plusieurs fois pour trouver la bonne méthode à appeler à chaque fois, donc n'ayez pas peur d'y aller.

On se retrouve dans la partie suivante pour la… correction !

Correction

Ding ! C'est l'heure de ramasser les copies !

Bien que je vous aie donné quelques conseils techniques, je vous ai volontairement laissé le choix pour certains petits détails (comme « quelles cases sont cochées par défaut »). Vous pouviez même présenter la fenêtre un peu différemment si vous vouliez.
Tout cela pour dire que ma correction n'est pas la correction ultime. Si vous avez fait différemment, ce n'est pas grave. Si vous n'avez pas réussi, ce n'est pas grave non plus, pas de panique : prenez le temps de bien lire mon code et d'essayer de comprendre ce que je fais. Vous devrez être capables par la suite de refaire ce TP sans regarder la correction.

main.cpp

Comme prévu, ce fichier est tout bête et ne mérite même pas d'explication.

#include <QApplication>
#include "FenPrincipale.h"

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);

    FenPrincipale fenetre;
    fenetre.show();

    return app.exec();
}

Je signale simplement qu'on aurait pu charger la langue française comme on l'avait fait dans le chapitre sur les boîtes de dialogue, afin que les menus contextuels et certains boutons automatiques soient traduits en français. Mais c'est du détail, cela ne se verra pas vraiment sur ce projet.

FenPrincipale.h

La fenêtre principale hérite deQWidgetcomme prévu. Elle utilise la macroQ_OBJECTcar nous définissons un slot personnalisé :

#ifndef HEADER_FENPRINCIPALE
#define HEADER_FENPRINCIPALE

#include <QtWidgets>

class FenPrincipale : public QWidget
{
    Q_OBJECT

    public:
    FenPrincipale();

    private slots:
    void genererCode();

    private:
    QLineEdit *nom;
    QLineEdit *classeMere;
    QCheckBox *protections;
    QCheckBox *genererConstructeur;
    QCheckBox *genererDestructeur;
    QGroupBox *groupCommentaires;
    QLineEdit *auteur;
    QDateEdit *date;
    QTextEdit *role;
    QPushButton *generer;
    QPushButton *quitter;

};

#endif

Ce qui est intéressant, ce sont tous les champs de formulaire que j'ai mis en tant qu'attributs (privés) de la classe. Il faudra les initialiser dans le constructeur. L'avantage d'avoir défini les champs en attributs, c'est que toutes les méthodes de la classe y auront accès et cela nous sera bien utile pour récupérer les valeurs des champs dans la méthode qui génèrera le code source.

Notre classe est constituée de deux méthodes, ce qui est ici largement suffisant :

    • FenPrincipale(): c'est le constructeur. Il initialisera les champs de la fenêtre, jouera avec les layouts et placera les champs à l'intérieur. Il fera des connexions entre les widgets et indiquera la taille de la fenêtre, son titre, son icône…

    • genererCode(): c'est une méthode (plus précisément un slot) qui sera connectée au signal « On a cliqué sur le bouton Générer ». Dès qu'on clique sur le bouton, cette méthode est appelée.

J'ai mis le slot en privé car il n'y a pas de raison qu'une autre classe l'appelle, mais j'aurais aussi bien pu le mettre public.

FenPrincipale.cpp

Bon, là, c'est le plus gros morceau. Il n'y a que deux méthodes (le constructeur et celle qui génère le code) mais elles sont volumineuses.

Pour conserver la lisibilité de cet ouvrage et éviter de vous assommer avec un gros code source, je vais vous présenter uniquement le canevas du code qu'il fallait réaliser. Vous trouverez le détail complet en téléchargeant le code source à l'aide du code web présenté plus loin.

#include "FenPrincipale.h"
#include "FenCodeGenere.h"

FenPrincipale::FenPrincipale()
{

    // Création des layouts et des widgets
    // …

    // Connexions des signaux et des slots
    connect(quitter, SIGNAL(clicked()), qApp, SLOT(quit()));
    connect(generer, SIGNAL(clicked()), this, SLOT(genererCode()));

}

void FenPrincipale::genererCode()
{
    // On vérifie que le nom de la classe n'est pas vide, sinon on arrête
    if (nom->text().isEmpty())
    {
        QMessageBox::critical(this, "Erreur", "Veuillez entrer au moins un nom de classe");
        return; // Arrêt de la méthode
    }

    // Si tout va bien, on génère le code
    QString code;
    
    // Génération du code à l'aide des informations de la fenêtre
    // …

    // On crée puis affiche la fenêtre qui affichera le code généré, qu'on lui envoie en paramètre
    FenCodeGenere *fenetreCode = new FenCodeGenere(code, this);
    fenetreCode->exec();
}

Le constructeur n'est pas compliqué à écrire, il consiste juste à placer et organiser ses widgets. Par contre, le slotgenererCodea demandé du travail de réflexion car il faut construire la chaîne du code source. Le slot récupère la valeur des champs de la fenêtre (via des méthodes commetext()pour lesQLineEdit).

UnQStringcodeest généré en fonction des choix que vous avez fait.
Une erreur se produit et la méthode s'arrête s'il n'y a pas au moins un nom de classe défini.

Tout à la fin degenererCode(), on n'a plus qu'à appeler la fenêtre secondaire et à lui envoyer le code généré :

FenCodeGenere *fenetreCode = new FenCodeGenere(code, this);
fenetreCode->exec();

Le code est envoyé lors de la construction de l'objet. La fenêtre sera affichée lors de l'appel àexec().

FenCodeGenere.h

La fenêtre du code généré est beaucoup plus simple que sa parente :

#ifndef HEADER_FENCODEGENERE
#define HEADER_FENCODEGENERE

#include <QtWidgets>

class FenCodeGenere : public QDialog
{
    public:
    FenCodeGenere(QString &code, QWidget *parent);

    private:
    QTextEdit *codeGenere;
    QPushButton *fermer;
};

#endif

Il y a juste un constructeur et deux petits widgets de rien du tout. ;-)

FenCodeGenere.cpp

Le constructeur prend deux paramètres :

  • une référence vers leQStringqui contient le code ;

  • un pointeur vers la fenêtre parente.

#include "FenCodeGenere.h"

FenCodeGenere::FenCodeGenere(QString &code, QWidget *parent = 0) : QDialog(parent)
{
    codeGenere = new QTextEdit();
    codeGenere->setPlainText(code);
    codeGenere->setReadOnly(true);
    codeGenere->setFont(QFont("Courier"));
    codeGenere->setLineWrapMode(QTextEdit::NoWrap);

    fermer = new QPushButton("Fermer");

    QVBoxLayout *layoutPrincipal = new QVBoxLayout;
    layoutPrincipal->addWidget(codeGenere);
    layoutPrincipal->addWidget(fermer);

    resize(350, 450);
    setLayout(layoutPrincipal);

    connect(fermer, SIGNAL(clicked()), this, SLOT(accept()));
}

C'est un rappel mais je pense qu'il ne fera pas de mal : le paramètreparentest transféré au constructeur de la classe-mèreQDialogdans cette ligne :

FenCodeGenere::FenCodeGenere(QString &code, QWidget *parent = 0) : QDialog(parent)

Schématiquement, le transfert se fait comme à la figure suivante.

Héritage et passage de paramètre

Téléchargez le projet

Je vous invite à télécharger le projet zippé :

Télécharger le projet ZeroClassGenerator

Ce zip contient :

  • les fichiers source.cppet.h;

  • le projet.cbppour ceux qui utilisent Code::Blocks ;

  • l'exécutable Windows et son icône (attention : il faudra mettre les DLL de Qt dans le même dossier si vous voulez que le programme puisse s'exécuter).

Des idées d'améliorations

Vous pensiez en avoir fini ?
Que nenni ! Un tel TP n'attend qu'une seule chose : être amélioré !

Voici une liste de suggestions qui me passent par la tête pour améliorer le ZeroCodeGenerator mais vous pouvez inventer les vôtres :

    • Lorsqu'on coche « Protéger le header contre les inclusions multiples », un define (aussi appelé header guard) est généré. Par défaut, ce header guard est de la formeHEADER_NOMCLASSE. Pourquoi ne pas l'afficher en temps réel dans un libellé lorsqu'on tape le nom de la classe ? Ou mieux, affichez-le en temps réel dans unQLineEditpour que la personne puisse le modifier si elle le désire. Le but est de vous faire travailler les signaux et les slots.

    • Ajoutez d'autres options de génération de code. Par exemple, vous pouvez proposer d'inclure le texte légal d'une licence libre (comme la GPL) dans les commentaires d'en-tête si la personne fait un logiciel libre ; vous pouvez demander quels headers inclure, la liste des attributs, générer automatiquement les accesseurs pour ces attributs, etc. Attention, il faudra peut-être utiliser des widgets de liste un peu plus complexes, comme leQListWidget.

    • Pour le moment on ne génère que le code du fichier.h. Même s'il y a moins de travail, ce serait bien de générer aussi le.cpp. Je vous propose d'utiliser unQTabWidget(des onglets) pour afficher le code.het le.cppdans la boîte de dialogue du code généré (figure suivante).

    • On ne peut que voir et copier/coller le code généré. C'est bien, mais comme vous je pense que si on pouvait enregistrer le résultat dans des fichiers, ce serait du temps gagné pour l'utilisateur. Je vous propose d'ajouter dans laQDialogun bouton pour enregistrer le code dans des fichiers.

Ce bouton ouvrira une fenêtre qui demandera dans quel dossier enregistrer les fichiers.het.cpp. Le nom de ces fichiers sera automatiquement généré à partir du nom de la classe. Pour l'enregistrement dans des fichiers, regardez du côté de la classeQFile. Bon courage.

    • C'est un détail mais les menus contextuels (quand on fait un clic-droit sur un champ de texte, par exemple) sont en anglais. Je vous avais parlé, dans un des chapitres précédents, d'une technique permettant de les avoir en français, un code à placer au début dumain(). Je vous laisse le retrouver !

    • On vérifie si le nom de la classe n'est pas vide mais on ne vérifie pas s'il contient des caractères invalides (comme un espace, des accents, des guillemets…). Il faudrait afficher une erreur si le nom de la classe n'est pas valide.

Pour valider le texte saisi, vous avez deux techniques : utiliser uninputMask()ou unvalidator(). L'inputMask()est peut-être le plus simple mais cela vaut le coup d'avoir pratiqué les deux. Pour savoir faire cela, direction la documentation deQLineEdit!

ZeroClassGenerator et onglets
ZeroClassGenerator et onglets

Voilà pour un petit début d'idées d'améliorations. Il y a déjà de quoi faire pour que vous ne dormiez pas pendant quelques nuits (ne me remerciez pas, c'est tout naturel ;)).

Comme toujours pour les TP, si vous êtes bloqués, rendez-vous sur les forums du Site du Zéro pour demander de l'aide. Bon courage à tous !

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