Voici maintenant une partie très intéressante, bien que plus délicate. Nous allons créer nos propres signaux et slots.
En effet, si en général les signaux et slots par défaut suffisent, il n'est pas rare que l'on se dise "
Zut, le signal (ou le slot) dont j'ai besoin n'existe pas". C'est dans un cas comme celui-là qu'il devient indispensable de créer son widget personnalisé.
Pour pouvoir créer son propre signal ou slot dans une classe, il faut que celle-ci dérive directement ou indirectement de QObject. C'est le cas de notre classe MaFenetre : elle hérite de QWidget, qui hérite de QObject. On a donc le droit de créer des signaux et des slots dans MaFenetre.
Nous allons commencer par créer notre propre slot, puis nous verrons comment créer notre propre signal.
Créer son propre slot
Je vous rappelle tout d'abord qu'un slot n'est rien d'autre qu'une méthode que l'on peut connecter à un signal.
Nous allons donc créer une méthode, mais en suivant quelques règles un peu particulières...
Le but du jeu
Pour nous entraîner, nous allons inventer un cas où le slot dont on a besoin n'existe pas.
Je vous propose de conserver le QSlider (je l'aime bien celui-là

) et de ne garder que ça sur la fenêtre. Nous allons
faire en sorte que le QSlider contrôle la largeur de la fenêtre.
Votre fenêtre doit ressembler à cela :
Nous voulons que le signal valueChanged(int) du QSlider puisse être connecté à un slot de notre fenêtre (de type MaFenetre). Ce nouveau slot aura pour rôle de modifier la largeur de la fenêtre.
Comme
il n'existe pas de slot "changerLargeur" dans la classe QWidget, nous allons devoir le créer.
Pour créer ce slot, il va falloir modifier un peu notre classe MaFenetre. Commençons par le header.
Le header (MaFenetre.h)
Dès que l'on doit créer un signal ou un slot personnalisé, il est nécessaire de définir une macro dans le header de la classe.
Cette macro porte le nom de Q_OBJECT (tout en majuscules) et doit être placée tout au début de la déclaration de la classe :
Code : C++ 1
2
3
4
5
6
7
8
9
10 | class MaFenetre : public QWidget
{
Q_OBJECT
public:
MaFenetre();
private:
QSlider *m_slider;
};
|
Pour le moment, notre classe ne définit qu'un attribut (le QSlider, privé) et une méthode (le constructeur, public).
La macro Q_OBJECT "prépare" en quelque sorte le compilateur à accepter un nouveau mot-clé : "slot". Nous allons maintenant pouvoir créer une section "slots", comme ceci :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13 | class MaFenetre : public QWidget
{
Q_OBJECT
public:
MaFenetre();
public slots:
void changerLargeur(int largeur);
private:
QSlider *m_slider;
};
|
Vous noterez la nouvelle section "public slots". Je rends toujours mes slots publics. On peut aussi les mettre privés mais ils seront quand même accessibles de l'extérieur car Qt a besoin de pouvoir appeler un slot depuis n'importe quel autre widget.
A part ça, le prototype de notre slot-méthode est tout à fait classique. Il ne nous reste plus qu'à l'implémenter dans le .cpp.
L'implémentation (MaFenetre.cpp)
L'implémentation est d'une simplicité redoutable. Regardez :
Code : C++1
2
3
4 | void MaFenetre::changerLargeur(int largeur)
{
setFixedSize(largeur, 100);
}
|
Le slot prend en paramètre un entier : la nouvelle largeur de la fenêtre.
Il se contente d'appeler la méthode setFixedSize de la fenêtre et de lui envoyer la nouvelle largeur qu'il a reçue.
Connexion
Bien, voilà qui est fait. Enfin presque : il faut encore connecter notre QSlider au slot de notre fenêtre. Où va-t-on faire ça ? Dans le constructeur de la fenêtre (toujours dans MaFenetre.cpp) :
Code : C++ 1
2
3
4
5
6
7
8
9
10 | MaFenetre::MaFenetre() : QWidget()
{
setFixedSize(200, 100);
m_slider = new QSlider(Qt::Horizontal, this);
m_slider->setRange(200, 600);
m_slider->setGeometry(10, 60, 150, 20);
QObject::connect(m_slider, SIGNAL(valueChanged(int)), this, SLOT(changerLargeur(int)));
}
|
J'ai volontairement modifié les différentes valeurs que peut prendre notre slider pour le limiter entre 200 et 600 avec la méthode
setRange(). Ainsi, on est sûr que notre fenêtre ne pourra ni être plus petite que 200 pixels de largeur, ni être plus grande que 600 pixels de largeur.
La connexion se fait entre le signal
valueChanged(int) de notre QSlider, et le slot
changerLargeur(int) de notre classe MaFenetre. Vous voyez là encore un exemple où
this est indispensable : il faut pouvoir indiquer un pointeur vers l'objet actuel (la fenêtre) et seul
this peut faire ça !
Schématiquement, on a réalisé la connexion suivante :
Compilation
Avec toutes les nouveautés que nous venons d'utiliser par rapport au C++, la compilation par un make ne suffira pas.
Je vous avais dit qu'il fallait refaire un qmake à chaque fois que les fichiers du projet changeaient. En fait j'ai un peu menti

