Qt事件和信号/插槽


97

在Qt世界中,事件和信号/时隙有什么区别?

一个会替代另一个吗?事件是信号/时隙的抽象吗?

Answers:


30

Qt文档可能是最好的解释吧:

在Qt中,事件是从抽象QEvent类派生的对象,它们表示发生在应用程序内部或由于应用程序需要了解的外部活动而发生的事情。QObject子类的任何实例都可以接收和处理事件,但是事件与小部件特别相关。本文档介绍了在典型应用程序中如何传递和处理事件。

因此,事件和信号/时隙是完成相同任务的两种并行机制。通常,事件将由外部实体(例如,键盘或鼠标滚轮)生成,并将通过中的事件循环传递QApplication。通常,除非设置代码,否则将不会生成事件。您可以QObject::installEventFilter()通过覆盖适当的函数来过滤它们或处理子类对象中的事件。

信号和插槽更容易生成和接收,您可以连接任何两个QObject子类。它们是通过元类处理的(更多信息请参见moc_classname.cpp文件),但是您将产生的大多数类间通信可能都将使用信号和插槽。信号可以立即传递或通过队列延迟(如果使用线程)。

可以产生信号。


延迟的队列与队列事件循环相同,还是存在两个队列?一个用于延迟信号,另一个用于事件?
Guillaume07

29
@Raphael对您接受此答案感到羞耻。–所引用的段落甚至不包含“信号”或“插槽”!
罗伯特·西默

1
@neuronet:该段落的引号是“ [它]解释得最好”,但事实并非如此。一点也不。一点也不是。
罗伯特·西默

@Robert,如果将这小段介绍行更改为“首先让我们考虑文档如何解释事件,然后再考虑事件与信号的关系”,您会满意吗?如果是这样,那么我们可以编辑答案以改善它,因为这只是次要问题,我同意可以用更好的措辞来表达。[编辑是一个真正的问题:因为我不确定其余部分会好得多....但是仅仅因为引用的部分没有提及信号,如果其余部分实际上是好的,这似乎是肤浅的担心,我没有假设。]
eric

4
@neuronet,如果您完全删除引号,它将改善我的回答。–如果您删除整个答案,它将改善整个质量检查。
罗伯特·西默

152

在Qt中,信号和事件都是Observer模式的实现。因为它们具有不同的优点和缺点,所以它们在不同的情况下使用。

首先,让我们精确地定义“ Qt事件”的含义:Qt类中的虚函数,如果您想处理事件,则希望在您的基类中重新实现它。它与“ 模板方法”模式有关

注意我如何使用“ 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)

看到?这段代码提出了一个新的抽象:一个小部件,其功能类似于按钮,但具有一些额外的功能。我们非常方便地添加了此功能:

  • 由于我们重新实现了虚拟,因此我们的实现会自动封装在我们的类中。如果Qt的设计师发出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()可以互换-只是它们是相关的。

因此,信号和事件具有不同的用途(但两者之间的相关性在于,它们都可以让您“订阅”某种事件的通知)。


我明白了 事件是针对每个类的,一个类的所有实例将做出相同的反应,它们都将调用相同的QClassName :: event。但是信号是每个对象的,每个对象可以有其独特的信号槽连接。
炸鱼薯条德里克

39

到目前为止,我不喜欢这些答案。–让我专注于问题的这一部分:

事件是信号/时隙的抽象吗?

简短的回答:不。长的答案提出了一个“更好的”问题:信号和事件如何关联?

空闲的主循环(例如Qt)通常“卡”在操作系统的select()调用中。该调用使应用程序“休眠”,同时将一堆套接字或文件或任何内容传递给内核,要求它们:如果这些改变了,请返回select()调用。–作为世界的主人,内核知道何时发生。

该select()调用的结果可能是:套接字上的新数据连接到X11,我们监听的UDP端口的数据包进来,等等– 。–这些东西既不是Qt信号,也不是Qt事件,并且Qt主循环自行决定是否将新数据转换为一个,另一个或忽略它。

Qt可以调用一个或多个类似keyPressEvent()的方法,从而将其有效地转换为Qt事件。或Qt发出信号,实际上查找该信号注册的所有功能,然后一个接一个地调用它们。

这两个概念的区别在这里可见:一个插槽对注册到该信号的其他插槽是否会被调用没有投票权。–事件更像一个链,事件处理程序决定是否中断该链。在这方面,信号看起来像星星或树。

一个事件可以触发或完全变成一个信号(只发出一个信号,不要调用“ super()”)。信号可以转换为事件(称为事件处理程序)。

什么抽象取决于情况:clicked()信号抽象鼠标事件(一个按钮又向下又向上滑动,没有太多移动)。键盘事件是较低级别的抽象(例如,果或é是我系统上的几个按键)。

也许focusInEvent()是相反的例子:它可以使用(从而抽象化)clicked()信号,但我不知道它是否确实如此。


