FAQ Qt
FAQ QtConsultez toutes les FAQ
Nombre d'auteurs : 26, nombre de questions : 298, dernière mise à jour : 15 juin 2021
- Les signaux et autres slots ?
- Comment s'interfacent les signaux et les slots ?
- Comment créer ses propres signaux et slots avec transmission de valeurs ?
- Comment utiliser les autoconnexions ?
- Comment paramétrer un slot selon les objets qui lui sont connectés ?
- Quelles sont les différentes méthodes d'exécution d'un slot ?
- Comment récupérer et utiliser l'objet déclencheur d'un slot ?
- Comment récupérer et utiliser l'objet déclencheur d'un slot avec QSignalMapper ?
Les signaux et slots sont l'implémentation par Nokia du pattern observer. Ce pattern permet de prévenir les classes voulant observer un événement.
Pour utiliser ce système, il faut hériter de QObject, soit directement, soit à l'aide d'un des objets dérivés de QObject. Ensuite, il faut définir la macro Q_OBJECT qui servira à qmake puis à moc pour générer le code nécessaire à l'utilisation de ces fonctions.
Pour déclarer des signaux, il suffit d'indiquer :
signals
:
void
monSignal();
Pour que moc génère le code qui va bien et pour que le signal puisse être utilisé. On appellera un signal par :
emit
monSignal();
Ces signaux peuvent être connectés à d'autres signaux ou à des slots. Ces slots sont des fonctions du développeur qui seront appelées dès que possible.
public
slots
:
void
monSlot();
Une fois les slots définis, il suffit de connecter les signaux et les slots entre eux. Comme il s'agit de connexion directe, il n'est pas possible de connecter un signal sans paramètre à un slot avec un paramètre. Pour cela, il faut utiliser l'intermédiaire QSignalMapper.
connect
(this
, SIGNAL
(monSignal()), somethingElse, SLOT
(monSlot()));
On constatera aussi que signaux et slots ne peuvent pas retourner de valeur, pour l'instant.
Lien : Les signaux et slots
Lien : Tutoriel sur les signaux et slots de Qt 4par Miles
Dans la question Les signaux et autres slots nous avons vu comment s'interface une connexion entre signaux et slots simples, c'est-à-dire sans transmission de valeur. Or, les signaux et slots ont la capacité de se transmettre des données par le biais de leurs arguments.
Prenons l'exemple de la classe QLineEdit de Qt.
Son signal textChanged(constQString&) permet de récupérer le texte présent dans le QLineEdit au moment de l'émission du signal. De même, son slot setText(constQString&) permet de redéfinir le texte contenu dans le QLineEdit à l'aide d'un objet QString.
Rappel : il est possible de connecter un signal avec un slot, mais on peut
également connecter un signal à un autre signal.
Pour créer une connexion signal/slot (ou signal/signal) qui permette la transmission de valeurs,
il suffit d'écrire la signature complète de chaque signal ou slot dans la fonction connect().
Autrement dit, il faut indiquer le nom des signaux/slots en question, ainsi que
les types des arguments qu'ils prennent en paramètres.
monSignal(QString
,int
) //signature d'un signal
monSlot(QString
,int
) //signature d'un slot
connect
(objet1,SIGNAL
(monSignal(QString
,int
)),objet2,SLOT
(monSlot(QString
,int
)))
Attention, car il faut prendre certaines précautions. Dans une fonction connect(), les types des arguments des deux fonctions doivent être compatibles et placés dans le même ordre. Des arguments sont compatibles si le même objet est en jeu. Par exemple, constQString& est compatible avec QString. Par contre, QString* et QString ne le sont pas. Voici un exemple de connexion avec deux types compatibles.
connect
(this
, SIGNAL
(monSignal(QString
, int
)), somethingElse, SLOT
(monSlot(const
QString
&
, int
)));
Cependant, le signal ou le slot qui est situé dans la partie droite de la fonction connect() peut avoir un nombre d'arguments inférieur ou égal à celui du signal situé à gauche. Par exemple la connexion suivante est valide. L'argument int sera simplement ignoré.
connect
(this
,SIGNAL
(monSignal(QString
,int
)),somethingElse,SLOT
(monSlot(QString
)))
Cette question nécessite d'avoir compris la création de signaux/slots simples (sans arguments) décrite dans la question Les signaux et autres slots ?
Pour créer un signal ou un slot avec des arguments, il suffit d'ajouter les noms des types dans le prototype.
class
myClass : public
xxx
{
Q_OBJECT
...
signals
:
void
monSignal(type1,type2)
public
slots
:
void
monSlot(type1,type2);
...
}
Ainsi, pour émettre son signal il suffit d'utiliser le mot clé emit.
void
fonction()
{
nom_type t;
emit
monSignal(t);
}
La connexion se fera avec un signal ou un slot qui prend un argument de type nom_type.
Sous Qt, les connexions entre les signaux et slots peuvent être mises en place, soit manuellement, soit automatiquement, en utilisant la capacité qu'a QMetaObject d'établir des liens entre ces derniers.
Cette partie ne concerne pas les connexions manuelles : elle traite de la méthode automatique.
Bien qu'il soit plutôt aisé d'implémenter un slot et de le connecter dans le constructeur, nous pouvons tout aussi bien utiliser l'outil d'autoconnexion de QMetaObject pour connecter le signal clicked() de myButton à un slot dans notre classe, ou pour connecter les signaux et les slots clicked() :
QMetaObject
::
connectSlotsByName(QObject
*
object);
Cette fonction du QMetaObject de Qt connecte automatiquement tout slot qui respecte la convention on_Nomobjet_Nomsignal() au signal Nomsignal() correspondant de l'objet Nomobjet. Deux conditions doivent être remplies.
- L'objet NomObjet doit être un enfant de l'objet passé en paramètre à la méthode connectSlotByName().
- Les objets (parents et enfants) doivent être nommés avec la fonction setObjectName(constQString& name).
Supposons, cette fois-ci, que nous créons notre fenêtre via Qt Designer et non plus en ligne de code. Nous possédons alors un fichier supplémentaire, à savoir : mainwindows.ui. Lors de la compilation, l'outil uic de Qt se charge de générer le code de la fonction setupUi() de notre fenêtre mainwindows.ui. Le code généré utilise alors de la même façon :
- setObjectName(constQString& name) : pour nommer les objets définis dans mainwindows.ui ;
- QMetaObject::connectSlotsByName : pour autoconnecter les objets définis dans mainwindows.ui pendant l'appel à setupUi().
Il est donc possible d'utiliser, de la même façon, les autoconnexions sur une GUI codée à la main que sur une GUI créée à l'aide de Qt Designer.
Si deux enfants ont le même nom, l'auto-connect ne se fera que sur le premier enfant trouvé.
Si QMetaObject::connectSlotsByName est appelé plusieurs fois, les connections générées seront multiples. Lors de l'utilisation d'un *.ui, ne pas oublier que la fonction setupUi() appelle l'autoconnexion.
Téléchargement : Utilisation de autoconnect sans *.ui
Téléchargement : Utilisation de autoconnect avec *.ui
Qt propose de nombreuses classes avec de nombreux signaux déjà définis. Cependant, il peut parfois être intéressant d'ajouter de l'information à ces signaux afin de les paramétrer selon l'objet émetteur. On pourrait ainsi souhaiter que différents boutons réalisant une action identique, par exemple ouvrir une page Web lorsque l'on clique dessus, soient connectés à un même slot auquel on précise l'URL à utiliser. Cependant, le signal clicked() de la classe QPushButton ne transmet aucun paramètre, ce qui ne permet pas de spécifier l'URL.
Une première solution consiste alors à connecter le signal clicked() de chaque bouton à un slot différent qui se contentera d'appeler la fonction d'ouverture de la page Web avec l'URL correspondante. Cependant, comme il est nécessaire de créer un slot différent par bouton, cela rallonge inutilement la taille du code, surtout lorsqu'il y a un grand nombre de boutons.
Une autre solution est d'utiliser la classe QSignalMapper. Dans notre exemple, celle-ci va s'occuper d'appeler le slot ouvrant la page Web avec un paramètre configuré pour chacun des boutons (l'URL de la page). Nous avons donc d'un côté les signaux clicked() des différents QPushButton, et de l'autre un slot openUrl(constQString& url) et le QSignalMapper au milieu pour faire les correspondances.
Tout d'abord il faut créer un objet QSignalMapper, puis connecter les signaux des boutons à son slot map(). On définit alors, pour chacun des boutons, le paramètre à utiliser pour l'appel à openUrl() via setMapping().
mapper =
new
QSignalMapper
();
// Bouton 1
connect
(bouton1, SIGNAL
(clicked()), mapper, SLOT
(map()));
mapper->
setMapping(bouton1, "http://url1"
);
// Bouton 2
connect
(bouton2, SIGNAL
(clicked()), mapper, SLOT
(map()));
mapper->
setMapping(bouton2, "http://url2"
);
// Autres boutons ?
Enfin, il suffit de connecter le signal mapped() de QSignalMapper à notre slot final. Ainsi quand un bouton émettra son signal, le slot openUrl() sera utilisé avec le paramètre correspondant au bouton.
connect
(mapper, SIGNAL
(mapped(const
QString
&
)), this
, SLOT
(openUrl(const
QString
&
)));
Note : QSignalMapper ne se limite pas à des paramètres de type QString. Il est également possible d'utiliser des entiers ou encore des QWidget* et des QObject*. Il faut dans ce cas utiliser le signal mapped() correspondant au type que l'on veut transmettre.
Un slot n'est, finalement, rien de plus qu'une fonction normale à laquelle sont rattachées quelques informations liées au concept de métaobjet de Qt. En conséquence, il est tout à fait possible de les appeler comme une fonction, et ce, de façon inter-thread (avec les précautions qui s'imposent dans un tel contexte). Comme précisé dans Comment fonctionne QThread ?, l'utilisation correcte et sûre de slots dans un thread requiert l'exécution d'une boucle d'événements (eventloop).
On peut répertorier les méthodes d'appel dans deux catégories :
- appel direct (à la façon d'une fonction normale) ;
- appel par un signal (paradigme classique et récurrent lors de l'écriture d'une application utilisant Qt).
Cette dernière catégorie peut être elle-même divisée en deux autres catégories :
- appel direct ;
- appel placé en queue de la boucle d'événement du thread récepteur.
Dans le premier cas, le slot est exécuté dans le contexte du thread appelant.
Dans le second cas, le contexte d'exécution du thread dépend du mode de connexion fourni à QObject::connect :
- directe (Qt::DirectConnection) : le slot est appelé immédiatement après l'émission du signal, et il est exécuté dans le thread contenant l'émission du signal ;
- placé en queue (Qt::QueuedConnection) : le signal est traité comme un événement et le slot concerné est donc appelé lors du prochain passage dans la boucle d'événement du thread dans lequel le destinataire vit (c'est-à-dire le thread dans lequel il a été créé ou, si QObject::moveToThread() a été utilisé, dans le thread qui a été donné à cette fonction) ;
- automatique (Qt::AutoConnection, paramètre par défaut): si le destinataire du signal vit dans le même thread, la connexion correspond à une connexion directe ; dans le cas contraire le comportement est celui d'une connexion en queue.
Cet exemple produira une sortie semblable à ceci.
MyThread::threadSlot(int), appel depuis le thread # 0xa28 : n= 4
MyThread::threadSlot(int), appel depuis le thread # 0xa24 : n= 1764
MyThread::threadSlot(int), appel depuis le thread # 0xa24 : n= 42
Le slot qui nous intéresse est ici MyThread::threadSlot(int). Il est appelé trois fois dans ce programme :
- dans void MyThread::run(), threadSlot() est exécuté dans le contexte du thread exécutant run(), car appelé directement ;
- dans void Window::onCallThreadSlot(), threadSlot() est exécuté dans le contexte du thread principal, car appelé directement depuis celui-ci ;
- dans void Window::onCallThreadSlotSig(), threadSlot() est exécuté dans le contexte du thread principal, car appelé indirectement par l'émission d'un signal à partir de ce thread.
Dans un slot, la méthode sender() retourne le QObject qui a déclenché ce slot via un signal (peu importe lequel).
Prenons un exemple.
monSlot est un slot personnalisé de la classe MaClasse.
MaClasse::
MaClasse()
{
QPushButton
*
btn =
new
QPushButton
("bouton"
);
QObject
::
connect
(btn, SIGNAL
(clicked()), this
, SLOT
(monSlot()));
}
MaClasse::
monSlot()
{
QObject
*
emetteur =
sender();
// emetteur contient le QPushButton btn si on clique sur ce bouton
}
Entendu, mais que faire de ce QObject ? C'est le QPushButton que l'on voulait !
Pas de panique ! Pour accéder pleinement à l'émetteur, la prochaine étape est de le caster. Voyons comment faire ça proprement avec qobject_cast.
MaClasse::
monSlot()
{
QObject
*
emetteur =
sender();
// On caste le sender en ce que nous supposons qu'il soit
QPushButton
*
emetteurCasted =
qobject_cast
<
QPushButton
*>
(emetteur);
// On teste la réussite du cast avant de procéder à un quelconque accès dessus !
if
(emetteurCasted) //emetteurCasted vaut 0 si le cast à échoué
{
// Suite du traitement
}
}
Cette méthode ne fonctionne pas en multithread ! Si l'émetteur et le récepteur ne sont pas dans le même thread, sender() ne renvoie rien (0). Dans ce cas, vous devez utiliser QSignalMapper
Lien : Comment récupérer et utiliser l'objet déclencheur d'un slot avec QSignalMapper ?
Une alternative à la méthodesender() est l'utilisation d'un QSignalMapper. Cette classe permet d'associer un signal sans paramètre d'un objet à un autre signal pourvu d'un paramètre défini. Ce paramètre peut être de type int, QString, QWidget* ou QObject*.
Voici un exemple simple d'utilisation, issu de la documentation Qt.
Dans le cas présent, on a une liste de boutons, contenant du texte, créée à partir d'une liste de QString. On souhaite lier l'action de presser un bouton (signal clicked()) à un signal qui contient le texte de ce bouton (signal clicked(QString)).
Voici comment fonctionne cet exemple.
On connecte le signal déclencheur souhaité de chaque objet avec le slot map() du QSignalMapper.
On indique au QSignalMapper des liens entre des objets et des paramètres via la méthode setMapping() (ici de type QString, mais on peut aussi le faire avec des int, QObject* ou QWidget*).
À chaque appel au slot map(), le QSignalMapper identifie l'entité qui déclenche le slot map() (sender()), cherche si cette entité est liée à un paramètre, et émet le signal mapped() avec le paramètre trouvé.
Lien : Comment récupérer et utiliser l'objet déclencheur d'un slot ?