Aller au menu - Aller au contenu

Icône Les signaux et les slots

Avatar
Mise à jour : 27/05/2011
92 771 visites depuis 7 jours , dont 1 549 sur ce chapitre , classé 5/777
Nous commençons à maîtriser petit à petit la création d'une fenêtre. Dans le chapitre précédent, nous avons posé de solides bases pour développer par la suite notre application. Nous avons réalisé une classe personnalisée, héritant de QWidget.

Nous allons maintenant découvrir le mécanisme des signaux et des slots, un principe propre à Qt qui est clairement un de ses points forts. Il s'agit d'une technique séduisante pour gérer les évènements au sein d'une fenêtre.
Par exemple, si on clique sur un bouton, on voudrait qu'une fonction soit appelée pour réagir au clic. C'est précisément ce que nous apprendrons à faire dans ce chapitre, qui va enfin rendre votre application dynamique :)
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Le principe des signaux et slots

Le principe est plutôt simple à comprendre : une application de type GUI réagit à partir d'évènements. C'est ce qui rend votre fenêtre dynamique.
Ceux d'entre vous qui ont déjà essayé la bibliothèque SDL se souviennent peut-être de la gestion des évènements : interception des touches du clavier, des déplacements de la souris, du joystick, etc.

Ce que Qt propose, c'est la même chose mais à plus haut niveau : c'est donc beaucoup plus facile à gérer.

On parle de signaux et de slots, mais qu'est-ce que c'est concrètement ? C'est un concept inventé par Qt. Voici une petite définition en guise d'introduction :

  • Un signal : c'est un message envoyé par un widget lorsqu'un évènement se produit.
    Exemple : on a cliqué sur un bouton.
  • Un slot : c'est la fonction qui est appelée lorsqu'un évènement s'est produit. On dit que le signal appelle le slot. Concrètement, un slot est une méthode d'une classe.
    Exemple : le slot quit() de la classe QApplication, qui provoque l'arrêt du programme.


Les signaux et les slots sont considérés par Qt comme des éléments d'une classe à part entière, en plus des attributs et des méthodes.

Voici un schéma qui montre ce qu'un objet pouvait contenir avant Qt, ainsi que ce qu'il peut contenir maintenant qu'on utilise Qt :

Un objet avec des signaux et des slots

Qt rajoute des éléments appelés "Signaux" et "Slots" aux objets


Avant Qt, un objet était constitué d'attributs et de méthodes. C'est tout.
Qt rajoute en plus la possibilité d'utiliser ce qu'il appelle des signaux et des slots pour gérer les évènements.

Un signal est un message envoyé par l'objet (par exemple "on a cliqué sur le bouton").
Un slot est une... méthode. En fait, c'est une méthode classique comme toutes les autres, à la différence près qu'elle a le droit d'être connectée à un signal.

Avec Qt, on dit que l'on connecte des signaux et des slots entre eux. Supposons que vous ayez deux objets, chacun ayant ses propres attributs, méthodes, signaux et slots (je n'ai pas représenté les attributs et les méthodes sur mon schéma pour simplifier) :

Des signaux et des slots


Sur le schéma ci-dessus, on a connecté le signal 1 de l'objet 1 avec le slot 2 de l'objet 2.