Comme vous utilisez la macro Q_OBJECT, Qt a besoin d'appeler un pré-compilateur qui lui est propre appelé le
moc (Meta-Object Compiler).
Rassurez-vous, vous n'avez rien à faire de spécial. Relancez juste un qmake avant de faire votre make, et Qt fera le travail de "traduction" du slot en quelque chose de compréhensible pour le compilateur C++.
Vous noterez que le qmake a provoqué la création d'un fichier intermédiaire moc_MaFenetre.cpp, ce qui est parfaitement normal. Ce fichier fournit des informations indispensables au compilateur.
Vous pouvez ensuite faire un make, la compilation devrait bien se passer.
Souvenez-vous ! Si jamais lors de la compilation vous rencontrez l'erreur suivante :
undefined reference to 'vtable for MaFenetre'
... cela signifie que vous n'avez pas fait de qmake avant. Si le moc ne s'est pas exécuté auparavant, la compilation échouera.
Vous pouvez enfin admirer le résultat. Ouf !
Amusez-vous à redimensionner la fenêtre comme bon vous semblera avec le slider. Comme nous avons fixé les limites du slider entre 200 et 600, la largeur de la fenêtre restera comprise entre 200 et 600 pixels.
Exercice : redimensionner la fenêtre en hauteur
Voici un petit exercice, mais qui va vous forcer à travailler (bande de fainéants, vous me regardez faire depuis tout à l'heure

).
Je vous propose de créer un second QSlider, vertical cette fois, qui contrôlera la hauteur de la fenêtre. Pensez à bien définir des limites appropriées pour les valeurs de ce nouveau slider.
Vous devriez obtenir un résultat qui ressemblera à ça :
Si vous voulez "conserver" la largeur pendant que vous modifiez la hauteur, et inversement, vous aurez besoin d'utiliser les méthodes accesseur width() (largeur actuelle) et height() (hauteur actuelle).
Vous comprendrez très certainement l'intérêt de ces informations lorsque vous coderez. Au boulot !
Créer son propre signal
Il est plus rare d'avoir à créer son signal que son slot, mais cela peut arriver.
Je vous propose de réaliser le programme suivant : si le slider horizontal arrive à sa valeur maximale (600 dans notre cas), alors on émet un signal "agrandissementMax". Notre fenêtre doit pouvoir émettre l'information comme quoi elle est agrandie au maximum.
Après, nous connecterons ce signal à un slot pour vérifier que notre programme réagit correctement.
Le header (MaFenetre.h)
Commençons par changer le header :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | class MaFenetre : public QWidget
{
Q_OBJECT
public:
MaFenetre();
public slots:
void changerLargeur(int largeur);
signals:
void agrandissementMax();
private:
QSlider *m_slider;
};
|
On a ajouté une section "signals". Les signaux se présentent en pratique sous forme de méthodes (comme les slots) à la différence près qu'on ne les implémente pas dans le .cpp. En effet, c'est Qt qui le fait pour nous. Si vous tentez d'implémenter un signal, vous aurez une erreur du genre "
Multiple definition of...".
Un signal peut passer un ou plusieurs paramètres. Dans notre cas, il n'en envoie aucun.
Un signal doit toujours renvoyer void.
L'implémentation (MaFenetre.cpp)
Maintenant que notre signal est défini, il faut que notre classe puisse l'émettre à un moment.
Quand est-ce qu'on sait que la fenêtre a été agrandie au maximum ? Dans le slot changerLargeur ! Il suffit de tester dans ce slot si la largeur correspond au maximum (600), et d'émettre alors le signal "
Youhou, j'ai été agrandie au maximum !".
Retournons dans MaFenetre.cpp et implémentons ce test qui émet le signal depuis changerLargeur :
Code : C++1
2
3
4
5
6
7
8
9 | void MaFenetre::changerLargeur(int largeur)
{
setFixedSize(largeur, height());
if (largeur == 600)
{
emit agrandissementMax();
}
}
|
Notre méthode s'occupe toujours de redimensionner la fenêtre, mais vérifie en plus si la largeur a atteint le maximum (600). Si c'est le cas, elle émet le signal agrandissementMax().
Pour émettre un signal, on utilise le mot-clé
emit, là encore un terme inventé par Qt qui n'existe pas en C++. L'avantage est que c'est très lisible, on comprend "
Emettre le signal agrandissementMax()".
Ici, notre signal n'envoie pas de paramètres. Toutefois, sachez que si vous voulez envoyer un paramètre c'est très simple. Il suffit d'appeler votre signal comme ceci : emit monSignal(parametre1, parametre2, ...);
Connexion
Il ne nous reste plus qu'à connecter notre nouveau signal à un slot. Vous pouvez connecter ce signal au slot que vous voulez. Personnellement, je propose de le connecter à l'application (à l'aide du pointeur global qApp) pour provoquer l'arrêt du programme.
Ca n'a pas trop de sens je suis d'accord, mais c'est juste pour s'entraîner et vérifier que ça fonctionne. Vous aurez l'occasion de faire des connexions plus logiques plus tard, je ne m'en fais pas pour ça
Dans le constructeur de MaFenetre, je rajoute donc :
Code : C++1 | QObject::connect(this, SIGNAL(agrandissementMax()), qApp, SLOT(quit()));
|
Vous pouvez tester le résultat : normalement le programme s'arrête quand la fenêtre est agrandie au maximum.
Le schéma des signaux qu'on vient d'émettre et connecter est le suivant :
Dans l'ordre, voici ce qui s'est passé :
- Le signal valueChanged du slider a appelé le slot changerLargeur de la fenêtre.
- Le slot a fait ce qu'il avait à faire (changer la largeur de la fenêtre) et a vérifié si la fenêtre était arrivée à sa taille maximale. Lorsque cela a été le cas, le signal personnalisé agrandissementMax() a été émis.
- Le signal agrandissementMax() de la fenêtre était connecté au slot quit() de l'application, ce qui a provoqué la fermeture du programme.
Et voilà comment le déplacement du slider peut, par réaction en chaîne, provoquer la fermeture du programme !
Bien entendu, ce schéma peut être aménagé et complexifié selon les besoins de votre application.
Maintenant que vous savez créer vos propres slots et signaux, vous avez toute la souplesse nécessaire pour
faire ce que vous voulez !