4
我发现这部分内容:“一个插槽对注册到该信号的其他插槽是否被调用没有投票权”,这对于信号和事件之间的区别很有启发。但是,我有一个相关的问题:什么是消息,消息与信号和事件有什么关系?(如果它们相关)。消息是信号和事件的抽象吗?可以使用它们来描述QObject(或其他对象)之间的这种交互吗?
user1284631 2012年

@axeoth:在Qt中,没有“消息”之类的东西。嗯,有一个消息框,仅此而已。
恢复莫妮卡

@KubaOber:谢谢。我的意思是“事件”。
user1284631 2013年

3
@axeoth:那你的问题是胡扯。内容为:“什么是事件,以及事件与信号和事件如何相关”。
恢复莫妮卡

@KubaOber:我不记得一年后的情况。无论如何,似乎我在问与OP几乎相同的事情:事件,事件以及事件与信号和时隙之间的关系是什么?当时的真实情况是IIRC,我正在寻找一种将Qt信号/插槽驱动的类包装到某种非Qt类中的方法,因此我在寻找一种从后者内部命令第一个的方法。我想我正在考虑向他们发送事件,因为那意味着我只需要包括Qt标头,而不必强迫我的类实现信号/插槽。
user1284631 2013年

16

事件由事件循环调度。每个GUI程序都需要一个事件循环,无论您使用Qt,Win32或任何其他GUI库将其编写为Windows还是Linux,都需要一个事件循环。同样,每个线程都有自己的事件循环。在Qt中,“ GUI事件循环”(这是所有Qt应用程序的主循环)是隐藏的,但是您可以通过以下方式启动它:

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

操作系统和其他应用程序发送给您程序的消息将作为事件调度。

信号和时隙是Qt机制。在使用moc(元对象编译器)进行编译的过程中,它们被更改为回调函数。

事件应具有一个接收者,该接收者应该调度它。没有其他人可以参加该活动。

连接到发射信号的所有插槽将被执行。

您不应将Signals视为事件,因为您可以在Qt文档中阅读:

发出信号后,与其连接的插槽通常会立即执行,就像正常的函数调用一样。发生这种情况时,信号和时隙机制完全独立于任何GUI事件循环。

发送事件时,它必须等待一段时间,直到事件循环调度所有较早出现的事件为止。因此,发送事件或信号后代码执行不同。发送事件后的代码将立即运行。使用信号和插槽机制,取决于连接类型。通常,它将在所有插槽之后执行。使用Qt :: QueuedConnection,它将像事件一样立即执行。检查Qt文档中的所有连接类型


这句话使我对Qt信号时隙和事件之间的区别有了清楚的了解: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 2014年

7

有一篇文章详细讨论了事件处理:http : //www.packtpub.com/article/events-and-signals

它在这里讨论事件和信号之间的区别:

事件和信号是用于完成同一件事的两种并行机制。通常,信号在使用小部件时有用,而事件在实现小部件时有用。例如,当我们使用QPushButton之类的小部件时,我们对它的clicked()信号比对引起信号发射的低级鼠标按下或按键事件更感兴趣。但是,如果我们正在实现QPushButton类,则对鼠标和键事件的代码实现更感兴趣。此外,我们通常会处理事件,但会通过信号发射得到通知。

这似乎是谈论它的一种常见方式,因为公认的答案使用了某些相同的短语。


请注意,请参阅下面有关库巴·奥伯(Kuba Ober)的答案的有用评论,这使我想知道这是否可能有点简单。


我看不到它们是完成同一件事的两种机制-我认为这是无用的概括,对任何人都没有帮助。
恢复莫妮卡

@KubaOber否,它有助于避免底层细节。您会说Python没有用,因为您可以用机器代码编写相同的东西吗?:)
eric

2
我的意思是,引号的第一句话笼统地说是没有用的。从技术上讲,这不是不正确的,只是因为您可以(如果希望的话)使用事件传递来完成信号时隙机制的工作,反之亦然。这会很麻烦。因此,不,信号时隙与事件一样,它们不能完成相同的事情,并且两者都存在的事实对于 Qt的成功至关重要。引用的按钮示例完全忽略了信号插槽系统的一般功能。
恢复莫妮卡

1
“事件和信号是在某些UI窗口小部件/控件的狭窄范围内用于完成同一件事的两种并行机制。” 粗体部分严重缺失,使引用无济于事。即使这样,也可以质疑真正完成了多少“相同的事情”:信号使您可以对单击事件做出反应,而无需在窗口小部件上安装事件过滤器(或从窗口小部件派生)。在没有信号的情况下执行此操作比较麻烦,并且会将客户端代码紧密耦合到控件。那是不好的设计。
恢复莫妮卡

