事件系统
在 Qt 中,事件是对象,继承自抽象 QEvent 类,表示已发生的事情,无论是应用程序内部发生还是由应用程序需要了解的外部活动引起的。事件可以被任何 QObject 子类实例接收和处理,但对于小部件特别相关。本文档描述了在典型应用程序中事件是如何被传递和处理的。
事件传递方式
当事件发生时,Qt 创建一个事件对象来表示它,通过构造适当的 QEvent 子类的实例,并通过调用其 event() 函数,将其传递给 QObject(或其子类)的特定实例。
此函数本身不处理事件;根据传递的事件类型,它将调用特定事件类型的的事件处理器,并根据事件是否被接受或忽略发送响应。
某些事件来自窗口系统,例如 QMouseEvent 和 QKeyEvent;某些事件来自其他来源,例如 QTimerEvent;还有一些来自应用程序本身。
事件类型
大多数事件类型都有特殊的类,特别是 QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent 和 QCloseEvent。每个类都是 QEvent 的子类,并添加特定事件的功能。例如,QResizeEvent 添加了 size() 和 oldSize(),以便小部件能够发现它们的大小如何改变。
某些类支持多个实际事件类型。QMouseEvent 支持鼠标按钮按下、双击、移动和其他相关操作。
每个事件都有一个关联的类型,定义在 QEvent::Type 中,这可以作为一个方便的运行时类型信息来源,快速确定事件对象是从哪个子类构造的。
由于程序需要以不同的和复杂的方式做出反应,Qt 的事件传递机制很灵活。有关 QCoreApplication::notify() 的文档简洁地说明了整个情况;《Qt 季刊》文章 再次审视事件 以更简练的方式重申了这一点。在这里,我们将解释足以满足 95% 的应用程序的需求。
事件处理器
事件传递的正常方式是通过调用虚函数。例如,QPaintEvent 通过调用 QWidget::paintEvent() 来传递。这个虚函数负责适当响应,通常是重绘小部件。如果你没有在虚函数的实现中执行所有必要的工作,你可能需要调用基类的实现。
例如,以下代码处理自定义复选框小部件上的左键单击,将所有其他按钮单击传递给基类 QCheckBox。
void MyCheckBox::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { // handle left mouse button here } else { // pass on other buttons to base class QCheckBox::mousePressEvent(event); } }
如果你想替换基类的函数,你必须自己实现所有内容。但是,如果你想扩展基类的功能,则实现你想要的函数,并调用基类以获取你不想处理的任何情况的默认行为。
偶尔,没有这样的特定事件函数,或者特定事件函数不足以应对。最常见的例子是Tab键的按下。通常,QWidget 通过拦截这些事件来移动键盘焦点,但一些小部件需要自己使用Tab键。
这些对象可以重新实现 QObject::event(),即通用事件处理器,并在通常处理之前或之后进行事件处理,或者他们可以完全替换该函数。一个既解释Tab又具有特定应用程序的特殊自定义事件的非常不寻常的小部件可能包含以下event()函数。
bool MyWidget::event(QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast<QKeyEvent *>(event); if (ke->key() == Qt::Key_Tab) { // special tab handling here return true; } } else if (event->type() == MyCustomEventType) { MyCustomEvent *myEvent = static_cast<MyCustomEvent *>(event); // custom event handling here return true; } return QWidget::event(event); }
请注意,对于所有未处理的情况,都调用QWidget::event(),返回值表示是否处理了事件;true
值防止事件继续发送到其他对象。
事件过滤器
有时一个对象需要查看,并且可能拦截发送给另一个对象的事件。例如,对话框通常想对某些小部件进行按键过滤;例如,修改Return键的处理。
通过设置一个事件过滤器,QObject::installEventFilter()函数实现这一点,使得指定的过滤对象能够在其QObject::eventFilter()函数中接收目标对象的事件。事件过滤器可以在目标对象处理事件之前处理事件,允许它根据需要检查和丢弃事件。可以使用QObject::removeEventFilter()函数移除现有的事件过滤器。
当调用过滤对象的eventFilter()实现时,它可以接受或拒绝事件,并允许或拒绝进一步处理事件。如果所有事件过滤器都允许进一步处理一个事件(每个都返回false
),则事件被发送到目标对象本身。如果其中之一停止处理(返回true
),则目标及其后的事件过滤器根本看不到该事件。
bool FilterObject::eventFilter(QObject *object, QEvent *event) { if (object == target && event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); if (keyEvent->key() == Qt::Key_Tab) { // Special tab handling return true; } else return false; } return false; }
上述代码显示了另一种拦截发送到特定目标小部件的Tab键按压力件事件的方式。在这种情况下,过滤器处理相关的事件并返回true
以阻止它们继续处理。所有其他事件都被忽略,过滤器返回false
以允许通过它上安装的任何其他事件过滤器将它们发送到目标小部件。
也可以通过在QApplication或QCoreApplication对象上安装事件过滤器来过滤整个应用程序的所有事件。这样的全局事件过滤器在对象特定过滤器之前调用。这非常强大,但也会减慢整个应用程序中每个事件的传递;通常应该使用前面讨论的其他技术。
发送事件
许多应用程序想要创建和发送它们自己的事件。您可以通过构造合适的事件对象,并使用 QCoreApplication::sendEvent() 和 QCoreApplication::postEvent() 方法以与 Qt own 事件循环相同的方式发送事件。
sendEvent() 立即处理事件。当它返回时,事件过滤器以及/或对象本身已经处理了该事件。对于许多事件类,都有一个名为 isAccepted() 的函数,它会告诉您事件是否被最后调用的处理程序接受或拒绝。
postEvent() 将事件排队以供稍后分发。下次 Qt 的主事件循环运行时,它会以一些优化将所有排队的事件进行分发。例如,如果有多个调整大小事件,它们将被压缩为一个。同样的,也适用于绘制事件:QWidget::update() 调用 postEvent(),这样可以避免闪烁并提高速度,因为它避免了多次重绘。
postEvent() 也用于对象初始化过程中,因为排队的通常会在对象初始化完成后立即分发。当实现小部件时,重要的是要认识到事件可以在其生命周期非常早期传递,因此在构造函数中,务必要尽早初始化成员变量,以免接收到任何事件。
要创建自定义类型的事件,您需要定义一个事件号,该号必须大于 QEvent::User,并且为了传递有关自定义事件的具体信息,可能需要从 QEvent 继承。有关更多详细信息,请参阅 QEvent 文档。
© 2024 Qt 公司有限公司。此处包含的文档贡献是各自所有者的版权。此处提供的文档是根据自由软件基金会发布的 GNU 自由文档许可证版本 1.3 的条款提供的。Qt 和相应的商标是芬兰的 Qt 公司及其在全球的子公司和关联公司的商标。所有其他商标均为其各自所有者的财产。