Il est possible de connecter un signal à plusieurs slots. Ainsi, un clic sur un bouton pourrait appeler non pas une mais plusieurs méthodes. Comble du raffinement, il est aussi possible de connecter un signal à un autre signal. Le signal d'un bouton peut donc provoquer la création du signal d'un autre widget, qui peut à son tour appeler des slots (voire appeler d'autres signaux pour provoquer une réaction en chaîne !). C'est un peu particulier et on ne verra pas ça dans ce chapitre.

Connexion d'un signal à un slot simple

Voyons un cas très concret. Je vais prendre 2 objets, l'un de type QPushButton, et l'autre de type QApplication. Dans le schéma ci-dessous, ce que vous voyez sont de vrais signaux et slots que vous allez pouvoir utiliser :

Signaux et slots en pratique


Regardez attentivement ce schéma. Nous avons d'un côté notre bouton appelé "m_bouton" (de type QPushButton), et de l'autre notre application (de type QApplication, utilisée dans le main).

Nous voudrions par exemple connecter le signal "bouton cliqué" au slot "quitter l'application". Ainsi, un clic sur le bouton provoquerait l'arrêt de l'application.

Pour ce faire, nous devons utiliser une méthode statique de la classe QObject : connect().


Le principe de la méthode connect()



connect() est une méthode statique. Vous vous souvenez ce que ça veut dire ?
Une méthode statique est une méthode d'une classe que l'on peut appeler sans créer d'objet. C'est en fait exactement comme une fonction classique du langage C.

Si vous avez un trou de mémoire, allez vite relire le chapitre traitant des méthodes statiques !


Pour appeler une méthode statique, il faut faire précéder son nom du nom de la classe dans laquelle elle est déclarée. Comme connect() appartient à la classe QObject, il faut donc écrire :

Code : C++
1
QObject::connect();
Signaux et slots en pratique


La méthode connect prend 4 arguments :

  • Un pointeur vers l'objet qui émet le signal.
  • Le nom du signal que l'on souhaite "intercepter".
  • Un pointeur vers l'objet qui contient le slot récepteur.
  • Le nom du slot qui doit s'exécuter lorsque le signal se produit.

Pour que vous puissiez vous repérer, j'ai remis ci-contre le schéma qu'on a vu un peu plus haut. Les couleurs sont les mêmes, cela devrait vous permettre de bien visualiser à quoi correspond chaque attribut.


Il existe aussi une méthode disconnect() permettant de casser la connexion entre 2 objets, mais on n'en parlera pas ici car on en a rarement besoin.

Utilisation de la méthode connect() pour quitter



Revenons au code, et plus précisément au constructeur de MaFenetre (fichier MaFenetre.cpp). Ajoutez cette ligne :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include "MaFenetre.h"
 
MaFenetre::MaFenetre() : QWidget()
{
    setFixedSize(300, 150);
 
    m_bouton = new QPushButton("Quitter", this);
    m_bouton->setFont(QFont("Comic Sans MS", 14));
    m_bouton->move(110, 50);
 
    // Connexion du clic du bouton à la fermeture de l'application
    QObject::connect(m_bouton, SIGNAL(clicked()), qApp, SLOT(quit()));
}

connect() est une méthode de la classe QObject. Comme notre classe MaFenetre hérite de QObject indirectement, elle possède elle aussi cette méthode. Cela signifie que dans ce cas, et dans ce cas uniquement, on peut enlever le préfixe QObject:: devant le connect() pour appeler la méthode statique.
J'ai choisi de conserver ce préfixe dans le cours pour rappeler qu'il s'agit d'une méthode statique, mais sachez donc qu'il n'a rien d'obligatoire si la méthode est appelée depuis une classe fille de QObject.


Etudions attentivement cette ligne et plus particulièrement les paramètres que l'on envoie à connect() :

  • m_bouton : c'est un pointeur vers le bouton qui va émettre le signal. Facile.
  • SIGNAL(clicked()) : là c'est assez perturbant comme façon d'envoyer un paramètre. En fait, SIGNAL() est une macro du préprocesseur. Qt transformera ça en un code "acceptable" pour la compilation. Le but de cette technique est de vous faire écrire un code court et compréhensible. Ne cherchez pas à comprendre comment Qt fait pour transformer le code, on s'en fout :p
  • qApp : c'est un pointeur vers l'objet de type QApplication que nous avons créé dans le main. D'où sort ce pointeur ? Euh... joker ^^
    En fait, Qt crée automatiquement un pointeur appelé qApp vers l'objet de type QApplication que nous avons créé. Ce pointeur est défini dans le header <QApplication>, que nous avons inclus dans "MaFenetre.h".
  • SLOT(quit()) : c'est le slot qui doit être appelé lorsqu'on a cliqué sur le bouton. Là encore, il faut utiliser la macro SLOT() pour que Qt traduise ce code "bizarre" en quelque chose de compilable.

Le slot quit() de notre objet de type QApplication est un slot prédéfini. Il en existe d'autres, comme aboutQt() qui affiche une fenêtre "A propos de Qt".
Parfois, pour ne pas dire souvent, les slots prédéfinis par Qt ne nous suffiront pas. Nous apprendrons dans la suite de ce chapitre à créer les nôtres.

Testons notre code ! La fenêtre qui s'ouvre est la suivante :

La fenêtre avec le bouton quitter


Rien de bien extraordinaire à première vue. Sauf que... si vous cliquez sur le bouton "Quitter", le programme s'arrête !
Hourra, on vient de réussir à connecter notre premier signal à un slot ! :D



Utilisation de la méthode connect() pour afficher "A propos"



On peut faire un autre essai pour se faire un peu plus la main si vous voulez. Je vous ai parlé d'un autre slot de QApplication : aboutQt().
Je vous propose de créer un second bouton qui se chargera d'afficher la fenêtre "A propos de Qt".

Je vous laisse rédiger le code tous seuls comme des grands.


...


...

C'est bon ?
Voici le code final :)

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include "MaFenetre.h"
 
MaFenetre::MaFenetre() : QWidget()
{
    setFixedSize(300, 150);
 
    m_quitter = new QPushButton("Quitter", this);
    m_quitter->setFont(QFont("Comic Sans MS", 14));
    m_quitter->move(110, 50);
    QObject::connect(m_quitter, SIGNAL(clicked()), qApp, SLOT(quit()));
 
    m_aPropos = new QPushButton("A propos", this);
    m_aPropos->setFont(QFont("Comic Sans MS", 14));
    m_aPropos->move(110, 90);
    QObject::connect(m_aPropos, SIGNAL(clicked()), qApp, SLOT(aboutQt()));
}


Vous noterez que j'ai pris la liberté de nommer les boutons avec des noms un peu plus compréhensibles.
Bien entendu, le fichier MaFenetre.h a un peu changé lui aussi du coup pour déclarer les attributs "m_quitter" et "m_aPropos", mais vous êtes assez grands pour le faire sans moi ;)

Le résultat est une fenêtre qui affiche 2 boutons :

Le bouton A propos


Le bouton "Quitter" ferme toujours l'application.
Quant à "A propos", il provoque l'ouverture de la fenêtre "A propos de Qt".

Le bouton A propos a ouvert une fenêtre

Des paramètres dans les signaux et slots

