在Qt世界中,事件和信号/插槽的区别是什么?
有人替换另一个吗?事件是信号/槽的抽象吗?
答案 0 :(得分:136)
在Qt中,信号和事件都是Observer pattern的实现。它们在不同的情况下使用,因为它们有不同的优点和缺点。
首先让我们完全定义'Qt事件'的含义:Qt类中的虚函数,如果你想处理事件,你应该在你的基类中重新实现。它与Template Method pattern。
有关请注意我是如何使用“ handle ”这个词的。实际上,这是信号和事件的意图之间的基本区别:
不同之处在于,当您“处理”事件时,您承担责任“回应”在课堂外有用的行为。例如,考虑一个应用程序,其上有一个带有数字的按钮。应用程序需要让用户关注按钮并通过按“向上”和“向下”键盘键来更改数字。否则按钮应该像普通QPushButton
一样运行(可以点击等)。在Qt中,这是通过创建自己的小可重用“组件”(QPushButton
的子类)来完成的,该组件重新实现QWidget::keyPressEvent
。伪代码:
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)
请参阅?此代码提供了一个新的抽象:一个小部件,其作用类似于按钮,但具有一些额外的功能。我们非常方便地添加了这个功能:
keyPressEvent
信号,我们需要决定是继承QPushButton
还是只是外部连接到信号。但那将是愚蠢的,因为在Qt中你总是总是在编写具有自定义行为的窗口小部件时会继承(有充分理由 - 可重用性/模块化)。因此,通过使keyPressEvent
成为事件,他们传达了keyPressEvent
只是功能的基本构建块的意图。如果它是一个信号,它看起来像一个面向用户的东西,当它不打算是。keyPressEvent
是一个信号,你可以看到这几乎是不可能的。Qt的设计经过深思熟虑 - 它们让我们陷入成功之中通过简单做正确的事情并且很难做错事(通过使keyPressEvent成为事件) )。
另一方面,考虑QPushButton
的最简单用法 - 只是实例化它并在点击时收到通知:
button = new QPushButton(this)
connect(button, SIGNAL(clicked()), SLOT(sayHello())
这显然是由班级的用户完成的:
QPushButton
,那么就没有充分的理由需要很多子类!一个总是显示的小部件单击“Hello world”messagebox
仅在单个案例中有用 - 因此它完全不可重复使用。同样,我们别无选择,只能通过外部连接来做正确的事。clicked()
- 或将多个信号连接到sayHello()
。信号没有大惊小怪。通过子类化,你必须坐下来思考一些类图,直到你决定一个合适的设计。请注意QPushButton
个clicked()
位置mousePressEvent()
位于其clicked()
实施中。这并不意味着mousePressEvent()
和{{1}}可以互换 - 只是它们是相关的。
所以信号和事件有不同的目的(但是相关的是,它们都让你“订阅”发生事情的通知)。
答案 1 :(得分:38)
到目前为止,我不喜欢这些答案。 - 让我集中讨论这部分问题:
事件是信号/插槽的抽象吗?
简答:否。长答案提出了一个“更好”的问题:信号与事件有何关联?
空闲主循环(例如Qt)通常在操作系统的select()调用中“卡住”。该调用使应用程序“休眠”,同时它将一堆套接字或文件传递给内核,要求:如果这些内容发生了变化,请让select()调用返回。 - 作为世界的主人,内核知道这种情况何时发生。
select()调用的结果可能是:套接字上的新数据连接到X11,一个数据包到我们监听的UDP端口,等等。 - 那些东西既不是Qt信号,也不是一个Qt事件,Qt主循环决定它是否将新数据转换为一个,另一个或忽略它。
Qt可以调用一个方法(或几个),如keyPressEvent(),有效地将其转换为Qt事件。或者Qt发出一个信号,它实际上会查找为该信号注册的所有功能,然后一个接一个地调用它们。
这两个概念的一个不同之处在于:一个插槽没有对是否会调用注册到该信号的其他插槽进行投票。 - 事件更像是一个链,事件处理程序决定它是否中断该链。在这方面,信号看起来像星星或树。
事件可以触发或完全变为信号(只发出一个,不要调用“super()”)。信号可以变成事件(调用事件处理程序)。
取决于具体情况的摘要:clicked() - 信号抽象鼠标事件(按钮向下和向上移动而不会移动太多)。键盘事件是来自较低级别的抽象(诸如果或é之类的东西是我系统上的几个击键)。
也许focusInEvent()是一个相反的例子:它可以使用(并因此抽象)clicked()信号,但我不知道它是否确实如此。
答案 2 :(得分:27)
Qt documentation最好解释一下:
在Qt中,事件是派生的对象 来自抽象的
QEvent
类,那个 代表已发生的事情 无论是在申请中还是作为 外部活动的结果是 应用程序需要了解。 可以接收和处理事件QObject
子类的任何实例, 但它们特别相关 小部件。本文档描述了如何 事件在一个交付和处理 典型的应用。
所以事件和信号/槽是完成相同事情的两个并行机制。通常,事件将由外部实体(例如,键盘或鼠标滚轮)生成,并将通过QApplication
中的事件循环传递。通常,除非您设置代码,否则您将不会生成事件。您可以通过QObject::installEventFilter()
过滤它们,或者通过覆盖相应的函数来处理子类对象中的事件。
信号和插槽更容易生成和接收,您可以连接任意两个QObject
子类。它们通过Metaclass处理(有关更多信息,请查看您的moc_classname.cpp文件),但您将生成的大多数类间通信可能会使用信号和插槽。信号可以立即传递或通过队列延迟(如果您使用线程)。
可以生成信号。
答案 3 :(得分:13)
事件循环调度事件。每个GUI程序都需要一个事件循环,无论您使用Qt,Win32还是任何其他GUI库编写Windows或Linux。每个线程都有自己的事件循环。在Qt中,“GUI事件循环”(这是所有Qt应用程序的主循环)被隐藏,但是你开始调用它:
QApplication a(argc, argv);
return a.exec();
消息发送到您的程序的OS和其他应用程序将作为事件发送。
信号和插槽是Qt机制。在使用moc(元对象编译器)的编译过程中,它们被更改为回调函数。
事件应该有一个接收器,应该发送它。没有人应该得到那个事件。
将执行连接到发射信号的所有插槽。
您不应该将Signals视为事件,因为您可以在Qt文档中阅读:
当发出信号时,通常会立即执行与其连接的插槽,就像正常的函数调用一样。当这种情况发生时,信号和 插槽机制完全独立 任何GUI事件循环。
发送事件时,它必须等待一段时间,直到事件循环调度之前发生的所有事件。因此,在发送事件或信号之后执行代码是不同的。发送事件后的代码将立即运行。信号和插槽机制取决于连接类型。通常它将在所有插槽之后执行。使用Qt :: QueuedConnection,它将立即执行,就像事件一样。检查all connection types in the Qt documentation。
答案 4 :(得分:6)
有一篇文章详细讨论了事件处理:http://www.packtpub.com/article/events-and-signals
它讨论了事件和信号之间的区别:
事件和信号是用于完成的两个并行机制 一样。作为一般差异,信号在使用时非常有用 小部件,而事件在实现小部件时很有用。对于 例如,当我们使用像QPushButton这样的小部件时,我们会更多 对它的clicked()信号感兴趣,而不是在低级鼠标按下 或导致信号发出的按键事件。但是,如果我们 正在实现QPushButton类,我们对此更感兴趣 实现鼠标和键事件的代码。而且,我们通常 处理事件,但通过信号发射得到通知。
这似乎是一种常见的谈论方式,因为接受的答案使用了一些相同的短语。
请注意,请参阅下面有关Kuba Ober的回答的有用评论,这让我想知道它是否有点过分简单。
答案 5 :(得分:4)
TL; DR:信号和插槽是间接方法调用。事件是数据结构。所以他们是完全不同的动物。
它们聚集在一起的唯一时间是跨越线程边界进行插槽调用。槽调用参数打包在数据结构中,并作为事件发送到接收线程的事件队列。在接收线程中,QObject::event
方法解包参数,执行调用,如果是阻塞连接,则可能返回结果。
如果我们愿意概括为遗忘,可以将事件视为调用目标对象的event
方法的一种方式。这是一种间接的方法调用,在时尚之后 - 但我不认为这是一种有用的思考方式,即使它是一个真实的陈述。
答案 6 :(得分:3)
事件(一般意义上是用户/网络交互)通常在Qt中处理信号/插槽,但信号/插槽可以做很多其他事情。
QEvent及其子类基本上只是框架与代码通信的标准化数据包。如果你想以某种方式关注鼠标,你只需要看看QMouseEvent API,并且每次你需要弄清楚鼠标在某个角落做了什么时,库设计师就不必重新发明轮子。 Qt API。
确实如果你正在等待某种事件(在一般情况下),你的插槽几乎肯定会接受一个QEvent子类作为参数。
话虽如此,信号和插槽当然可以在没有QEvents的情况下使用,尽管您会发现激活信号的原始动力通常是某种用户交互或其他异步活动。但是,有时候,您的代码只会达到触发某个信号才能正常运行的程度。例如,在漫长的过程中触发连接到progress bar的信号在此之前不涉及QEvent。
答案 7 :(得分:1)
另一个小的务实考虑:发出或接收信号需要继承QObject
而任何继承的对象可以发布或发送事件(因为你调用QCoreApplication.sendEvent()
或postEvent()
)这通常是不是问题但是:使用信号PyQt奇怪地要求QObject
成为第一个超类,并且你可能不想重新安排继承顺序只是为了能够发送信号。)
答案 8 :(得分:0)
在我看来,事件完全是多余的,可能会被抛弃。除了已经按原样设置Qt之外,没有理由不通过信号用事件或事件替换信号。排队信号由事件包裹,事件可以被信号包裹,例如:
connect(this, &MyItem::mouseMove, [this](QMouseEvent*){});
将替换mouseMoveEvent()
中找到的便捷QWidget
功能(但不再在QQuickItem
中)并处理场景管理器为该项目发出的mouseMove
信号。信号是由一些外部实体代表项目发出的这一事实并不重要,并且在Qt组件的世界中经常发生,即使它被认为是不允许的(Qt组件经常绕过这个规则)。但是Qt是一个由许多不同的设计决策组成的集团,并且因为害怕破坏旧代码(这种情况经常发生这种情况)而非常流行。
答案 9 :(得分:0)
我在阅读Leow Wee Kheng的'Event processing'时发现了这个问题。它还说: