【QT】事件系统

事件系统

在 Qt 中,事件是对象,派生自抽象的 QEvent 类,代表了在应用程序内部发生的事情,或者是应用程序需要知道的外部活动的结果。事件可以被任何 QObject 子类的实例接收和处理,但它们特别适用于小部件。本文档描述了在典型应用程序中如何传递和处理事件。

事件如何传递

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

这个函数本身不处理事件;根据传递的事件类型,它会调用该特定类型事件的事件处理程序,并根据事件是否被接受或忽略发送响应。

一些事件,如 QMouseEvent 和 QKeyEvent,来自窗口系统;一些,如 QTimerEvent,来自其他来源;还有一些来自应用程序本身。

事件类型

大多数事件类型都有特殊的类,特别是 QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent 和 QCloseEvent。每个类都是 QEvent 的子类,并添加了特定于事件的函数。例如,QResizeEvent 添加了 size() 和 oldSize() 来使小部件能够发现它们的尺寸如何被改变。

有些类支持多个实际事件类型。QMouseEvent 支持鼠标按钮按下、双击、移动和其他相关操作。

每个事件都有一个关联的类型,定义在 QEvent::Type 中,这可以作为方便的运行时类型信息源,快速确定给定事件对象是从哪个子类构造而来。

由于程序需要以各种各样和复杂的方式做出反应,Qt 的事件传递机制是灵活的。QCoreApplication::notify() 的文档简洁地讲述了整个故事;Qt Quarterly 文章 Another Look at Events 在更不简洁地总结了它。在这里,我们将解释足够 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,则阻止事件传递到其他对象。

事件过滤器

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

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 对象上安装事件过滤器来过滤整个应用程序的 所有 事件。这种全局事件过滤器在对象特定的过滤器之前被调用。这非常强大,但也会减慢整个应用程序中每个事件的事件传递速度;通常应该使用其他讨论过的技术。

发送事件

许多应用程序希望创建并发送自己的事件。你可以使用与 Qt 自身事件循环完全相同的方式发送事件,方法是构造适当的事件对象,并使用 QCoreApplication::sendEvent() 和 QCoreApplication::postEvent() 发送它们。

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

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

postEvent() 在对象初始化期间也被使用,因为发布的事件通常会在对象初始化完成后很快分派。在实现小部件时,重要的是要意识到事件可以在其生命周期的非常早期就被传递,因此,在其构造函数中,请确保在任何可能接收事件之前尽早初始化成员变量。

要创建自定义类型的事件,你需要定义一个事件号,它必须大于 QEvent::User,并且你可能需要继承 QEvent 来传递有关你的自定义事件的特定信息。有关详细信息,请参阅 QEvent 文档。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值