1
事件是一个普遍存在的概念。Qt中的特定实现将它们具体设置为传递给的数据结构event。信号和插槽是具体的方法,而连接机制是一种数据结构,可让信号调用一个或多个列为已连接到其上的插槽。我希望您看到将信号/插槽说成是事件的“子集”或“变体”是胡说八道,反之亦然。它们确实是不同的东西,在某些小部件的上下文中碰巧会用于相似的目的。而已。您推广的越多,对IMHO的帮助就越小。
恢复莫妮卡

5

TL; DR:信号和插槽是间接方法调用。事件是数据结构。因此它们是完全不同的动物。

它们唯一的在一起就是跨越线程边界进行插槽调用。插槽调用参数打包在数据结构中,并作为事件发送到接收线程的事件队列。在接收线程中,该QObject::event方法解压缩参数,执行调用,并且如果它是阻塞连接,则可能返回结果。

如果我们愿意将其概括为遗忘,则可以将事件视为调用目标对象event方法的一种方式。这是一种间接的方法调用,是一种流行的做法-但我认为这不是思考的有用方法,即使它是一个真实的声明。


2

事件(从一般意义上来说,用户/网络交互)通常在Qt中使用信号/插槽来处理,但是信号/插槽可以做很多其他事情。

QEvent及其子类基本上只是用于框架与代码进行通信的少量标准化数据包。如果您想以某种方式关注鼠标,则只需查看QMouseEvent API,并且库设计人员不必每次都需要弄清楚鼠标在鼠标某个角处的操作时就重新发明了轮子。 Qt API。

的确,如果您正在等待某种事件(通常也是这种情况),则您的广告位几乎可以肯定会接受QEvent子类作为参数。

话虽如此,信号和插槽当然可以在没有QEvents的情况下使用,尽管您会发现激活信号的原始动力通常是某种用户交互或其他异步活动。但是,有时候,您的代码会达到触发特定信号的正确做法。例如,在很长的过程中触发连接到进度条的信号直到该点都不会涉及QEvent。


2
“事件通常在Qt中通过信号/插槽来处理”-实际上没有... QEvent对象总是通过重载的虚函数传递!例如,QWidget中没有信号“ keyDown”-而是keyDown是一个虚函数。
Stefan Monov

同意Stefan,实际上是Qt的一个令人困惑的部分
Harald Scheirich

1
@Stefan:我认为很少有Qt应用程序会覆盖keyDown(并且通常使用QAbstractButton :: clicked和QLineEdit :: editingFinished之类的信号)来证明“通常”是合理的。我之前肯定已经抓紧了键盘输入,但这不是处理事件的常用方法。
jkerian's

编辑后(澄清“事件”的含义),您的帖子现在是正确的。不过,我避免重复使用此类词语。在没有将信号称为“某种事件”的情况下,事情变得很泥泞。
Stefan Monov

2

我在阅读Leow Wee Kheng的“事件处理”时发现了这个问题。它还说:

在此处输入图片说明

茉莉花·布兰切特Jasmine Blanchette)说:

使用事件而不是标准函数调用或信号和插槽的主要原因是,事件可以同步和异步使用(取决于您是否调用sendEvent()或postEvents()),而调用函数或调用插槽始终是同步的。事件的另一个优点是可以对其进行过滤。


1

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


-1

我认为事件是完全多余的,可能会被丢弃。除了已经按原样设置Qt之外,没有理由不能用事件或事件替换信号。排队的信号由事件包装,事件可以想象为由信号包装,例如:

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

将替换(但现在不再可用)中的便捷mouseMoveEvent()功能,并将处理场景管理器将为该项目发出的信号。尽管某些信号是不允许的,但某些外部实体代表项目发出信号的事实并不重要,并且经常在Qt组件的世界中发生(即使Qt组件经常绕过此规则)。但是Qt是许多不同设计决策的集合体,并且由于担心破坏旧代码(无论如何经常发生)而几乎陷入僵局。QWidgetQQuickItemmouseMove


QObject在必要时,事件具有传播父子层次结构的优势。满足特定条件时,信号/插槽连接只是直接或间接调用函数的承诺。没有与信号和插槽关联的处理层次结构。
匿名

您可以对信号实施类似的传播规则。没有规则。不能将某个信号重新发布到另一个对象(子对象)。您可以accepted在信号和处理信号的插槽中添加参考参数,也可以将结构作为带有accepted字段的参考参数。但是Qt做出了它所做出的设计选择,现在它们已经定格了。
user1095108 '18

1
如果您按照建议的方式进行操作,那么基本上您只是在重新实现事件。
Fabio A.19年

@FabioA。这就是OP的问题所在。事件和信号可以/可以互相替换。
user1095108

如果您有重新实现事件,那么您不是要替换事件。不仅如此:信号是事件之上实现的。您需要某种“事件系统”,以便告诉其他一些事件循环(可能是在您自己的线程之外的循环中),将来某个时候它必须在某个地方执行给定的函数。
Fabio A.19年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.