Evénements et signaux / slots Qt

97

Dans le monde Qt, quelle est la différence entre les événements et les signaux / slots?

L'un remplace-t-il l'autre? Les événements sont-ils une abstraction du signal / des créneaux?

Raphael
la source

Réponses:

30

La documentation Qt l' explique probablement le mieux:

Dans Qt, les événements sont des objets, dérivés de la QEventclasse abstraite , qui représentent des choses qui se sont produites soit au sein d'une application, soit à la suite d'une activité extérieure que l'application doit connaître. Les événements peuvent être reçus et gérés par n'importe quelle instance d'une QObjectsous - classe, mais ils sont particulièrement pertinents pour les widgets. Ce document décrit comment les événements sont livrés et traités dans une application typique.

Ainsi, les événements et les signaux / créneaux sont deux mécanismes parallèles qui accomplissent les mêmes choses. En général, un événement sera généré par une entité extérieure (par exemple, le clavier ou la molette de la souris) et sera diffusé via la boucle d'événements dans QApplication. En général, à moins que vous ne configuriez le code, vous ne générerez pas d'événements. Vous pouvez les filtrer QObject::installEventFilter()ou gérer les événements dans un objet sous-classé en remplaçant les fonctions appropriées.

Les signaux et les slots sont beaucoup plus faciles à générer et à recevoir et vous pouvez connecter deux QObjectsous-classes quelconques . Ils sont gérés via la Metaclass (consultez votre fichier moc_classname.cpp pour en savoir plus), mais la plupart des communications interclasses que vous produirez utiliseront probablement des signaux et des slots. Les signaux peuvent être livrés immédiatement ou différés via une file d'attente (si vous utilisez des threads).

Un signal peut être généré.

Harald Scheirich
la source
la file d'attente différée est la même que la boucle d'événements de file d'attente, ou il existe deux files d'attente? un pour les signaux différés et un pour l'événement?
Guillaume07
29
@Raphael, honte à vous d'avoir accepté cette réponse. - Le paragraphe cité ne contient même pas les mots «signal» ou «slot»!
Robert Siemer
1
@neuronet: le paragraphe est cité avec «[il] l'explique le mieux», ce qui n'est pas le cas. Pas du tout. Pas même un peu.
Robert Siemer
@Robert, donc si cette petite ligne d'introduction était remplacée par "Voyons d'abord comment les documents expliquent les événements, avant de passer à la façon dont ils sont liés aux signaux", seriez-vous d'accord avec la réponse? Si tel est le cas, nous pouvons simplement modifier la réponse pour l'améliorer, car il s'agit d'un point mineur et je suis d'accord que cela pourrait être mieux formulé. [Modifier c'est une vraie question: comme je ne suis pas sûr que le reste soit beaucoup mieux ... mais juste parce que la partie citée ne mentionne pas de signaux semble un souci superficiel si le reste est vraiment bon, ce que je suis not assuming .]
eric
4
@neuronet, si vous supprimez entièrement la citation, cela améliorera la réponse à mes yeux. - Si vous supprimez toute la réponse, cela améliorera tout ce contrôle qualité.
Robert Siemer
152

Dans Qt, les signaux et les événements sont tous deux des implémentations du modèle Observer . Ils sont utilisés dans différentes situations car ils ont des forces et des faiblesses différentes.

Tout d'abord, définissons exactement ce que nous entendons par «événement Qt»: une fonction virtuelle dans une classe Qt, que vous êtes censé réimplémenter dans une de vos classes de base si vous souhaitez gérer l'événement. Il est lié au modèle de méthode de modèle .

Notez comment j'ai utilisé le mot « poignée ». En effet, voici une différence fondamentale entre l'intention des signaux et des événements:

  • Vous « gérez » les événements
  • Vous « êtes averti » des émissions de signaux

La différence est que lorsque vous «gérez» l'événement, vous assumez la responsabilité de «répondre» avec un comportement utile en dehors de la classe. Par exemple, considérons une application qui a un bouton avec un numéro dessus. L'application doit permettre à l'utilisateur de se concentrer sur le bouton et de modifier le numéro en appuyant sur les touches du clavier «haut» et «bas». Sinon, le bouton devrait fonctionner normalement QPushButton(il peut être cliqué, etc.). Dans Qt, cela se fait en créant votre propre petit "composant" réutilisable (sous-classe de QPushButton), qui se réimplémente QWidget::keyPressEvent. Pseudocode:

class NumericButton extends QPushButton
    private void addToNumber(int value):
        // ...

    reimplement base.keyPressEvent(QKeyEvent event):
        if(event.key == up)
            this.addToNumber(1)
        else if(event.key == down)
            this.addToNumber(-1)
        else
            base.keyPressEvent(event)

Voir? Ce code présente une nouvelle abstraction: un widget qui agit comme un bouton, mais avec quelques fonctionnalités supplémentaires. Nous avons ajouté cette fonctionnalité très facilement:

  • Depuis que nous avons réimplémenté un virtuel, notre implémentation est automatiquement devenue encapsulée dans notre classe. Si les concepteurs de Qt avaient créé keyPressEventun signal, nous devrons décider s'il faut hériter QPushButtonou simplement se connecter extérieurement au signal. Mais ce serait stupide, car dans Qt on s'attend toujours à ce que vous héritiez lors de l'écriture d'un widget avec un comportement personnalisé (pour une bonne raison - réutilisabilité / modularité). Ainsi, en créant keyPressEventun événement, ils transmettent leur intention qui keyPressEventn'est qu'un élément de base de la fonctionnalité. S'il s'agissait d'un signal, cela ressemblerait à une chose face à l'utilisateur, alors que ce n'est pas prévu.
  • Puisque l'implémentation de la classe de base de la fonction est disponible, nous implémentons facilement le modèle de chaîne de responsabilité en gérant nos cas spéciaux (touches haut et bas) et en laissant le reste à la classe de base. Vous pouvez voir que ce serait presque impossible s'il keyPressEvents'agissait d'un signal.

La conception de Qt est bien pensée - ils nous ont fait tomber dans le gouffre du succès en rendant facile de faire la bonne chose et difficile de faire la mauvaise chose (en faisant de keyPressEvent un événement).

D'un autre côté, considérez l'utilisation la plus simple de QPushButton- simplement l'instancier et être notifié quand on clique dessus :

button = new QPushButton(this)
connect(button, SIGNAL(clicked()), SLOT(sayHello())

Ceci est clairement destiné à être fait par l' utilisateur de la classe:

  • si nous devions sous-classer QPushButtonchaque fois que nous voulons qu'un bouton nous avertisse d'un clic, cela nécessiterait beaucoup de sous-classes sans raison valable! Un widget qui affiche toujours un "Hello world" messageboxlorsque vous cliquez dessus n'est utile que dans un seul cas - il n'est donc absolument pas réutilisable. Encore une fois, nous n'avons pas d'autre choix que de faire la bonne chose - en nous y connectant de l'extérieur.
  • nous pouvons vouloir connecter plusieurs slots à clicked()- ou connecter plusieurs signaux à sayHello(). Avec les signaux, il n'y a pas de problème. Avec le sous-classement, vous devrez vous asseoir et réfléchir à certains diagrammes de classe jusqu'à ce que vous décidiez d'une conception appropriée.

Notez que l'un des lieux QPushButtonémet clicked()est dans son mousePressEvent()implémentation. Cela ne veut pas dire clicked()et mousePressEvent()sont interchangeables - juste qu'ils sont liés.

Ainsi, les signaux et les événements ont des objectifs différents (mais sont liés en ce que les deux vous permettent de «vous abonner» à une notification de quelque chose qui se passe).

Stefan Monov
la source
J'ai l'idée. L'événement est par classe, toutes les instances d'une classe réagiront de la même manière, toutes appeleront le même événement QClassName ::. Mais le signal est par objet, chaque objet peut avoir sa connexion signal-slot unique.
炸鱼 薯条 德里克
39

Je n'aime pas les réponses pour l'instant. - Permettez-moi de me concentrer sur cette partie de la question:

Les événements sont-ils une abstraction du signal / des créneaux?

Réponse courte: non. La longue réponse soulève une «meilleure» question: comment les signaux et les événements sont-ils liés?

Une boucle principale inactive (Qt par exemple) est généralement "bloquée" dans un appel select () du système d'exploitation. Cet appel fait «dormir» l'application, alors qu'elle passe un tas de sockets ou de fichiers ou autre au noyau demandant: si quelque chose change sur ceux-ci, laissez l'appel select () revenir. - Et le noyau, en tant que maître du monde, sait quand cela se produit.

Le résultat de cet appel à select () pourrait être: de nouvelles données sur le socket se connectent à X11, un paquet vers un port UDP sur lequel nous écoutons est entré, etc. - Ce truc n'est ni un signal Qt, ni un événement Qt, et le La boucle principale Qt décide elle-même si elle transforme les nouvelles données en l'une, l'autre ou l'ignore.

Qt pourrait appeler une méthode (ou plusieurs) comme keyPressEvent (), la transformant effectivement en événement Qt. Ou Qt émet un signal, qui en effet recherche toutes les fonctions enregistrées pour ce signal et les appelle l'une après l'autre.

Une différence de ces deux concepts est visible ici: un slot n'a pas de vote pour savoir si d'autres slots enregistrés sur ce signal seront appelés ou non. - Les événements ressemblent plus à une chaîne et le gestionnaire d'événements décide s'il interrompt cette chaîne ou non. Les signaux ressemblent à une étoile ou à un arbre à cet égard.

Un événement peut se déclencher ou être entièrement transformé en signal (n'en émettez qu'un seul et n'appelez pas «super ()»). Un signal peut être transformé en événement (appelez un gestionnaire d'événements).

Ce qui résume ce qui dépend du cas: le signal clicked () - résume les événements de la souris (un bouton descend et remonte sans trop bouger). Les événements de clavier sont des abstractions de niveaux inférieurs (des choses comme 果 ou é sont plusieurs touches sur mon système).

Peut-être que focusInEvent () est un exemple du contraire: il pourrait utiliser (et donc abstrait) le signal clicked (), mais je ne sais pas si c'est le cas.

Robert Siemer
la source
4
Je trouve cette partie: "un slot n'a pas de vote sur le fait que d'autres slots enregistrés sur ce signal seront appelés ou non" très éclairant pour la différence entre les signaux et les événements. Cependant, j'ai une question connexe: que sont les messages et comment les messages sont-ils liés aux signaux et aux événements? (s'ils sont liés). Les messages sont-ils une abstraction des signaux et des événements? Peuvent-ils être utilisés pour décrire de telles interactions entre QObjects (ou d'autres objets?)
user1284631
@axeoth: Dans Qt, il n'y a pas de "messages". Eh bien, il y a une boîte de message, mais c'est à peu près tout.
Réintégrer Monica le
@KubaOber: Merci. Je voulais dire «événements».
user1284631
3
@axeoth: Alors votre question est absurde. Il lit: "Que sont les événements et comment les événements sont-ils liés aux signaux et aux événements".
Réintégrer Monica le
@KubaOber: Je ne me souviens pas du contexte un an plus tard. Quoi qu'il en soit, il semble que je demandais presque la même chose que l'OP: quelle est la différence entre les événements et comment les événements sont liés aux signaux et aux créneaux horaires. Ce qui était vraiment le cas à l'époque, IIRC, je cherchais un moyen d'envelopper les classes pilotées par le signal / slot Qt dans une sorte de classes non-Qt, donc je cherchais un moyen de commander la première depuis l'intérieur de cette dernière. J'imagine que j'envisageais de leur envoyer des événements, car cela signifiait que je n'avais qu'à inclure les en-têtes Qt, et ne pas forcer mes classes à implémenter le signal / les slots.
user1284631
16

Les événements sont distribués par la boucle d'événements. Chaque programme GUI a besoin d'une boucle d'événements, quoi que vous l'écriviez sous Windows ou Linux, en utilisant Qt, Win32 ou toute autre bibliothèque GUI. De plus, chaque thread a sa propre boucle d'événements. Dans Qt, "GUI Event Loop" (qui est la boucle principale de toutes les applications Qt) est masquée, mais vous la lancez en appelant:

QApplication a(argc, argv);
return a.exec();

Les messages que le système d'exploitation et les autres applications envoient à votre programme sont envoyés sous forme d'événements.

Les signaux et les slots sont des mécanismes Qt. Dans le processus de compilations utilisant moc (compilateur de méta-objets), ils sont modifiés en fonctions de rappel.

L'événement doit avoir un récepteur, qui doit le distribuer. Personne d'autre ne devrait avoir cet événement.

Tous les slots connectés au signal émis seront exécutés.

Vous ne devriez pas considérer les signaux comme des événements, car comme vous pouvez le lire dans la documentation Qt:

Lorsqu'un signal est émis, les slots qui lui sont connectés sont généralement exécutés immédiatement, tout comme un appel de fonction normal. Lorsque cela se produit, le mécanisme des signaux et des slots est totalement indépendant de toute boucle d'événement GUI.

Lorsque vous envoyez un événement, il doit attendre un certain temps jusqu'à ce que la boucle d'événements distribue tous les événements qui sont venus plus tôt. Pour cette raison, l'exécution du code après l'envoi d'un événement ou d'un signal est différente. Le code suivant l'envoi d'un événement sera exécuté immédiatement. Avec les mécanismes de signaux et de slots, cela dépend du type de connexion. Normalement, il sera exécuté après tous les emplacements. En utilisant Qt :: QueuedConnection, il sera exécuté immédiatement, tout comme les événements. Vérifiez tous les types de connexion dans la documentation Qt .

crameur
la source
Cette phrase me donne une compréhension concise de la différence entre le signal-slot Qt et les événements:When you send an event, it must wait for time when event loop dispatch all events that came earlier. Because of this, execution of the cod after sending event or signal is different
swdev
7

Il existe un article qui traite du traitement des événements en détail: http://www.packtpub.com/article/events-and-signals

Il discute de la différence entre les événements et les signaux ici:

Les événements et les signaux sont deux mécanismes parallèles utilisés pour accomplir la même chose. En règle générale, les signaux sont utiles lors de l'utilisation d'un widget, tandis que les événements sont utiles lors de l'implémentation du widget. Par exemple, lorsque nous utilisons un widget comme QPushButton, nous sommes plus intéressés par son signal clicked () que par les événements de bas niveau de pression de la souris ou de touche qui ont provoqué l'émission du signal. Mais si nous implémentons la classe QPushButton, nous sommes plus intéressés par l'implémentation de code pour les événements de souris et de touches. En outre, nous gérons généralement les événements, mais nous sommes notifiés par des émissions de signaux.

Cela semble être une façon courante d'en parler, car la réponse acceptée utilise certaines des mêmes phrases.


Remarque, s'il vous plaît voir les commentaires utiles ci-dessous sur cette réponse de Kuba Ober, qui me font me demander si cela pourrait être un peu simpliste.

Eric
la source
Je ne vois pas comment ce seraient deux mécanismes utilisés pour accomplir la même chose - je pense que c'est une généralisation inutile qui n'aide personne.
Réintégrer Monica le
@KubaOber non, cela permet d'éviter les détails de niveau inférieur. Diriez-vous que Python n'est pas utile car vous pouvez faire la même chose en écrivant en code machine? :)
eric
2
Je dis que la première phrase de la citation se généralise au point d'être inutile. Techniquement, ce n'est pas incorrect, mais uniquement parce que vous pouvez, si vous le souhaitez, utiliser le passage d'événements pour accomplir ce que fait le mécanisme de créneau de signal, et vice-versa. Ce serait très encombrant. Donc non, les créneaux de signal ne sont pas les mêmes que les événements, ils n'accomplissent pas la même chose, et le fait que les deux existent est essentiel au succès de Qt. L'exemple de bouton cité ignore complètement la fonctionnalité générale du système de créneaux de signal.
Réintégrer Monica le
1
"Les événements et les signaux sont deux mécanismes parallèles utilisés pour accomplir la même chose dans le cadre restreint de certains widgets / contrôles de l'interface utilisateur ." La partie en gras manque cruellement et rend la citation inutile. Même dans ce cas, on peut se demander dans quelle mesure la «même chose» est réellement accomplie: les signaux vous permettent de réagir aux événements de clic sans installer de filtre d'événement sur le widget (ou dériver du widget). Le faire sans les signaux est beaucoup plus encombrant et associe étroitement le code client au contrôle. C'est une mauvaise conception.
Réintégrer Monica le
1
Les événements sont un concept omniprésent . L'implémentation particulière dans Qt les définit concrètement comme des structures de données transmises event. Les signaux et slots, sont, concrètement , des méthodes, tandis que le mécanisme de connexion est une structure de données qui permet au signal d'invoquer un ou plusieurs slots listés comme connectés à lui. J'espère que vous voyez que parler de signal / slots comme un "sous-ensemble" ou une "variante" d'événements est absurde, et vice versa. Ils sont vraiment différentes choses qui arrivent à être utilisées à des fins similaires dans le contexte de certains widgets. C'est tout. Plus vous généralisez, moins vous devenez utile à mon humble avis.
Réintégrer Monica le
5

