Qt 之事件(概要)

事件系统

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

本文档介绍如何在典型应用程序中传递和处理事件。

 

事件是如何被传递的

当事件发生时,Qt 通过构造相应 QEvent 子类的实例来创建一个事件对象来表示它,并通过调用其 event() 函数将其传递给 QObject 的特定实例(或其子类之一)。

此函数不处理事件本身;根据传递的事件类型,它调用该特定类型的事件的事件处理程序,并根据事件是被接受还是被忽略来发送响应。 某些事件(如 QMouse 事件和 QKey事件)来自窗口系统;有些,如QTimer事件,来自其他来源;有些来自应用程序本身。

 

事件类型

大多数事件类型都有特殊的类,特别是 “QResizeEvent, QPaintEvent, QMouseEvent, QKeyEvent, and QCloseEvent.”。每个类都对 QEvent 进行子类化,并添加特定于事件的函数。例如,QResizeEvent添加了  size()  and  oldSize(),以使小部件能够发现其尺寸是如何变化的。 某些类支持多个实际事件类型。QMouseEvent 支持鼠标按钮按下,双击,移动和其他相关操作。 每个事件都有一个关联的类型,该类型在 QEvent::Type 中定义,这可以用作运行时类型信息的便捷源,以快速确定从哪个子类构造给定事件对象。 由于程序需要以各种复杂的方式做出反应,Qt的事件交付机制是灵活的。QCore应用的文档::QCoreApplication::notify()  简明扼要地讲述了整个故事;Qt季刊文章《Another Look at Events》不太简洁地重述了它。在这里,我们将解释足以满足95%的应用程序。

 

事件处理程序

传递事件的正常方法是调用虚拟函数。例如,通过调用QWidget::paintEvent() 来传递 QPaintEvent 。此虚函数负责做出适当的反应,通常是通过重新绘制小部件。如果未在虚函数的实现中执行所有必要的工作,则可能需要调用基类的实现。 例如,以下代码处理对自定义复选框构件的鼠标左键单击,同时将所有其他按钮单击传递给基本 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 值可防止将事件发送到其他对象。

事件过滤器

有时,一个对象需要查看并可能拦截传递到另一个对象的事件。例如,对话框通常想要过滤某些小部件的按键;例如,修改返回键处理。

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 or QCoreApplication 对象上安装事件筛选器来筛选整个应用程序的所有事件。此类全局事件筛选器在特定于对象的筛选器之前调用。这是非常强大的,但它也减慢了整个应用程序中每个事件的事件交付速度;通常应改用所讨论的其他技术。

发送事件

许多应用程序都希望创建和发送自己的事件。您可以通过构造合适的事件对象并使用QCoreApplication::sendEvent() and QCoreApplication::postEvent(). 发送它们,这与 Qt 自己的事件循环完全相同的方式发送事件。

sendEvent() 立即处理事件。当它返回时,事件筛选器和/或对象本身已经处理了事件。对于许多事件类,有一个名为isAccepted()的函数,它告诉您事件是被调用的最后一个处理程序接受还是被拒绝。

postEvent() 将事件发布到队列中以供以后调度。下次Qt的主事件循环运行时,它会调度所有已发布的事件,并进行一些优化。例如,如果有多个调整大小事件,则会将它们压缩为一个。这同样适用于绘画事件:QWidget::update() 调用postEvent(),这消除了闪烁,并通过避免多次重绘来提高速度。

postEvent()  也在对象初始化期间使用,因为发布的事件通常会在对象初始化完成后很快被调度。在实现小部件时,重要的是要意识到事件可以在其生命周期的早期交付,因此,在其构造函数中,请确保在它有机会接收事件之前尽早初始化成员变量。 若要创建自定义类型的事件,需要定义一个事件编号,该事件编号必须大于QEvent::User,并且可能需要对 QEvent 进行子类化,以便传递有关自定义事件的特定信息。有关更多详细信息,请参阅 QEvent 文档。

posted @ 2022-09-29 11:03  南海捉虾  阅读(94)  评论(0编辑  收藏  举报