简体   繁体   English

Qt 事件和信号/插槽

[英]Qt events and signal/slots

In the Qt world, what is the difference of events and signal/slots? Qt世界中,事件和信号/槽的区别是什么?

Does one replace the other?一个会取代另一个吗? Are events an abstraction of signal/slots?事件是信号/槽的抽象吗?

In Qt, signals and events are both implementations of the Observer pattern .在 Qt 中,信号和事件都是观察者模式的实现 They are used in different situations because they have different strengths and weaknesses.它们用于不同的情况,因为它们具有不同的优点和缺点。

First of all let's define what we mean by 'Qt event' exactly: a virtual function in a Qt class, which you're expected to reimplement in a base class of yours if you want to handle the event.首先,让我们准确定义“Qt 事件”的含义:Qt 类中的虚函数,如果您想处理该事件,则需要在您的基类中重新实现它。 It's related to the Template Method pattern .它与模板方法模式有关

Note how I used the word " handle ".请注意我是如何使用“句柄”这个词的。 Indeed, here's a basic difference between the intent of signals and events:事实上,这是信号和事件意图之间的基本区别:

  • You " handle " events你“处理”事件
  • You " get notified of " signal emissions你“得到通知”信号发射

The difference is that when you "handle" the event, you take on the responsibility to "respond" with a behavior that is useful outside the class.不同之处在于,当您“处理”事件时,您有责任以在类之外有用的行为“响应”。 For example, consider an app that has a button with a number on it.例如,考虑一个应用程序,上面有一个带有数字的按钮。 The app needs to let the user focus the button and change the number by pressing the "up" and "down" keyboard keys.该应用程序需要让用户聚焦按钮并通过按“向上”和“向下”键盘键来更改数字。 Otherwise the button should function like a normal QPushButton (it can be clicked, etc).否则按钮应该像普通的QPushButton (它可以被点击等)。 In Qt this is done by creating your own little reusable "component" (subclass of QPushButton ), which reimplements QWidget::keyPressEvent .在 Qt 中,这是通过创建您自己的QPushButton重用“组件”( QPushButton子类)来完成的,它重新实现了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)

See?看到了吗? This code presents a new abstraction: a widget that acts like a button, but with some extra functionality.这段代码提出了一个新的抽象:一个像按钮一样的小部件,但有一些额外的功能。 We added this functionality very conveniently:我们非常方便地添加了此功能:

  • Since we reimplemented a virtual, our implementation automatically became encapsulated in our class.由于我们重新实现了一个 virtual,我们的实现会自动封装在我们的类中。 If Qt's designers had made keyPressEvent a signal, we would need to decide whether to inherit QPushButton or just externally connect to the signal.如果 Qt 的设计者将keyPressEvent设为信号,我们将需要决定是继承QPushButton还是仅从外部连接到该信号。 But that would be stupid, since in Qt you're always expected to inherit when writing a widget with a custom behavior (for good reason - reusability/modularity).但这将是愚蠢的,因为在 Qt 中,您总是希望在编写具有自定义行为的小部件时继承(有充分的理由 - 可重用性/模块化)。 So by making keyPressEvent an event, they convey their intent that keyPressEvent is just a basic building block of functionality.因此,通过使keyPressEvent成为事件,他们传达了他们的意图,即keyPressEvent只是功能的基本构建块。 If it were a signal, it'd look like a user-facing thing, when it's not intended to be.如果它是一个信号,它看起来就像是一个面向用户的东西,但它并不打算这样做。
  • Since the base-class-implementation of the function is available, we easily implement the Chain-of-responsibility pattern by handling our special cases (up&down keys) and leaving the rest to the base class.由于函数的基类实现是可用的,我们通过处理我们的特殊情况(向上和向下键)并将其余的留给基类来轻松实现责任链模式 You can see this would be nearly impossible if keyPressEvent were a signal.您可以看到,如果keyPressEvent是一个信号,这几乎是不可能的。

The design of Qt is well thought out - they made us fall into the pit of success by making it easy to do the right thing and hard to do the wrong thing (by making keyPressEvent an event). Qt 的设计是经过深思熟虑的——它们使我们容易做正确的事情而难以做错误的事情(通过使 keyPressEvent 成为一个事件),从而使我们陷入成功的陷阱。

