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()
可以互换-只是它们相关。
因此,信号和事件具有不同的目的(但是相关的是两者都让您“订阅”某些事情发生的通知)。
到目前为止,我不喜欢这些答案。-让我集中在问题的这部分上:
事件是否是信号/插槽的抽象?
简短回答:不是。长答引出了一个更好的问题:信号和事件有什么关系?
一个空闲的主循环(例如Qt)通常会在操作系统的select()调用中“卡住”。当应用程序传输一堆套接字、文件或其他东西给内核并要求它们:如果其中的某些内容发生变化,请让select()调用返回。当内核知道那个变化发生时,应用程序就会“睡眠”。
select()调用的结果可能是:与X11连接的套接字上有新数据,我们监听的UDP端口收到了一个包等等。- 那些东西既不是Qt信号,也不是Qt事件,而且Qt主循环自己决定是否将新鲜数据转换为其中之一或忽略它。
Qt可以调用类似于keyPressEvent()的方法(或多个方法),从而将其转换为Qt事件。或者Qt发出一个信号,该信号实际上查找为该信号注册的所有函数,并依次调用它们。
这两个概念的一个区别在于:插槽对于是否调用注册到该信号的其他插槽没有任何投票权。-事件更像是一个链,事件处理程序决定是否中断该链或不中断。这方面,信号看起来像星形或树状结构。
事件可以触发或完全转换为信号(只需发出一个信号,而不调用“super()”)。信号可以转换为事件(调用事件处理程序)。
抽象什么取决于情况:clicked() -signal抽象了鼠标事件(按钮按下并松开而没有太多移动)。键盘事件是从较低级别的抽象(类似果或é的东西在我的系统上需要几个按键)。
也许focusInEvent()是相反的例子:它可以使用(和因此抽象)clicked()信号,但我不知道它实际上是否这样做。
事件由事件循环触发。每个GUI程序都需要一个事件循环,无论您是在Windows还是Linux上编写它,使用Qt、Win32或任何其他GUI库。同时,每个线程都有自己的事件循环。在Qt中,“GUI事件循环”(是所有Qt应用程序的主循环)是隐藏的,但您可以通过调用以下方式来启动它:
QApplication a(argc, argv);
return a.exec();
操作系统和其他应用程序发送到您的程序的消息被视为事件并进行调度。
信号和槽是Qt机制。在使用moc(元对象编译器)进行编译的过程中,它们会被转换为回调函数。
每个事件应该有一个接收者,应该由该接收者进行分派,其他人不应该获取该事件。
与发出的信号连接的所有槽将被执行。
您不应将信号视为事件,因为正如您可以在Qt文档中读到的那样:
当信号被发射时,与其连接的槽通常会立即执行,就像普通的函数调用一样。 当这种情况发生时,信号和槽机制完全独立于任何GUI事件循环。
当您发送事件时,必须等待一段时间,直到事件循环调度所有先前到达的事件。因此,发送事件或信号后的代码执行方式不同。发送事件后的代码将立即运行。对于信号和槽机制,它取决于连接类型。通常,它会在所有槽之后执行。使用Qt :: QueuedConnection,它将立即执行,就像事件一样。请查看Qt文档中的所有连接类型。
QObject::event
方法解包参数,执行调用,并在阻塞连接时可能返回结果。event
方法的一种方式。这是一种间接的方法调用,但我认为这并不是一种有帮助的思考方式,即使它是一个真实的陈述。'事件处理',作者Leow Wee Kheng说:
使用事件而不是标准函数调用或信号和插槽的主要原因是,事件可以同步和异步地使用(取决于您调用sendEvent()还是postEvents()),而调用函数或调用插槽始终是同步的。事件的另一个优点是它们可以被过滤。
在Qt中,事件(以用户/网络交互的一般意义为例)通常使用信号/槽来处理,但信号/槽还可以做很多其他事情。
QEvent及其子类基本上只是用于框架与您的代码进行通信的小型标准化数据包。如果您想以某种方式关注鼠标,则只需查看QMouseEvent API,库设计人员无需每次需要弄清楚Qt API某个角落中鼠标所做的事情时都重新发明轮子。
确实,如果您正在等待某种类型的事件(再次以一般情况为例),则您的槽几乎肯定会接受一个QEvent子类作为参数。
话虽如此,信号和槽当然可以在没有QEvents的情况下使用,尽管您会发现激活信号的原始动机通常是某种用户交互或其他异步活动。但有时,您的代码将仅达到触发某个信号的正确时机。例如,在长时间进程期间触发连接到进度条的信号不涉及到那个点的QEvent。
QObject
,而任何继承的对象都可以发布或发送事件(因为您调用 QCoreApplication.sendEvent()
或 postEvent()
)。这通常不是问题,但是使用信号时,PyQt 奇怪地要求 QObject
必须是第一个超类,而您可能不想重新排列继承顺序以便能够发送信号。connect(this, &MyItem::mouseMove, [this](QMouseEvent*){});
将替换在QWidget
中找到的方便的mouseMoveEvent()
函数(但不再在QQuickItem
中),并处理场景管理器为该项发出的mouseMove
信号。信号代表某个外部实体代表该项发出并不重要,在Qt组件世界中经常发生,尽管据说不允许(Qt组件经常规避此规则)。但是,Qt是许多不同设计决策的综合体,并且基本上是铸成石头的,以免破坏旧代码(无论如何都经常发生)。
QObject
父子层次结构的优势。信号/槽连接只是承诺在满足某些条件时直接或间接调用函数。信号和槽没有相关的处理层次结构。 - jonspaceharper