La méthode statique connect() est assez originale, vous l'avez vu. Il s'agit justement d'une des particularités de Qt que l'on ne retrouve pas dans les autres bibliothèques.
Ces autres bibliothèques, comme wxWidgets par exemple, utilisent à la place de nombreuses macros et se servent du mécanisme un peu complexe et délicat des pointeurs de fonction (pour indiquer l'adresse de la fonction à appeler en mémoire).

Il y a d'autres avantages à utiliser la méthode connect() avec Qt. On va ici découvrir que les signaux et les slots peuvent s'échanger des paramètres !


Dessin de la fenêtre



Dans un premier temps, nous allons placer de nouveaux widgets dans notre fenêtre.
Vous pouvez enlever les boutons, on ne va plus s'en servir ici.

A la place, je souhaite vous faire utiliser 2 nouveaux widgets :

  • QSlider : un curseur qui permet de définir une valeur.
  • QLCDNumber : un widget qui affiche un nombre.


On va aller un peu plus vite, je vous donne le code directement pour créer ça.
Tout d'abord, le header :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#ifndef DEF_MAFENETRE
#define DEF_MAFENETRE
 
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLCDNumber>
#include <QSlider>
 
class MaFenetre : public QWidget
{
    public:
    MaFenetre();
 
    private:
    QLCDNumber *m_lcd;
    QSlider *m_slider; 
};
 
#endif


J'ai donc enlevé les boutons comme vous pouvez le voir, et rajouté un QLCDNumber et un QSlider.
Surtout, n'oubliez pas d'inclure le header de ces classes pour pouvoir les utiliser. J'ai gardé l'include du QPushButton ici, ça ne fait pas de mal de le laisser mais si vous ne comptez pas le réutiliser vous pouvez le virer sans crainte.

Et le fichier .cpp :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include "MaFenetre.h"
 
MaFenetre::MaFenetre() : QWidget()
{
    setFixedSize(200, 100);
 
    m_lcd = new QLCDNumber(this);
    m_lcd->setSegmentStyle(QLCDNumber::Flat);
    m_lcd->move(50, 20);
 
    m_slider = new QSlider(Qt::Horizontal, this);
    m_slider->setGeometry(10, 60, 150, 20);
}

Les détails ne sont pas très importants. J'ai modifié le type d'afficheur LCD pour qu'il soit plus lisible (avec setSegmentStyle). Quant au slider, j'ai rajouté un paramètre pour qu'il apparaisse horizontalement (sinon il est vertical).

Voilà qui est fait. Avec ce code, cette petite fenêtre devrait s'afficher :

Un afficheur LCD


Connexion avec des paramètres



Maintenant... connexiooooon !
C'est là que les choses deviennent intéressantes. On veut que l'afficheur LCD change de valeur en fonction de la position du curseur du slider.

On dispose du signal et du slot suivant :

  • Le signal valueChanged(int) du QSlider : il est émis dès que l'on change la valeur du curseur du slider en le déplaçant. La particularité de ce signal est qu'il envoie un paramètre de type int (la nouvelle valeur du slider).
  • Le slot display(int) du QLCDNumber : il affiche la valeur qui lui est passée en paramètre.

La connexion se fait avec le code suivant :

Code : C++
1
QObject::connect(m_slider, SIGNAL(valueChanged(int)), m_lcd, SLOT(display(int)));


Bizarre n'est-ce pas ? :p
Il suffit d'indiquer le type du paramètre envoyé, ici un int, sans donner de nom à ce paramètre. Qt fait automatiquement la connexion entre le signal et le slot et "transmet" le paramètre au slot.

Le transfert de paramètre se fait comme ceci :

Connexion int à int


Ici il n'y a qu'un paramètre à transmettre, c'est donc simple. Sachez toutefois qu'il pourrait très bien y avoir plusieurs paramètres.

Le type des paramètres doivent correspondre absolument !
Vous ne pouvez pas connecter un signal qui envoie (int, double) à un slot qui reçoit (int, int). C'est un des avantages du mécanisme des signaux et des slots : il respecte le type des paramètres. Veillez donc à ce que les signatures soient identiques entre votre signal et votre slot.
En revanche, un signal peut envoyer plus de paramètres à un slot que celui-ci ne peut en recevoir. Dans ce cas, les paramètres supplémentaires seront ignorés.


Résultat : quand on change la valeur du slider, le LCD affiche la valeur correspondante !

Un afficheur LCD génère des évènements


Mais comment je sais moi quels sont les signaux et les slots que proposent chacune des classes ? Et aussi, comment je sais qu'un signal envoie un int en paramètre ?


La réponse devrait vous paraître simple les amis : la doc, la doc, la doc ! :D

Si vous regardez la documentation de la classe QLCDNumber, vous pouvez voir au début la liste de ses propriétés (attributs) et ses méthodes. Un peu plus bas, vous avez la liste des slots ("Public Slots") et des signaux ("Signals") qu'elle possède !

Les signaux et les slots sont hérités comme les attributs et méthodes. Et ça, c'est génial, bien qu'un peu déroutant au début.
Vous noterez donc qu'en plus des slots propres à QLCDNumber, celui-ci propose de nombreux autres slots qui ont été définis dans sa classe parente QWidget, et même des slots issus de QObject ! Vous pouvez par exemple lire :
  • 19 public slots inherited from QWidget
  • 1 public slot inherited from QObject

N'hésitez pas à consulter les slots (ou signaux) qui sont hérités des classes parentes. Parfois on va vous demander d'utiliser un signal ou un slot que vous ne verrez pas dans la page de documentation de la classe : vérifiez donc si celui-ci n'est pas défini dans une classe parente !


Exercice



Pour vous entraîner, je vous propose de réaliser une petite variation du code source précédent.
Au lieu d'afficher le nombre avec un QLCDNumber, affichez-le sous la forme d'une jolie barre de progression comme ceci :

Slider et progressbar


Je ne vous donne que 3 indications qui devraient vous suffire :

  • La barre de progression est gérée par un QProgressBar
  • Il faut donner des dimensions à la barre de progression pour qu'elle apparaisse correctement, à l'aide de la méthode setGeometry() que l'on a déjà vue auparavant.
  • Le slot récepteur du QProgressBar est setValue(int). Il s'agit d'un de ses slots, mais la documentation vous indique qu'il y en a d'autres. Par exemple, reset() remet à zéro la barre de progression. Pourquoi ne pas ajouter un bouton qui remettrait à zéro la barre de progression ?

C'est tout. Bon courage :)

Créer ses propres signaux et slots

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à :D ) 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 :

Fenêtre avec slider


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 :

Connexion entre le slider et la fenêtre


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 :p
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 ! :)

Le slider élargit la fenêtre


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 :p ).
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 :

Un slider vertical


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 :

Echange de signaux entre objets


Dans l'ordre, voici ce qui s'est passé :

  1. Le signal valueChanged du slider a appelé le slot changerLargeur de la fenêtre.
  2. 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.
  3. 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 ! :D

Q.C.M.

Laquelle de ces notions n'existe pas normalement en C++ ?
Peut-on connecter un signal à plusieurs slots ?
Les signaux et les slots sont-ils hérités ?
Quelle(s) condition(s) doi(ven)t être remplie(s) pour que l'on puisse définir un signal ou un slot personnalisé dans une classe ?
Comment émet-on un signal ?

Statistiques de réponses au QCM

Eh ben dites donc les amis, que de nouveautés dans ce chapitre décidément !