TL; DR: les signaux et les slots sont des appels de méthode indirects. Les événements sont des structures de données. Ce sont donc des animaux très différents.

Le seul moment où ils se rejoignent est lorsque les appels d'emplacement sont effectués au-delà des limites de thread. Les arguments d'appel d'emplacement sont regroupés dans une structure de données et sont envoyés en tant qu'événement à la file d'attente d'événements du thread récepteur. Dans le thread de réception, la QObject::eventméthode décompresse les arguments, exécute l'appel et retourne éventuellement le résultat s'il s'agissait d'une connexion bloquante.

Si nous voulons généraliser à l'oubli, on pourrait considérer les événements comme un moyen d'invoquer la eventméthode de l'objet cible . C'est un appel de méthode indirect, en quelque sorte - mais je ne pense pas que ce soit une façon utile d'y penser, même si c'est une vraie déclaration.

Réintégrer Monica
la source
2

Les événements (dans un sens général d'interaction utilisateur / réseau) sont généralement traités dans Qt avec des signaux / slots, mais les signaux / slots peuvent faire beaucoup d'autres choses.

QEvent et ses sous-classes ne sont en fait que de petits paquets de données standardisés permettant au framework de communiquer avec votre code. Si vous voulez faire attention à la souris d'une manière ou d'une autre, il vous suffit de regarder l'API QMouseEvent, et les concepteurs de la bibliothèque n'ont pas à réinventer la roue à chaque fois que vous devez comprendre ce que la souris a fait dans un coin de l'API Qt.

Il est vrai que si vous attendez des événements (encore une fois dans le cas général) d'une certaine sorte, votre slot acceptera presque certainement une sous-classe QEvent comme argument.

Cela dit, les signaux et les slots peuvent certainement être utilisés sans QEvents, bien que vous constatiez que l'impulsion initiale pour activer un signal sera souvent une sorte d'interaction utilisateur ou une autre activité asynchrone. Parfois, cependant, votre code atteindra juste un point où déclencher un certain signal sera la bonne chose à faire. Par exemple, le déclenchement d'un signal connecté à une barre de progression pendant un long processus n'implique pas jusqu'à ce point un QEvent.

jkerian
la source
2
"Les événements sont généralement gérés dans Qt avec des signaux / slots" - en fait non ... Les objets QEvent sont toujours transmis via des virtuels surchargés! Par exemple, il n'y a pas de signal "keyDown" dans QWidget - à la place, keyDown est une fonction virtuelle.
Stefan Monov
D'accord avec stefan, en fait un morceau assez déroutant de Qt
Harald Scheirich
1
@Stefan: Je dirais que peu d'applications Qt remplacent keyDown (et utilisent plutôt des signaux tels que QAbstractButton :: clicked et QLineEdit :: editFinished) pour justifier "typiquement". J'ai certainement déjà accroché la saisie au clavier, mais ce n'est pas la manière habituelle de gérer les événements.
jkerian
après votre modification (clarifiant ce que vous entendez par "événement"), votre message est maintenant correct. Je m'abstiens cependant de réutiliser des mots comme ça. Les choses sont assez boueuses sans appeler des signaux "un type d'événements".
Stefan Monov
2

J'ai trouvé cette question en lisant «Traitement des événements» par Leow Wee Kheng. Il dit aussi:

entrez la description de l'image ici

Jasmine Blanchette dit:

La principale raison pour laquelle vous utiliseriez des événements plutôt que des appels de fonction standard, ou des signaux et des slots, est que les événements peuvent être utilisés à la fois de manière synchrone et asynchrone (selon que vous appelez sendEvent () ou postEvents ()), tout en appelant une fonction ou en invoquant un slot est toujours synchrone. Un autre avantage des événements est qu'ils peuvent être filtrés.

francek
la source
1

Autre considération pragmatique mineure: émettre ou recevoir des signaux nécessite d'hériter QObjectalors qu'un objet de n'importe quel héritage peut poster ou envoyer un événement (puisque vous invoquez QCoreApplication.sendEvent()ou postEvent()) Ce n'est généralement pas un problème mais: pour utiliser des signaux PyQt nécessite étrangement QObjectd'être la première super classe, et vous ne voudrez peut-être pas réorganiser votre ordre d'héritage juste pour pouvoir envoyer des signaux.)

