Qt事件系统概述
- 一、概述
- 二、事件类型 - Event Types
- 三、事件处理程序 - Event Handlers
- 四、事件过滤器 - Event Filters
- 五、发送事件 - Sending Events
- 1. sendEvent()
- 2. postEvent()
一、概述
在Qt中,事件是由抽象的QEvent类派生而来的对象,表示发生在应用程序内部或应用程序需要知道的外部活动的结果。
事件可以由QObject子类的任何实例接收和处理,一般这个事件在窗口程序里面使用非常频繁。
那事件是如何传递并处理的呢?
当事件发生时,Qt会通过构造适当的QEvent子类的实例来创建一个event对象来表示事件,并通过调用其 event() 函数将事件传递给QObject的特定实例(或其子类之一)。
这个 event() 函数不处理事件本身;根据交付的事件类型,它调用该特定类型事件的事件处理程序,并根据事件是被接受还是被忽略发送响应。
有些事件,如QMouseEvent和QKeyEvent,来自window系统;有些事件(如QTimerEvent)来自其他来源;有些来自应用程序本身。
二、事件类型 - Event Types
大多数事件类型都有特殊的类,特别是QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent和QCloseEvent。每个类都是QEvent的子类,并添加了特定于事件的函数。例如,QResizeEvent添加了size()和oldSize(),让部件能够发现它们的尺寸是如何变化的。
有些类支持多个实际事件类型。就像 QMouseEvent支持鼠标按键、双击、移动及其他相关操作。
每个事件都有一个关联的事件类型,定义在QEvent:: type中,这可以用作运行时类型信息的方便来源,以快速确定给定事件对象是从哪个子类构建的。
由于程序需要以各种复杂的方式作出反应,因此Qt的事件传递机制是灵活的。QCoreApplication::notify() 的文档简明地讲述了整个过程;Qt的季刊文章《另一看事件》(Another Look at Events)则不那么简洁。在这里,我们将解释95%的应用程序。
三、事件处理程序 - Event Handlers
传递事件的正常方式是调用虚函数。例如,QPaintEvent通过调用QWidget::paintEvent()来交付。这个虚函数负责适当地作出反应,通常是通过重绘窗口组件。如果在实现虚函数时没有执行所有必要的工作,可能需要调用基类的实现。
也就是要自己去重写这个虚函数来实现功能。
例如,下面的代码处理自定义复选框部件上的鼠标左键单击,同时将所有其他按钮单击传递给基本的QCheckBox类
这个 MyCheckBox 就是继承下来QCheckBox并重写 mousePressEvent 函数的类。
:
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会拦截这些键来移动键盘焦点,但是有一些widget本身需要Tab键。
这些对象可以重新实现通用事件处理程序 QObject::event(),并在通常的处理之前或之后进行事件处理,或者完全取代函数。(如果是自己已经处理过的才去返回 true, 如果不去处理的话,后面基类的函数也会被调用的,有可能会覆盖自己写的功能)
下面就是实现的 一个按下 tab 能够循环切换tab的功能 的event()函数:
bool EventUse::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_Tab)
{
qDebug()<<"当前页面:"<<ui->tabWidget->currentIndex();
ui->tabWidget->setCurrentIndex((ui->tabWidget->currentIndex() + 1) % ui->tabWidget->count());
return true;
}
}
return QWidget::event(event);
}
请注意,对于所有未处理的情况,仍然调用QWidget::event(),并且返回值表明是否处理了事件:true值阻止将事件发送到其他对象。
四、事件过滤器 - Event Filters
有时,一个对象需要查看并拦截传递给另一个对象的事件。例如,对话框通常希望过滤某些部件的按键;;例如,修改返回键处理。
QObject::installEventFilter()函数通过设置事件过滤器来实现这一点,使指定的过滤器对象在其QObject::eventFilter()函数中接收目标对象的事件。
事件过滤器在目标对象之前处理事件,允许它根据需要检查和丢弃事件。可以使用QObject::removeEventFilter()函数删除现有的事件过滤器。
在调用filter对象的eventFilter()实现时,可以接受或拒绝事件,并允许或拒绝事件的进一步处理。如果所有的事件过滤器都允许进一步处理事件(每次都返回false),则事件会被发送到目标对象本身。如果其中一个停止处理(通过返回true),那么目标和以后的任何事件过滤器都不会看到该事件。相当于一个短路性。这就起到了过滤的功能嘛!
就行我在一个控件的父窗口安装了 一个事件处理器,处理了然后返回false,也在该控件上安装了事件处理器,针对同一个事件,父窗口返回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;
}
上面的代码展示了另一种拦截发送到特定目标widget的制表键按下事件的方法。在这种情况下,过滤器会处理相关的事件,并返回true以停止后续处理。所有其他事件都被忽略,过滤器返回false,允许它们通过安装在目标部件上的其他事件过滤器发送到目标部件。
通过在QApplication或QCoreApplication对象上安装事件过滤器,也可以过滤整个应用程序的所有事件。这样的全局事件过滤器在特定于对象的过滤器之前调用。这是非常强大的,但它也减慢了整个应用程序中每个事件的交付速度;这个通常应该使用讨论的其他技术。
QObject::installEventFilter(QObject *filterObj) 的用例如下:
在这个对象上安装一个事件过滤器filterObj。例如:
monitoredObj - > installEventFilter (filterObj);
事件过滤器是一个对象,它接收发送到该对象的所有事件。过滤器可以停止事件,也可以将事件转发给这个对象。事件过滤器filterObj通过eventFilter()函数接收事件。如果事件需要被过滤(即停止),eventFilter()函数必须返回true;否则必须返回false。
如果在单个对象上安装了多个事件过滤器,则最后安装的过滤器会首先被激活。类似压栈的机制
下面是KeyPressEater类,它会吃掉被监控对象的按键行为,因为他拦截了所有的按键行为:
class KeyPressEater : public QObject
{
Q_OBJECT
...
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
bool KeyPressEater::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug("Ate key press %d", keyEvent->key());
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
下面是如何在两个部件上安装它:
KeyPressEater *keyPressEater = new KeyPressEater(this);
QPushButton *pushButton = new QPushButton(this);
QListView *listView = new QListView(this);
pushButton->installEventFilter(keyPressEater);
listView->installEventFilter(keyPressEater);
例如,QShortcut类使用这种技术来拦截快捷键的按下。
警告:如果你删除了eventFilter()函数中的receiver对象,请确保返回true。如果返回false, Qt将事件发送给被删除的对象,程序将崩溃。
注意,筛选对象必须与该对象在同一个线程中。如果filterObj在不同的线程中,这个函数什么都不做。如果filterObj或该对象在调用该函数后被移动到不同的线程,则事件过滤器将不会被调用,直到两个对象再次具有相同的线程亲和性(未被删除)。
五、发送事件 - Sending Events
许多应用程序希望创建和发送自己的事件。通过构造合适的事件对象并使用QCoreApplication::sendEvent()和QCoreApplication::postEvent()来发送事件,你可以以与Qt自己的事件循环完全相同的方式发送事件。
1. sendEvent()
sendEvent()立即处理事件当它返回时,事件过滤器和/或对象本身已经处理了事件。对于许多事件类来说,都有一个名为isAccepted()的函数,它可以告诉你事件被上一个被调用的处理程序接受还是拒绝。
2. postEvent()
postEvent()将事件发送到队列中,以便稍后分发下一次Qt的主事件循环运行时,它将分发所有发布的事件,并进行了一些优化。例如,如果有多个resize事件,它们会被压缩为一个。这同样适用于绘制事件:QWidget::update()调用postEvent(),通过避免多次重绘来消除闪烁并提高速度。
postEvent()也用于对象初始化,因为提交的事件通常会在对象初始化完成后很快分发。在实现窗口时,重要的是要意识到事件可以在其生命周期的早期交付,因此,在其构造函数中,确保在它可能接收到事件之前尽早初始化成员变量。
要创建自定义类型的事件,您需要定义一个事件编号,它必须大于QEvent::User,并且您可能需要子类化QEvent以便传递有关自定义事件的特定信息。这个就和那个 win32消息编号一样