On the other hand, consider the simplest usage of QPushButton - just instantiating it and getting notified when it's clicked :另一方面,考虑QPushButton的最简单用法 -只需实例化它并在单击时收到通知

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

This is clearly meant to be done by the user of the class:这显然是由类的用户完成的:

  • if we had to subclass QPushButton every time we want some button to notify us of a click, that would require a lot of subclasses for no good reason!如果每次我们想要某个按钮通知我们点击时都必须对QPushButton进行子类化,那将无缘无故地需要很多子类 A widget that always shows a "Hello world" messagebox when clicked is useful only in a single case - so it's totally not reusable.单击时始终显示“Hello world” messagebox的小部件仅在单个情况下有用 - 因此它完全不可重用。 Again, we have no choice but to do the right thing - by connecting to it externally.同样,我们别无选择,只能做正确的事情——通过外部连接。
  • we may want to connect several slots to clicked() - or connect several signals to sayHello() .我们可能希望将多个插槽连接到clicked() - 或者将多个信号连接到sayHello() With signals there is no fuss.有了信号就没有大惊小怪了。 With subclassing you would have to sit down and ponder some class diagrams until you decide on an appropriate design.对于子类化,您将不得不坐下来思考一些类图,直到您决定合适的设计。

Note that one of the places QPushButton emits clicked() is in its mousePressEvent() implementation.请注意, QPushButton发出clicked()的地方之一是在其mousePressEvent()实现中。 That doesn't mean clicked() and mousePressEvent() are interchangable - just that they're related.这并不意味着clicked()mousePressEvent()是可以互换的——只是它们是相关的。

So signals and events have different purposes (but are related in that both let you "subscribe" to a notification of something happening).所以信号和事件有不同的目的(但两者都可以让你“订阅”正在发生的事情的通知)。

I don't like the answers so far.我不喜欢到目前为止的答案。 – Let me concentrate on this part of the question: – 让我专注于问题的这一部分:

Are events an abstraction of signal/slots?事件是信号/槽的抽象吗?

Short answer: no.简短的回答:没有。 The long answer raises a “better” question: How are signals and events related?长答案提出了一个“更好”的问题:信号和事件是如何相关的?

