web-dev-qa-db-fra.com

Événements Qt et signal/slots

Dans le monde Qt, quelle est la différence d'événements et de signaux/slots? 

Est-ce que l'un remplace l'autre? Les événements sont-ils une abstraction du signal/des slots?

78
Raphael

La documentation Qt l'explique probablement mieux:

Dans Qt, les événements sont des objets, dérivés de la classe abstraite QEvent, que représenter des choses qui se sont passées soit dans une application ou en tant que résultat d'une activité extérieure que le l'application doit savoir sur . Les événements peuvent être reçus et gérés par toute instance d'une sous-classe QObject, mais ils sont particulièrement pertinents pour widgets. Ce document décrit comment les événements sont livrés et traités dans un fichier application typique.

Donc, les événements et le signal/les slots sont deux mécanismes parallèles accomplissant 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 transmis via la boucle d'événement dans QApplication. En général, sauf si vous configurez le code, vous ne générerez pas d'événements. Vous pouvez les filtrer à travers 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 sous-classes QObject. Elles sont gérées par la métaclasse (consultez votre fichier moc_classname.cpp pour plus d’informations), mais la plupart des communications interclasses que vous allez produire utiliseront probablement des signaux et des slots. Les signaux peuvent être dé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é.

27
Harald Scheirich

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 classe de base si vous souhaitez gérer l'événement. C'est lié au modèle Modèle de méthode .

Notez comment j'ai utilisé le mot "handle _". En effet, voici une différence fondamentale entre l’intention des signaux et celle des événements:

  • Vous "handle" événements
  • Vous "recevez notification de" émissions de signaux

La différence est que lorsque vous "gérez" l'événement, vous prenez la responsabilité de "réagir" avec un comportement utile en dehors de la classe. Par exemple, considérons une application comportant un bouton avec un numéro. L'application doit permettre à l'utilisateur d'activer le bouton et de modifier le numéro en appuyant sur les touches du clavier "haut" et "bas". Sinon, le bouton devrait fonctionner comme une QPushButton normale (vous pouvez cliquer dessus, etc.). Cela se fait en Qt en créant votre propre petit "composant" réutilisable (sous-classe de QPushButton), qui 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 des 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 encapsulée dans notre classe. Si les concepteurs de Qt avaient défini keyPressEvent comme signal, nous devions décider d'hériter de QPushButton ou simplement de nous connecter au signal en externe. Mais ce serait stupide, car dans Qt, on s'attend à toujours hériter lors de l'écriture d'un widget avec un comportement personnalisé (pour une bonne raison - réutilisabilité/modularité). Donc, en faisant de keyPressEvent un événement, ils expriment leur intention de dire que keyPressEvent n'est qu'un élément de base de la fonctionnalité. S'il s'agissait d'un signal, cela ressemblerait à une chose qui s'adresse à l'utilisateur, alors que ce n'est pas prévu.
  • Puisque la fonction de base-implémentation de la fonction est disponible, nous implémentons facilement le modèle Chaîne de responsabilité en traitant nos cas spéciaux (touches haut et bas) et en laissant le reste à la classe de base. Vous pouvez voir que cela serait presque impossible si keyPressEvent était un signal.

La conception de Qt est bien pensée - ils nous ont fait tomber dans le gouffre du succès en facilitant la tâche, et difficilement la mauvaise chose (en faisant de keyPressEvent un événement).

D'autre part, considérons l'utilisation la plus simple de QPushButton - simplement en l'instanciant et en étant avertie quand on clique dessus:

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

Ceci est clairement destiné à être effectué par le utilisateur de la classe:

  • si nous devions sous-classer QPushButton à chaque fois que nous voulions qu'un bouton nous avertisse d'un clic, cela demanderait beaucoup de sous-classes sans bonne raison! Un widget qui affiche toujours un "Bonjour tout le monde" messagebox lorsque vous cliquez dessus n'est utile que dans un seul cas - il n'est donc pas réutilisable. Encore une fois, nous n’avons pas d’autre choix que de faire ce qui est juste - en nous y connectant à l’extérieur.
  • nous pouvons vouloir connecter plusieurs slots à clicked() - ou connecter plusieurs signaux à sayHello(). Avec des signaux, il n'y a pas de problèmes. Avec le sous-classement, vous devez vous asseoir et réfléchir à des diagrammes de classes jusqu'à ce que vous décidiez d'un design approprié.

Notez que l’un des emplacements QPushButton émet clicked() se trouve dans son implémentation mousePressEvent(). Cela ne signifie pas que clicked() et mousePressEvent() sont interchangeables, mais simplement qu'elles sont liées.

Ainsi, les signaux et les événements ont des objectifs différents (mais sont liés en ce sens qu'ils vous permettent de vous "abonner" à une notification de quelque chose qui se passe).

134
Stefan Monov

Je n’ai pas aimé les réponses jusqu’à présent. - Permettez-moi de me concentrer sur cette partie de la question:

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

Réponse courte: non. La réponse longue 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 rend l’application «en sommeil», alors qu’elle transmet un paquet de sockets, fichiers ou quoi que ce soit 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 la socket se connectent à X11, un paquet sur un port UDP sur lequel nous écoutons est entré, etc. - Ce genre de choses n'est ni un signal Qt, ni un événement Qt, et la boucle principale Qt décide elle-même si elle transforme les données fraîches en une, ou les ignore.

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

Une différence entre ces deux concepts est visible ici: un emplacement n'a pas de droit de vote sur le fait que d'autres emplacements enregistrés pour ce signal seront appelés ou non. - Les événements ressemblent davantage à 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 déclencher ou être entièrement transformé en un signal (il suffit d’en émettre un et de ne pas appeler «super ()»). Un signal peut être transformé en un événement (appelez un gestionnaire d'événements).

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

FocusInEvent () est peut-être un exemple du contraire: il pourrait utiliser (et donc abstraite) le signal clicked (), mais je ne sais pas s'il le fait réellement.

38
Robert Siemer

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

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

Les messages OS et les autres applications envoyées à votre programme sont distribués en tant qu'événements.

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

L'événement devrait avoir un récepteur, qui devrait l'envoyer. Personne d'autre ne devrait avoir cet événement.

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

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

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

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 antérieurs. De ce fait, l'exécution du code après l'envoi de l'événement ou du signal est différente. Le code suivant l'envoi d'un événement sera exécuté immédiatement. Avec les signaux et les mécanismes de slots, cela dépend du type de connexion. Normalement, il sera exécuté après tous les créneaux. 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 .

13
firescreamer

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

Il discute 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 le même chose. En général, les signaux sont utiles lorsqu’on utilise un fichier widget, alors que les événements sont utiles lors de la mise en œuvre du widget. Pour Par exemple, lorsque nous utilisons un widget comme QPushButton, nous sommes plus intéressé par son signal clicked () que par la souris de bas niveau ou appuyez sur des événements qui ont provoqué l'émission du signal. Mais si nous implémentons la classe QPushButton, nous sommes plus intéressés par le implémentation du code pour la souris et les événements clés. Aussi, nous avons l'habitude gérer les événements mais être averti par les é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.


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

6
neuronet

TL; DR: les signaux et les slots sont des appels de méthode indirects. Les événements sont des structures de données. Donc, ce sont des animaux assez différents.

Le seul moment où ils se rencontrent est lorsque des appels de logement sont passés au-delà des limites du 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'événements du thread de réception. Dans le thread récepteur, la méthode QObject::event décompresse les arguments, exécute l'appel et renvoie éventuellement le résultat s'il s'agissait d'une connexion bloquante.

Si nous sommes disposés à généraliser à l'oubli, les événements peuvent être considérés comme un moyen d'invoquer la méthode event de l'objet cible. C’est un appel indirect à une méthode, mais je ne pense pas que ce soit une façon utile d’y réfléchir, même si c’est une affirmation vraie.

4
Kuba Ober

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

QEvent et ses sous-classes ne sont fondamentalement que de petits paquets de données normalisés permettant au framework de communiquer avec votre code. Si vous voulez prêter attention à la souris, il vous suffit de regarder l'API QMouseEvent, et les concepteurs de bibliothèques n'ont pas à réinventer la roue à chaque fois que vous devez comprendre ce que la souris a fait l'API Qt.

Il est vrai que si vous attendez des événements (encore une fois dans le cas général), votre emplacement 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 l'impulsion initiale d'activation du signal soit souvent une sorte d'interaction de l'utilisateur ou une autre activité asynchrone. Parfois, cependant, votre code atteindra simplement un point où déclencher un certain signal sera la bonne chose à faire. Par exemple, déclencher un signal connecté à un barre de progression pendant un processus long n'implique pas un QEvent jusqu'à ce point.

3
jkerian

Une autre considération pragmatique mineure: émettre ou recevoir des signaux nécessite l'héritage QObject, alors qu'un objet de n'importe quel héritage peut poster ou envoyer un événement (puisque vous appelez QCoreApplication.sendEvent() ou postEvent()). QObject pour ê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.)

1
bootchk

J'ai trouvé cette question en lisant 'Event processing' de Leow Wee Kheng. Il dit aussi:

enter image description here

0
francek

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

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

Remplacerait la fonction de commodité mouseMoveEvent() trouvée dans QWidget (mais plus dans QQuickItem) et traiterait les signaux mouseMove qu'un gestionnaire de scène émettrait pour l'élément. Le fait que le signal soit émis pour le compte 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 ce n'est apparemment pas autorisé (les composants Qt contournent souvent cette règle). Mais Qt est un conglomérat aux multiples décisions en matière de conception et jeté dans les pierres de peur de rompre l’ancien code (ce qui arrive assez souvent de toute façon).

0
user1095108