Qt事件和信号/插槽

在Qt世界,事件和信号/时隙有什么不同?

一个取代另一个吗? 事件是信号/插槽的抽象吗?

Qt文档可能是最好的解释

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

所以事件和信号/插槽是完成同样事情的两个并行机制,通常一个事件将由外部实体(例如键盘,Mouswheel)产生,并将通过QApplication中的事件循环传递。 一般情况下,除非设置了代码,否则不会生成事件。 你可以通过QObject::installEventFilter()来过滤它们,或者通过覆盖相应的函数来处理子类中的事件。

信号和插槽更容易生成和接收,你可以连接任何两个QObject子类。 它们是通过Metaclass处理的(查看你的moc_classname.cpp文件以获取更多信息),但是你将要生成的大多数类间通信可能会使用信号和插槽。 信号可以立即传递,也可以通过队列延期(如果使用线程的话)可以产生一个信号

在Qt中,信号和事件都是Observer模式的实现 。 他们在不同的情况下使用,因为他们有不同的长处和短处。

首先让我们定义一下Qt事件:Qt类中的一个虚拟函数,如果你想处理这个事件,那么你需要在你的基类中重新实现它。 它与模板方法模式有关 。

请注意我如何使用“ 句柄 ”一词。 事实上,信号和事件的意图有一个基本的区别:

  • 你“ 处理 ”事件
  • 你“ 得到通知 ”的信号发射

不同之处在于,当您“处理”事件时,您有责任以“在课堂外有用的行为”进行“回应”。 例如,考虑一个带有一个数字button的应用程序。 应用程序需要让用户聚焦button,并通过按“上”和“下”键盘键来改变数字。 否则,button应该像一个正常的QPushButton(它可以被点击等)function。 在Qt中,这是通过创build您自己的可重用的“组件”(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) 

看到? 这段代码提供了一个新的抽象:一个像button一样的小部件,但有一些额外的function。 我们很方便地添加了这个function:

  • 由于我们重新实现了虚拟,我们的实现自动被封装在我们的类中。 如果Qt的devise者已经使keyPressEvent成为一个信号,我们需要决定是inheritanceQPushButton还是只是外部连接到信号。 但是这样做会很愚蠢,因为在Qt中,当编写一个具有自定义行为的Widget时, 总是需要inheritance(出于很好的理由 – 可重用性/模块化)。 所以通过使keyPressEvent成为一个事件,他们expression了他们的意图,即keyPressEvent只是一个基本的function构build块。 如果它是一个信号,它就会像一个面向用户的东西,当它不是有意的时候。
  • 由于函数的基类实现是可用的,所以我们通过处理我们的特殊情况(向上和向下键)并将其余部分留给基类来轻松实现责任链模式 。 你可以看到,如果keyPressEvent是一个信号,这几乎是不可能的。

Qt的devise经过深思熟虑 – 他们让我们陷入成功之谜,通过轻松做正确的事情,很难做错事(通过使keyPressEvent成为事件)。

另一方面,考虑QPushButton的最简单的用法 – 只是实例化它,并在被点击时得到通知

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

这显然是要由class级的用户来完成的:

  • 如果我们每次需要子类QPushButton,我们需要一些button来通知我们一个点击,这将需要很多的子类没有很好的理由! 单击时总是显示“Hello world”消息框的小部件仅在单个情况下有用 – 因此它是完全不可重用的。 再次,我们别无select,只能做正确的事情 – 通过连接到外部。
  • 我们可能要连接几个插槽clicked() – 或连接几个信号sayHello() 。 信号没有大惊小怪。 通过子类化,你将不得不坐下来思考一些类图,直到你决定一个合适的devise。

请注意,QPushButton发出clicked()的地方之一是在其mousePressEvent()实现中。 这并不意味着clicked()mousePressEvent()是可以互换的 – 只是它们是相关的。

因此,信号和事件有不同的目的(但相关的是,两者都允许您“订阅”某事发生的通知)。

我不喜欢迄今为止的答案。 – 让我专注于这个问题的这一部分:

事件是信号/插槽的抽象吗?

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

一个空闲的主循环(例如Qt)通常会在操作系统的select()调用中“卡住”。 这个调用让应用程序“hibernate”,同时它将一堆套接字或文件或任何东西传递给内核,要求:如果这些东西有变化,让select()调用返回。 – 作为世界的主人,内核知道这是什么时候发生的。

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

Qt可以像keyPressEvent()那样调用一个或多个方法,将其有效地转换为Qt事件。 或者Qt发出一个信号,它实际上查找为该信号注册的所有function,并且一个接一个地调用它们。

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

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

什么抽象取决于案件:clicked() – 信号抽象鼠标事件(一个button下来,再次没有太多的四处走动)。 键盘事件是较低层次的抽象(例如,果或é是我的系统上的几个关键笔画)。

也许focusInEvent()是一个相反的例子:它可以使用(并且因此抽象)clicked()信号,但是我不知道它是否实际上。

事件由事件循环分派。 每个GUI程序都需要事件循环,无论你使用Qt,WinApi或其他GUI库编写Windows还是Linux。 以及每个线程都有自己的事件循环。 在Qt中,“GUI事件循环”(女巫是所有Qt应用程序的主循环)是隐藏的,但你开始调用:

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

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

信号和插槽是Qt机制,在使用moc(元对象编译器)进行编译的过程中,它被改为callback函数。

事件应该有一个接收器,女巫应该派遣它。 没有人应该得到这个事件。

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

你不应该把Signals看作事件,因为你可以在Qt doc中读到:

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

当你发送一个事件时,它必须等待事件循环发送所有先前发生的事件。 因此,发送事件或信号后鳕鱼的执行是不同的。 鳕鱼休闲发送事件将立即运行。 信号和插槽机制取决于连接types。 通常它会在所有插槽之后执行。 使用Qt :: QueuedConnection,它将立即执行,就像事件一样。 检查Qt Doc中的所有连接types

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

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

事件和信号是两个并行的机制,用来完成同样的事情。 作为一个普遍的差异,信号在使用小部件时非常有用,而事件在实现小部件时非常有用。 例如,当我们使用像QPushButton这样的小部件时,我们更感兴趣的是clicked()信号,而不是低级别的鼠标按键或导致信号发射的按键事件。 但是,如果我们正在实现QPushButton类,我们更感兴趣的是鼠标和键事件的代码的实现。 另外,我们通常会处理事件,但会通过信号发射得到通知。

这似乎是一个常用的方式来谈论它,因为接受的答案使用了一些相同的短语。


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

TL; DR:信号和时隙是间接的方法调用。 事件是数据结构。 所以他们是非常不同的动物。

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

如果我们愿意推广到遗忘,可以把事件看作是调用目标对象event方法的一种方式。 这是一种间接的方法调用,但我不认为这是一个有用的思考方式,即使这是一个真实的说法。

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

QEvent及其子类基本上只是用于框架与代码进行通信的一些标准化数据包。 如果您想以某种方式关注鼠标,您只需要查看QMouseEvent API,并且图库devise人员无需在每次需要确定鼠标在某个angular落做什么时重新发明轮子Qt API。

确实,如果你正在等待某种事件(也是一般情况下),那么你的插槽几乎肯定会接受一个QEvent子类作为参数。

有了这个说法,信号和插槽当然可以使用没有QEvents,虽然你会发现激活信号的原始动力往往是某种用户交互或其他asynchronous活动。 然而,有时候,你的代码只能触发一个特定的信号是正确的。 例如,在长时间处理期间触发连接到进度条的信号不会涉及到达此时的QEvent。

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

在我看来,事件是完全多余的,可能会被抛出。 除了Qt已经设置好之外,没有理由不能用信号替代事件或事件。 排队的信号被事件包裹,事件可能被信号包裹,例如:

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

将取代在QWidget (但不再在QQuickItem中)中find的便利mouseMoveEvent()函数,并将处理场景pipe理器将为该项目发出的mouseMove信号。 信号是由外部实体代表项目发出的事实并不重要,而且在Qt组件的世界中经常发生,尽pipe它是不允许的。 但Qt是许多不同devise决策的集合体,因为害怕破坏旧代码(无论如何经常发生这种事情),所以大打出手。