An idle main loop (Qt's for example) is usually “stuck” in a select() call of the operating system.空闲的主循环(例如 Qt 的)通常“卡在”操作系统的 select() 调用中。 That call makes the application “sleep”, while it passes a bunch of sockets or files or whatever to the kernel asking for: if something changes on these, let the select() call return.该调用使应用程序“休眠”,同时它将一堆套接字或文件或其他任何内容传递给内核要求:如果这些内容发生变化,则让 select() 调用返回。 – And the kernel, as the master of the world, knows when that happens. – 而内核,作为世界的主人,知道什么时候会发生。

The result of that select() call could be: new data on the socket connect to X11, a packet to a UDP port we listen on came in, etc. – That stuff is neither a Qt signal, nor a Qt event, and the Qt main loop decides itself if it turns the fresh data into the one, the other or ignores it. select() 调用的结果可能是:socket 上的新数据连接到 X11,我们监听的一个 UDP 端口的数据包进来了,等等。——这些东西既不是 Qt 信号,也不是 Qt 事件,而且Qt 主循环自行决定是否将新数据转换为一个、另一个或忽略它。

Qt could call a method (or several) like keyPressEvent(), effectively turning it into a Qt event. Qt 可以调用一个(或多个)方法,如 keyPressEvent(),有效地将其转换为 Qt 事件。 Or Qt emits a signal, which in effect looks up all functions registered for that signal, and calls them one after the other.或者 Qt 发出一个信号,它实际上查找为该信号注册的所有函数,并一个接一个地调用它们。

One difference of those two concepts is visible here: a slot has no vote on whether other slots registered to that signal will get called or not.这两个概念的一个区别在这里是可见的:一个插槽没有投票决定注册到该信号的其他插槽是否会被调用。 – Events are more like a chain, and the event handler decides if it interrupts that chain or not. – 事件更像是一个链,事件处理程序决定是否中断该链。 Signals look like a star or tree in this respect.在这方面,信号看起来像一颗星星或一棵树。

An event can trigger or be entirely turned into a signal (just emit one, and don't call “super()”).一个事件可以触发或完全变成一个信号(只发出一个信号,不要调用“super()”)。 A signal can be turned into an event (call an event handler).信号可以变成事件(调用事件处理程序)。

What abstracts what depends on the case: the clicked()-signal abstracts mouse events (a button goes down and up again without too much moving around).什么抽象取决于情况:clicked()-signal 抽象鼠标事件(一个按钮在没有太多移动的情况下再次上下移动)。 Keyboard events are abstractions from lower levels (things like 果 or é are several key strokes on my system).键盘事件是来自较低级别的抽象(例如 果 或 é 是我系统上的几个击键)。

Maybe the focusInEvent() is an example of the opposite: it could use (and thus abstract) the clicked() signal, but I don't know if it actually does.也许 focusInEvent() 是一个相反的例子:它可以使用(因此抽象)clicked() 信号,但我不知道它是否真的这样做了。

The Qt documentation probably explains it best: Qt 文档可能解释得最好:

In Qt, events are objects, derived from the abstract QEvent class, that represent things that have happened either within an application or as a result of outside activity that the application needs to know about.在 Qt 中,事件是从抽象QEvent类派生的对象,它们表示在应用程序内发生的事情或作为应用程序需要了解的外部活动的结果。 Events can be received and handled by any instance of a QObject subclass, but they are especially relevant to widgets.事件可以由QObject子类的任何实例接收和处理,但它们与小部件特别相关。 This document describes how events are delivered and handled in a typical application.本文档描述了如何在典型应用程序中传递和处理事件。

So events and signal/slots are two parallel mechanisms accomplishing the same things.所以事件和信号/槽是完成相同事情的两个并行机制。 In general, an event will be generated by an outside entity (for example, keyboard or mouse wheel) and will be delivered through the event loop in QApplication .通常,事件将由外部实体(例如,键盘或鼠标滚轮)生成,并通过QApplication的事件循环传递。 In general, unless you set up the code, you will not be generating events.通常,除非您设置代码,否则您不会生成事件。 You might filter them through QObject::installEventFilter() or handle events in subclassed object by overriding the appropriate functions.您可以通过QObject::installEventFilter()过滤它们或通过覆盖适当的函数来处理子类对象中的事件。

Signals and Slots are much easier to generate and receive and you can connect any two QObject subclasses. Signals 和 Slots 更容易生成和接收,您可以连接任何两个QObject子类。 They are handled through the Metaclass (have a look at your moc_classname.cpp file for more), but most of the interclass communication that you will produce will probably use signals and slots.它们是通过元类处理的(更多信息请查看您的 moc_classname.cpp 文件),但是您将生成的大多数类间通信可能会使用信号和槽。 Signals can get delivered immediately or deferred via a queue (if you are using threads).信号可以立即传递或通过队列延迟传递(如果您正在使用线程)。

A signal can be generated.可以产生信号。

Events are dispatched by the event loop.事件由事件循环调度。 Each GUI program needs an event loop, whatever you write it Windows or Linux, using Qt, Win32 or any other GUI library.每个 GUI 程序都需要一个事件循环,无论您在 Windows 或 Linux 上编写它,使用 Qt、Win32 或任何其他 GUI 库。 As well each thread has its own event loop.同样,每个线程都有自己的事件循环。 In Qt "GUI Event Loop" (which is the main loop of all Qt applications) is hidden, but you start it calling:在 Qt 中,“GUI 事件循环”(这是所有 Qt 应用程序的主循环)是隐藏的,但您启动它时调用:

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

Messages OS and other applications send to your program are dispatched as events.操作系统和其他应用程序发送到您的程序的消息作为事件分派。

Signals and slots are Qt mechanisms.信号和槽是 Qt 机制。 In the process of compilations using moc (meta-object compiler), they are changed to callback functions.在使用 moc(元对象编译器)进行编译的过程中,它们被更改为回调函数。

Event should have one receiver, which should dispatch it.事件应该有一个接收器,它应该调度它。 No one else should get that event.没有其他人应该得到那个事件。

All slots connected to the emitted signal will be executed.连接到发出信号的所有槽都将被执行。

You shouldn't think of Signals as events, because as you can read in the Qt documentation:您不应该将信号视为事件,因为您可以在 Qt 文档中阅读:

When a signal is emitted, the slots connected to it are usually executed immediately, just like a normal function call.当一个信号被发出时,与其相连的槽通常会立即执行,就像一个普通的函数调用一样。 When this happens, the signals and slots mechanism is totally independent of any GUI event loop.发生这种情况时,信号和槽机制完全独立于任何 GUI 事件循环。

When you send an event, it must wait for some time until the event loop dispatches all events that came earlier.当您发送事件时,它必须等待一段时间,直到事件循环调度所有较早出现的事件。 Because of this, execution of the code after sending event or signal is different.因此,发送事件或信号后的代码执行是不同的。 Code following sending an event will be run immediately.发送事件后的代码将立即运行。 With the signals and slots mechanisms it depends on the connection type.对于信号和插槽机制,它取决于连接类型。 Normally it will be executed after all slots.通常它会在所有插槽之后执行。 Using Qt::QueuedConnection, it will be executed immediately, just like events.使用 Qt::QueuedConnection,它将立即执行,就像事件一样。 Check all connection types in the Qt documentation .检查Qt 文档中的所有连接类型

There is an article that discusses event processing in some detail: http://www.packtpub.com/article/events-and-signals有一篇文章详细讨论了事件处理: http : //www.packtpub.com/article/events-and-signals

It discussions the difference between events and signals here:它在这里讨论了事件和信号之间的区别:

Events and signals are two parallel mechanisms used to accomplish the same thing.事件和信号是用于完成同一件事的两种并行机制。 As a general difference, signals are useful when using a widget, whereas events are useful when implementing the widget.一般来说,信号在使用小部件时很有用,而事件在实现小部件时很有用。 For example, when we are using a widget like QPushButton, we are more interested in its clicked() signal than in the low-level mouse press or key press events that caused the signal to be emitted.例如,当我们使用像 QPushButton 这样的小部件时,我们对它的 clicked() 信号比导致信号发射的低级鼠标按下或按键事件更感兴趣。 But if we are implementing the QPushButton class, we are more interested in the implementation of code for mouse and key events.但是如果我们在实现 QPushButton 类,我们更感兴趣的是鼠标和按键事件的代码实现。 Also, we usually handle events but get notified by signal emissions.此外,我们通常会处理事件,但会收到信号发射的通知。

This seems to be a common way of talking about it, as the accepted answer uses some of the same phrases.这似乎是一种常见的谈论方式,因为接受的答案使用了一些相同的短语。


Note, please see helpful comments below on this answer from Kuba Ober, that make me wonder if it might be a bit simplistic.请注意,请参阅下面关于 Kuba Ober 的这个答案的有用评论,这让我想知道它是否可能有点简单。

TL;DR: Signals and slots are indirect method calls. TL;DR:信号和槽是间接方法调用。 Events are data structures.事件是数据结构。 So they are quite different animals.所以它们是完全不同的动物。

The only time when they come together is when slot calls are made across thread boundaries.它们在一起的唯一时间是跨线程边界进行槽调用时。 The slot call arguments are packed up in a data structure and get sent as an event to the receiving thread's event queue.槽调用参数打包在一个数据结构中,并作为事件发送到接收线程的事件队列。 In the receiving thread, the QObject::event method unpacks the arguments, executes the call, and possibly returns the result if it was a blocking connection.在接收线程中, QObject::event方法解包参数,执行调用,如果是阻塞连接,则可能返回结果。

If we're willing to generalize to oblivion, one could think of events as as a way of invoking the target object's event method.如果我们愿意概括为遗忘,可以将事件视为调用目标对象的event方法的一种方式。 This is an indirect method call, after a fashion - but I don't think it's a helpful way of thinking about it, even if it's a true statement.这是一种间接的方法调用,在时尚之后 - 但我认为这不是一种有用的思考方式,即使它是一个真实的陈述。

'Event processing' by Leow Wee Kheng says: Leow Wee Kheng 的“事件处理”说:

在此处输入图片说明

Jasmine Blanchette says:茉莉花布兰切特说:

The main reason why you would use events rather than standard function calls, or signals and slots, is that events can be used both synchronously and asynchronously (depending on whether you call sendEvent() or postEvents()), whereas calling a function or invoking a slot is always synchronous.您将使用事件而不是标准函数调用或信号和槽的主要原因是事件可以同步和异步使用(取决于您是调用 sendEvent() 还是 postEvents()),而调用函数或调用插槽始终是同步的。 Another advantage of events is that they can be filtered.事件的另一个优点是它们可以被过滤。

Events (in a general sense of user/network interaction) are typically handled in Qt with signals/slots, but signals/slots can do plenty of other things.事件(一般意义上的用户/网络交互)通常在 Qt 中使用信号/插槽处理,但信号/插槽可以做很多其他事情。

QEvent and its subclasses are basically just little standardized data packages for the framework to communicate with your code. QEvent 及其子类基本上只是框架与代码通信的小型标准化数据包。 If you want to pay attention to the mouse in some way, you only have to look at the QMouseEvent API, and the library designers don't have to reinvent the wheel every time you need to figure out what the mouse did in some corner of the Qt API.如果你想以某种方式关注鼠标,你只需要查看 QMouseEvent API,并且库设计者不必每次需要弄清楚鼠标在某个角落做了什么时重新发明轮子Qt API。

It is true that if you're waiting for events (again in the general case) of some sort, your slot will almost certainly accept a QEvent subclass as an argument.确实,如果您正在等待某种类型的事件(再次在一般情况下),您的插槽几乎肯定会接受 QEvent 子类作为参数。

With that said, signals and slots can certainly be used without QEvents, although you'll find that the original impetus for activating a signal will often be some kind of user interaction or other asynchronous activity.话虽如此,信号和槽当然可以在没有 QEvents 的情况下使用,尽管您会发现激活信号的原始动力通常是某种用户交互或其他异步活动。 Sometimes, however, your code will just reach a point where firing off a certain signal will be the right thing to do.但是,有时您的代码会达到触发某个信号才是正确做法的地步。 For example, firing off a signal connected to a progress bar during a long process doesn't involve a QEvent up to that point.例如,在一个漫长的过程中触发一个连接到进度条的信号在那个点之前并不涉及 QEvent。

另一个实用的小考虑:发射或接收信号需要继承QObject而任何继承的对象都可以发布或发送事件(因为您调用了QCoreApplication.sendEvent()postEvent() )这通常不是问题,但是:使用信号 PyQt奇怪的是要求QObject作为第一个超类,并且您可能不想为了能够发送信号而重新排列继承顺序。)

In my opinion events are completely redundant and could be thrown out.在我看来,事件是完全多余的,可以扔掉。 There is no reason why signals could not be replaced by events or events by signals, except that Qt is already set up as it is.没有理由不能用事件代替信号或用信号代替事件,除非 Qt 已经按原样设置。 Queued signals are wrapped by events and events could conceivably be wrapped by signals, for example:排队的信号被事件包裹,而事件可以被信号包裹,例如:

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

Would replace the convenience mouseMoveEvent() function found in QWidget (but not in QQuickItem anymore) and would handle mouseMove signals that a scene manager would emit for the item.将替换在QWidget找到的方便的mouseMoveEvent()函数(但不再在QQuickItem ),并将处理场景管理器将为项目发出的mouseMove信号。 The fact that the signal is emitted on behalf of the item by some outside entity is unimportant and happens quite often in the world of Qt components, even though it is supposedly not allowed (Qt components often circumvent this rule).某些外部实体代表项目发出信号这一事实并不重要,并且在 Qt 组件的世界中经常发生,即使它被认为是不允许的(Qt 组件经常绕过这个规则)。 But Qt is a conglomerate of many different design decisions and pretty much cast in stone for fear of breaking old code (which happens often enough anyway).但是 Qt 是许多不同设计决策的综合体,并且几乎是一成不变的,因为害怕破坏旧代码(无论如何,这种情况经常发生)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM