Programmation événementielle 2 : Premiers pas avec Qt
article de Def
, publié le 28 mars 2006 à 21:24
Si vous avez suivi mes précieux conseils, vous avez réussi la 1ère étape de votre transformation en bombe sexuelle en installant correctement Qt.
Il serait pas mal maintenant d'apprendre à l'utiliser.
Dans ce 2ème article, je vais vous parler de certains widgets de Qt, vous montrer comment gérer des événements et enfin, vous apprendre à utiliser Qt Assistant, votre nouveau meilleur ami. Maintenant que nous disposons d'un environnement de développement en place (si ce n'est pas le cas, on lira cet article), nous allons pouvoir nous lancer dans la création d'applications.
La première étape va consister à réperer où se trouve de l'aide.
Effectivement, quelle que soit la puissance d'une libraire, son utilisation risque d'être un calvaire si elle n'est pas bien documentée.
Heureusement pour nous, l'aide de Qt est un exemple en la matière.
Mon ami Qt Assistant
L'aide de Qt répond au doux nom de Qt Assistant.
Mais commençons par le lancer pour découvrir le visage de la chose : Menu démarrer >Qt >Assistant.
La page de présentation de l'aide s'affiche avec un bon nombre de liens vers différents sujets.
Pas de raison de paniquer devant cette quantité ; le seul qui va nous intéresser est Tutorial and Examples.
Enfin, "intéresser" est un bien grand mot dans ce cas. Je trouve personnellement que le tutoriel n'est pas des plus pratiques.
Par contre, les différents exemples se révèleront très utiles par la suite. Mes connaissances sur le sujet ne sont peut-être plus à jour mais ce tutoriel me semble être le seul existant sur Qt4. D'où mon envie d'en faire un plus à mon goût.
Mais j'arrête de raconter ma vie. Revenons brièvement à l'assistant en question.
Les choses les plus utiles se trouveront par le biais de l'onglet index. Nous avons là un listing de toutes les classes, méthodes et tout ce que l'on pourrait souhaiter trouver à propos de Qt.
Nous y reviendrons en temps utile.
Laissons donc Qt Assistant de côté pour le moment et réouvrons notre projet créé dans le 1er article.
Structure d'une application Qt
Observons le code de notre fichier main.cpp:
QApplication app(argc, argv);
QDialog *window = new QDialog;
window->show();
return app.exec(); var is_code=true;
Qu'allons nous faire ?
- on instancie un objet QApplication qui sert de base à toute application Qt.
- on instancie l'objet principal de notre application (en principe dérivé de QDialog ou de QMainWindow. On reviendra plus tard sur ce sujet).
- on appelle la méthode show de notre objet principal pour l'afficher.
- on termine le programme avec la valeur renvoyée par la méthode exec().
Ces notions vont être plus faciles à assimiler en démarrant notre application.
Mon premier widget !
Mais qu'est-ce qu'un widget ?
Un widget est un composant graphique. Un bouton est un widget, une fenêtre est un widget, une zone de texte est un widget...
Et si on en ajoutait un de ces widgets dans notre fenêtre toute remplie de vide ?
Commençons par y mettre un champs texte non éditable.
En Qt, c'est l'objet QLabel.
On va donc remplacer l'objet QDialog instancié par défaut par un QLabel, ce qui nous donne le code suivant (en n'oubliant pas d'inclure QLabel à la place de QDialog):
#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QLabel * label = new QLabel();
label->show();
return app.exec();
} var is_code=true;
On compile, on lance et hormis la taille plus petite de la fenêtre, on ne voit aucune différence.
Nous avons créé un QLabel vide. Il ne fallait pas s'attendre à de grands changements. Il faudra maintenant modifier notre objet.
Reprenons Qt Assistant et recherchons, toujours par l'onglet index, la classe QLabel:
Intéressons-nous en premier à ses constructeurs:
QLabel ( QWidget * parent = 0, Qt::WFlags f = 0 )
QLabel ( const QString & text, QWidget * parent = 0, Qt::WFlags f = 0 )
Nous avons utilisé le 1er constructeur en laissant les paramètres par défaut. Le 2ème demande en plus un QString (classe de Qt pour gérer les chaines de caractères mais je pense que ça, tout le monde l'avait deviné).
Si l'on clique sur le constructeur, on peut lire:
Constructs a label that displays the text, text.
Ça tombe bien, c'est exactement ce que l'on voulait faire !
Remplaçons notre instanciation d'objet par:
QLabel * label = new QLabel("Hello world !"); var is_code=true;
Compilation, exécution:
Un peu petite, notre QLabel, quand même.
Regardons les méthodes qui nous sont proposées.
Hmm... y a rien pour redimensionner !
Mais n'oublions pas que nous nous sommes lancés dans de la programmation orientée objet.
Ccomme nous pouvons le voir dans Qt Assistant, QLabel hérite de QFrame, QWidget, QObject et de QPaintDevice. En haut de la page sur QLabel, il y a un lien intitulé "List of all members, including inherited members".
Cliquons dessus !
On arrive sur une nouvelle page listant l'intégralité des méthodes pouvant être utilisées avec notre QLabel. Après une petite recherche, nous découvrons:
resize ( int, int )
Allons-y !
label->resize(200, 50); var is_code=true;
C'est déjà mieux !
Ajoutons un bouton !
Instancions un 2ème widget, plus précisément, un QPushButton:
QPushButton * bouton = new QPushButton();
bouton->show(); var is_code=true;
A l'exécution, on remarque avec horreur que deux fenêtres se lancent !
Pourquoi donc ?
La raison est très simple: pour rester dans une seule fenêtre, on ne peut utiliser la méthode show que sur un seul widget.
- Allons-nous donc être obligés de se contenter de programmes avec un seul widget ?
- Mais non Roger, y'a un moyen pour faire mieux.
Ce que nous allons faire, c'est créer un nouveau widget contenant notre QLabel et notre QPushButton et enfin, afficher ce widget.
Prenons l'habitude de mettre nos classes dans des fichiers séparés pour ne pas finir avec un unique fichier de 12'000 lignes de codes.
Créons 2 fichiers (project -> new file) que l'on sauvegardera sous les noms de "MonWidget.h" et "MonWidget.cpp".
Nous obtiendrons donc:
MonWidget.h :
#include <QDialog>
#include <QLabel>
#include <QPushButton>
class MonWidget : public QDialog
{
Q_OBJECT
public:
MonWidget();
private:
QLabel * label;
QPushButton * bouton;
}; var is_code=true;
Pas grand chose à expliquer ici si ce n'est que nous dérivons de QDialog qui est le widget de base pour les boîtes de dialogue et que nous utilisons la macro Q_OBJECT.
Rien de bien sorcier là dedans; elle sert juste à indiquer à qmake, l'utilitaire qui crée notre makefile, qu'il faudra appeler MOC (Meta Object Compiler) pour traduire le code Qt en code c++.
On peut résumer par : "on s'en bat, on la met sinon ça compile pas".
MonWidget.cpp :
#include "MonWidget.h"
MonWidget::MonWidget()
{
label = new QLabel("Hello World !", this);
label->resize(200, 50);
bouton = new QPushButton("OK", this);
bouton->move(0, 50);
} var is_code=true;
Ici aussi, le code est bien explicite.
Deux précisions, néanmoins: dans les constructeurs de label et de bouton, j'utilise This comme 2ème paramètre. On donne simplement au widget l'objet MonWidget comme parent.
L'utilité ?
Lorsqu'un objet est détruit, tous ses "enfants" le sont également. Un poids en moins pour la gestion de la mémoire.
Deuxième précision: j'utilise la méthode move sur mon bouton. Comme nous n'utilisons pas encore de layout, tous les widgets vont être créés les uns sur les autres. Je descends donc le bouton de 50 pixels pour que le label soit lisible.
main.cpp
#include <QApplication>
#include "MonWidget.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MonWidget * widget = new MonWidget();
widget->resize(200, 100);
widget->show();
return app.exec();
} var is_code=true;
Evidemment, on n'oublie pas de refaire le makefile puisque nous avons ajouté des fichiers.
Compilons et lançons enfin notre projet:
Utilisons notre bouton !
Je vais refaire un peu de théorie pour vous expliquer comment Qt gère les événements.
Celui-ci utilise un système de signaux et de slots. Un signal est émis par un widget pour annoncer un événement et un widget reçoit des signaux par un slot afin d'effectuer des actions. Il suffit alors de connecter le signal d'un widget au slot d'un autre pour pouvoir gérer l'événement.
Pour connecter, nous allons utiliser connect:
connect(objet1, SIGNAL(signal()), objet2, SLOT(slot())); var is_code=true;
Il est très important d'avoir un signal et un slot avec exactement les mêmes nombre et type de paramètres. Pour l'instant nous allons en utiliser sans paramètre. Cette question fera l'objet d'un article prochain.
Il est bien entendu possible de créer ses propres signaux et slots mais il en existe aussi des prédéfinis pour nous simplifier la tâche.
En pratique, nous allons utiliser le signal "void clicked()" de QPushButton (hérité de QAbstractButton, Qt Assistant est toujours ton ami) et le connecter à un slot de MonWidget que nous allons créer. Lorsque MonWidget recevra le signal sur son slot, nous lui ferons modifier le label.
Commençons par créer le slot dans MonWidget.h:
private slots:
void changeLabel(); var is_code=true;
Puis dans MonWidget.cpp:
void MonWidget::changeLabel()
{
static int nb = 0;
nb++;
label->setText("Nombre de clic : "+QString::number(nb));
} var is_code=true;
Que faisons-nous à ce stade ?
On commence par créer une variable nb qui contiendra le nombre de clics et on l'incrémente à chaque appel de changeLabel.
Ensuite, on modifie le label (setText. Oui, Qt Assistant est plus que jamais ton ami) en affichant le nombre de clics.
Petite précision sur QString::number(nb): la méthode setText demande un QString et nb est un int. On va le transtyper en QString avec cette méthode (Qt Assis... oui, okay, j'arrête).
Le signal existe déjà, le slot a été créé; il ne nous reste plus qu'à les connecter ensemble dans le constructeur de MonWidget grâce à connect:
connect(bouton, SIGNAL(clicked()), this, SLOT(changeLabel())); var is_code=true;
On peut compiler, lancer et hop:
\o/
Nous allons nous arrêter là pour aujourd'hui.
Le prochain article devrait vous présenter de nouveaux widgets pour pouvoir faire un peu plus qu'un bête compteur de clics.
Les sources du programme final peuvent être téléchargées ici.
Il serait pas mal maintenant d'apprendre à l'utiliser.
Dans ce 2ème article, je vais vous parler de certains widgets de Qt, vous montrer comment gérer des événements et enfin, vous apprendre à utiliser Qt Assistant, votre nouveau meilleur ami. Maintenant que nous disposons d'un environnement de développement en place (si ce n'est pas le cas, on lira cet article), nous allons pouvoir nous lancer dans la création d'applications.
La première étape va consister à réperer où se trouve de l'aide.
Effectivement, quelle que soit la puissance d'une libraire, son utilisation risque d'être un calvaire si elle n'est pas bien documentée.
Heureusement pour nous, l'aide de Qt est un exemple en la matière.
Mon ami Qt Assistant
L'aide de Qt répond au doux nom de Qt Assistant.
Mais commençons par le lancer pour découvrir le visage de la chose : Menu démarrer >Qt >Assistant.
La page de présentation de l'aide s'affiche avec un bon nombre de liens vers différents sujets.
Pas de raison de paniquer devant cette quantité ; le seul qui va nous intéresser est Tutorial and Examples.
Enfin, "intéresser" est un bien grand mot dans ce cas. Je trouve personnellement que le tutoriel n'est pas des plus pratiques.
Par contre, les différents exemples se révèleront très utiles par la suite. Mes connaissances sur le sujet ne sont peut-être plus à jour mais ce tutoriel me semble être le seul existant sur Qt4. D'où mon envie d'en faire un plus à mon goût.
Mais j'arrête de raconter ma vie. Revenons brièvement à l'assistant en question.
Les choses les plus utiles se trouveront par le biais de l'onglet index. Nous avons là un listing de toutes les classes, méthodes et tout ce que l'on pourrait souhaiter trouver à propos de Qt.
Nous y reviendrons en temps utile.
Laissons donc Qt Assistant de côté pour le moment et réouvrons notre projet créé dans le 1er article.
Structure d'une application Qt
Observons le code de notre fichier main.cpp:
QApplication app(argc, argv);
QDialog *window = new QDialog;
window->show();
return app.exec(); var is_code=true;
Qu'allons nous faire ?
- on instancie un objet QApplication qui sert de base à toute application Qt.
- on instancie l'objet principal de notre application (en principe dérivé de QDialog ou de QMainWindow. On reviendra plus tard sur ce sujet).
- on appelle la méthode show de notre objet principal pour l'afficher.
- on termine le programme avec la valeur renvoyée par la méthode exec().
Ces notions vont être plus faciles à assimiler en démarrant notre application.
Mon premier widget !
Mais qu'est-ce qu'un widget ?
Un widget est un composant graphique. Un bouton est un widget, une fenêtre est un widget, une zone de texte est un widget...
Et si on en ajoutait un de ces widgets dans notre fenêtre toute remplie de vide ?
Commençons par y mettre un champs texte non éditable.
En Qt, c'est l'objet QLabel.
On va donc remplacer l'objet QDialog instancié par défaut par un QLabel, ce qui nous donne le code suivant (en n'oubliant pas d'inclure QLabel à la place de QDialog):
#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QLabel * label = new QLabel();
label->show();
return app.exec();
} var is_code=true;
On compile, on lance et hormis la taille plus petite de la fenêtre, on ne voit aucune différence.
Nous avons créé un QLabel vide. Il ne fallait pas s'attendre à de grands changements. Il faudra maintenant modifier notre objet.
Reprenons Qt Assistant et recherchons, toujours par l'onglet index, la classe QLabel:
Intéressons-nous en premier à ses constructeurs:
QLabel ( QWidget * parent = 0, Qt::WFlags f = 0 )
QLabel ( const QString & text, QWidget * parent = 0, Qt::WFlags f = 0 )
Nous avons utilisé le 1er constructeur en laissant les paramètres par défaut. Le 2ème demande en plus un QString (classe de Qt pour gérer les chaines de caractères mais je pense que ça, tout le monde l'avait deviné).
Si l'on clique sur le constructeur, on peut lire:
Constructs a label that displays the text, text.
Ça tombe bien, c'est exactement ce que l'on voulait faire !
Remplaçons notre instanciation d'objet par:
QLabel * label = new QLabel("Hello world !"); var is_code=true;
Compilation, exécution:
Un peu petite, notre QLabel, quand même.
Regardons les méthodes qui nous sont proposées.
Hmm... y a rien pour redimensionner !
Mais n'oublions pas que nous nous sommes lancés dans de la programmation orientée objet.
Ccomme nous pouvons le voir dans Qt Assistant, QLabel hérite de QFrame, QWidget, QObject et de QPaintDevice. En haut de la page sur QLabel, il y a un lien intitulé "List of all members, including inherited members".
Cliquons dessus !
On arrive sur une nouvelle page listant l'intégralité des méthodes pouvant être utilisées avec notre QLabel. Après une petite recherche, nous découvrons:
resize ( int, int )
Allons-y !
label->resize(200, 50); var is_code=true;
C'est déjà mieux !
Ajoutons un bouton !
Instancions un 2ème widget, plus précisément, un QPushButton:
QPushButton * bouton = new QPushButton();
bouton->show(); var is_code=true;
A l'exécution, on remarque avec horreur que deux fenêtres se lancent !
Pourquoi donc ?
La raison est très simple: pour rester dans une seule fenêtre, on ne peut utiliser la méthode show que sur un seul widget.
- Allons-nous donc être obligés de se contenter de programmes avec un seul widget ?
- Mais non Roger, y'a un moyen pour faire mieux.
Ce que nous allons faire, c'est créer un nouveau widget contenant notre QLabel et notre QPushButton et enfin, afficher ce widget.
Prenons l'habitude de mettre nos classes dans des fichiers séparés pour ne pas finir avec un unique fichier de 12'000 lignes de codes.
Créons 2 fichiers (project -> new file) que l'on sauvegardera sous les noms de "MonWidget.h" et "MonWidget.cpp".
Nous obtiendrons donc:
MonWidget.h :
#include <QDialog>
#include <QLabel>
#include <QPushButton>
class MonWidget : public QDialog
{
Q_OBJECT
public:
MonWidget();
private:
QLabel * label;
QPushButton * bouton;
}; var is_code=true;
Pas grand chose à expliquer ici si ce n'est que nous dérivons de QDialog qui est le widget de base pour les boîtes de dialogue et que nous utilisons la macro Q_OBJECT.
Rien de bien sorcier là dedans; elle sert juste à indiquer à qmake, l'utilitaire qui crée notre makefile, qu'il faudra appeler MOC (Meta Object Compiler) pour traduire le code Qt en code c++.
On peut résumer par : "on s'en bat, on la met sinon ça compile pas".
MonWidget.cpp :
#include "MonWidget.h"
MonWidget::MonWidget()
{
label = new QLabel("Hello World !", this);
label->resize(200, 50);
bouton = new QPushButton("OK", this);
bouton->move(0, 50);
} var is_code=true;
Ici aussi, le code est bien explicite.
Deux précisions, néanmoins: dans les constructeurs de label et de bouton, j'utilise This comme 2ème paramètre. On donne simplement au widget l'objet MonWidget comme parent.
L'utilité ?
Lorsqu'un objet est détruit, tous ses "enfants" le sont également. Un poids en moins pour la gestion de la mémoire.
Deuxième précision: j'utilise la méthode move sur mon bouton. Comme nous n'utilisons pas encore de layout, tous les widgets vont être créés les uns sur les autres. Je descends donc le bouton de 50 pixels pour que le label soit lisible.
main.cpp
#include <QApplication>
#include "MonWidget.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MonWidget * widget = new MonWidget();
widget->resize(200, 100);
widget->show();
return app.exec();
} var is_code=true;
Evidemment, on n'oublie pas de refaire le makefile puisque nous avons ajouté des fichiers.
Compilons et lançons enfin notre projet:
Utilisons notre bouton !
Je vais refaire un peu de théorie pour vous expliquer comment Qt gère les événements.
Celui-ci utilise un système de signaux et de slots. Un signal est émis par un widget pour annoncer un événement et un widget reçoit des signaux par un slot afin d'effectuer des actions. Il suffit alors de connecter le signal d'un widget au slot d'un autre pour pouvoir gérer l'événement.
Pour connecter, nous allons utiliser connect:
connect(objet1, SIGNAL(signal()), objet2, SLOT(slot())); var is_code=true;
Il est très important d'avoir un signal et un slot avec exactement les mêmes nombre et type de paramètres. Pour l'instant nous allons en utiliser sans paramètre. Cette question fera l'objet d'un article prochain.
Il est bien entendu possible de créer ses propres signaux et slots mais il en existe aussi des prédéfinis pour nous simplifier la tâche.
En pratique, nous allons utiliser le signal "void clicked()" de QPushButton (hérité de QAbstractButton, Qt Assistant est toujours ton ami) et le connecter à un slot de MonWidget que nous allons créer. Lorsque MonWidget recevra le signal sur son slot, nous lui ferons modifier le label.
Commençons par créer le slot dans MonWidget.h:
private slots:
void changeLabel(); var is_code=true;
Puis dans MonWidget.cpp:
void MonWidget::changeLabel()
{
static int nb = 0;
nb++;
label->setText("Nombre de clic : "+QString::number(nb));
} var is_code=true;
Que faisons-nous à ce stade ?
On commence par créer une variable nb qui contiendra le nombre de clics et on l'incrémente à chaque appel de changeLabel.
Ensuite, on modifie le label (setText. Oui, Qt Assistant est plus que jamais ton ami) en affichant le nombre de clics.
Petite précision sur QString::number(nb): la méthode setText demande un QString et nb est un int. On va le transtyper en QString avec cette méthode (Qt Assis... oui, okay, j'arrête).
Le signal existe déjà, le slot a été créé; il ne nous reste plus qu'à les connecter ensemble dans le constructeur de MonWidget grâce à connect:
connect(bouton, SIGNAL(clicked()), this, SLOT(changeLabel())); var is_code=true;
On peut compiler, lancer et hop:
\o/
Nous allons nous arrêter là pour aujourd'hui.
Le prochain article devrait vous présenter de nouveaux widgets pour pouvoir faire un peu plus qu'un bête compteur de clics.
Les sources du programme final peuvent être téléchargées ici.
article de Def — publié le 28 mars 2006 à 21:24
« précédent
Article
suivant »
Écrire un article
Me, Myself and I