bootchk
la source
-1

À mon avis, les événements sont complètement redondants et pourraient être rejetés. Il n'y a aucune raison pour que les signaux ne puissent pas être remplacés par des événements ou des événements par des signaux, sauf que Qt est déjà configuré tel quel. Les signaux en file d'attente sont enveloppés par des événements et les événements pourraient éventuellement être enveloppés par des signaux, par exemple:

connect(this, &MyItem::mouseMove, [this](QMouseEvent*){});

Remplacerait la mouseMoveEvent()fonction de commodité trouvée dans QWidget(mais plus QQuickItemmaintenant) et gérerait les mouseMovesignaux qu'un gestionnaire de scène émettrait pour l'élément. Le fait que le signal soit émis au nom de l'élément par une entité extérieure est sans importance et se produit assez souvent dans le monde des composants Qt, même si cela n'est pas autorisé (les composants Qt contournent souvent cette règle). Mais Qt est un conglomérat de nombreuses décisions de conception différentes et à peu près coulé dans la pierre de peur de casser l'ancien code (ce qui arrive assez souvent de toute façon).

user1095108
la source
Les événements ont l'avantage de propager la QObjecthiérarchie parent-enfant, si nécessaire. Les connexions signal / créneau ne sont qu'une promesse d'appeler une fonction, directement ou indirectement, lorsqu'une certaine condition est remplie. Il n'y a pas de hiérarchie de traitement associée avec les signaux et les slots.
anonyme
Vous pouvez implémenter des règles de propagation similaires avec des signaux. Il n'y a pas de règle. qu'un certain signal ne peut pas être réémis vers un autre objet (enfant). Vous pouvez ajouter un acceptedparamètre de référence à un signal et aux slots le traitant ou vous pouvez avoir une structure comme paramètre de référence avec un acceptedchamp. Mais Qt a fait les choix de conception qu'il a faits et ils sont désormais gravés dans la pierre.
user1095108
1
Vous ne faites que réimplémenter des événements si vous le faites comme vous le suggérez.
Fabio A.
@FabioA. C'est le but de la question du PO. Les événements et les signaux peuvent / pourraient se remplacer.
user1095108
Si vos événements de réimplémentation, vous ne remplacez pas les événements. Plus encore: les signaux sont implémentés en plus des événements. Vous avez besoin d'une sorte de "système d'événements" pour indiquer à d'autres boucles d'événements, éventuellement dans un thread qui n'est pas le vôtre, que dans le futur il doit exécuter une fonction donnée quelque part.
Fabio A.