Les signaux et les slots, c'est vraiment ce qui fait la force de Qt... mais ses détracteurs disent que c'est une erreur d'avoir voulu "modifier" le langage C++. En effet, la compilation est plus lourde car il y a des étapes de pré-compilation à effectuer impérativement si on veut que le code soit compilable. C'est un point de vue qui se défend.

L'avantage de ce système, et ça personne ne le discute, c'est qu'il est robuste. On dispose d'une extraordinaire souplesse pour faire communiquer des objets entre eux :

  • Un signal peut appeler le slot d'un autre objet pour l'informer d'un évènement.
  • Un signal peut appeler plusieurs slots d'objets différents si nécessaire pour faire plusieurs traitements.
  • Un signal peut être connecté à un autre signal directement, qui lui-même peut être raccordé à un autre signal (réaction en chaîne) ou appeler un slot.
  • La connexion entre un signal et un slot permet d'échanger un ou plusieurs paramètres.
  • L'échange de paramètres entre le signal et le slot est sécurisé : Qt vérifie que la signature du signal correspond bien à celle du slot.

Les autres bibliothèques, comme wxWidgets, utilisent un ensemble de macros, moins lisibles mais qui ne nécessitent pas l'utilisation d'outils intermédiaires comme le moc.

Bref, profitez à fond des signaux et des slots, avec ça vous pouvez vraiment faire ce que vous voulez :D
Chapitre précédent Sommaire Chapitre suivant

Partager

78 commentaires pour "Les signaux et les slots"
Note moyenne : 3.86 / 4 (1682 votes)
Pseudo Commentaire
Hors ligne Shill # Posté le 30/12/2011 à 06:59:01

Avis : Très bon

@Cynn1608
@grimpeur

J'ai eu ce même problème et, plusieurs cheveux en moins, j'ai finalement trouvé un sujet où quelqu'un avait déjà répondu à cette question. :colere2:

Le solution (du moins, pour moi) est de « nettoyer » le projet. Il suffit de cliquer sur l'onglet « compiler » puis sur «Nettoyer le projet "nomDeVotreProjet".

Bonne chance ;)
Hors ligne Arnaud921 # Posté le 11/01/2012 à 22:33:25

Citation : rob9782
Bonsoir,j'ai une erreur un peu bête....

Au moment ou l'on doit utiliser les slider,j'ai copier coller le code de matheo, mais lorsque je compile il me dit:

request for member 'show' in 'Fenetre', which is of non-class type 'MaFenetre()' o_O

pourtant j'ai fait comme le demandais matheo
Code : C++
1
2
MaFenetre Fenetre();
Fenetre.show();


Il m'indique la ligne du
"Fenetre.show()"

Merci.


C'est parce que dans ton fichier main.cpp tu as créer l'objet fenetre avec des parenthèses alors que le constructeur n'est censé prendre aucune valeur :) donc tu as juste a enlevé les parenthèses, comme ceci : MaFenetre fenetre;
C'est sur c'est assez bete mais faut y penser ! Jme suis fait avoir aussi j'ai bloqué 10 mn mdr'
voila (:
Connecté -Dr3ck- # Posté le 14/01/2012 à 18:17:47
///Testostérone\\\
Avatar

Bonjour,, étant habitué à Code::Blocks, je suis un peu perdus sur Qt Créator, je conçois que ma question est très bête, mais, quand on créé nos propres slots, les commandes qmake make on les marque ou ?

U MAD BRO ? Image utilisateur
 
Hors ligne cardman # Posté le 28/01/2012 à 20:20:50

Avis : Très bon

Ville : Condécourt
Pays : France métropolitaine

Je mets 20/20 à ce tuto,
car il montre tous les cas d'utilisation des signaux et des slots,
ce tutoriel m'a permis de progresser pour l'interface graphique.

En JAVA, la gestion de l'ihm avec les événements est plus complexe.

François Mercier des Rochettes.
 
Hors ligne yb36 # Posté le 07/02/2012 à 00:38:47

Études : INSA Toulouse

Bonjour à tous

Je ne sais pas si j'ai le droit de poster ici mais je suis au niveau de creer son propre slot. Tout fonctionne très bien si je ne crée pas de nouveau slot. Mais des que je rajoute ma fonction Slot ca me met une erreur de lien :

MaFenetre.obj:-1: erreur : LNK2001: unresolved external symbol "public: virtual struct QMetaObject const * __thiscall Fenetre::metaObject(void)const " (?metaObject@Fenetre@@UBEPBUQMetaObject@@XZ)

Est ce que quelqu'un sait d'ou cela peut provenir ?

Voir tous les commentaires