目录
一、事件基本概念
1.1 事件基本概念和产生
1.2 事件类
1.3 事件发送
二、事件的捕获处理
2.1 事件处理流程
2.2事件分发(event)处捕获事件
2.3 事件过滤器
2.4 重新实现事件处理函数
三、 QEventLoop
3.1事件循环基本概念
3.2 QEventLoop基本使用
四、update异步刷新流程
4.1 刷新事件的异步投递
4.2 刷新事件处理流程
4.3 重绘到内存Image
4.4 BackingStore和双缓冲
4.5 树形绘制
一、事件基本概念
1.1 事件基本概念和产生
事件(event)是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
Qt 程序需要在main()函数创建一个QApplication对象,然后调用它的exec()函数。这个函数就是开始 Qt 的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件。当事件发生时,Qt 将创建一个事件对象。Qt 中所有事件类都继承于QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)。
在所有组件的父类QWidget中,定义了很多事件处理的回调函数,如
这些函数都是 protected virtual 的,也就是说,我们可以在子类中重新实现这些函数。
- keyPressEvent()
- keyReleaseEvent()
- mouseDoubleClickEvent()
- mouseMoveEvent()
- mousePressEvent()
- mouseReleaseEvent() 等。
1.2 事件类
一个事件就是一个类对象 ,每一个事件类都有个唯一身份值:type值; type 值的作用就是我们在处理事件时用于识别事件类的代号 , 我们在官方文档可以看到,Qt 自带的事件类的 type 值都已经在 QEvent::type 中有了,数值范围在 0 - 999 之间 ; 而我自己定义的事件类也有个 type 值。为了保证我的这个值不和 Qt 的冲突,所以数值要大于 999。Qt 给我们规定了两个边界值:QEvent::User 和 QEvent::MaxUser,即 1000 - 65535。
Qt 提供了一个函数 registerEventType() 专门用于自定义事件的注册。如下:
class MyEvent : public QEvent
{
public:MyEvent();MyEvent(int x, int y, int z);static const Type type;int x;int y;int z;
};
1.3 事件发送
bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
void QCoreApplication::sendPostedEvents(QObject *receiver = Q_NULLPTR, int event_type = 0)
直接发送:sendEvent
两个参数中一个是要发给谁,另一个是发送什么事件。这种方式的发送我们要等待对方处理,所以返回值是一个 bool 量来判断。对于许多事件类,有一个名为 isAccepted() 来告诉你是否被处理。因为事件被发送的时候,event 对象并不会被销毁,因此我们要在栈上创建 event 对象。
发到队列:postEvent
我们创建一个 Qt 程序的时候,一般在 main 下面会看到 QCoreApplication a(argc, argv) 以及 return a.exec() 的字样。这其实就是开启了 Qt 事件循环来维护一个事件队列,exec 的本质就是不停的调用 processEvent() 函数从队列中获取事件来处理。而 postEvent() 的作用就是把事件发送到这个队列中去。
这种方式不需要等待处理结果,只要把事件发到队列中就可以了,所以返回值是 void。由于事件队列会持有发送的事件对象,在 post 的时候会将事件 delete 掉,所以我们必须在堆上创建 event 对象。 它将分发所有发布的事件,并进行了一些优化。例如,如果有多个resize事件,它们会被压缩为一个。这同样适用于绘制事件:QWidget::update()调用postEvent(),通过避免多次重绘来消除闪烁并提高速度。
在队列中立即处理:sendPostedEvents
看参数我们就可以知道,这个函数的作用就是立刻、马上将队列中的 event_type 类型的事件立马交给 receiver 进行处理。需要注意的是,来自窗口系统的事件并不由这个函数进行处理,而是 processEvent()。
二、事件的捕获处理
2.1 事件处理流程
2.2事件分发(event)处捕获事件
事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler),event()函数主要用于事件的分发,具体类似以下代码。
//!!! Qt5
bool QObject::event(QEvent *e)
{switch (e->type()) {case QEvent::Timer:timerEvent((QTimerEvent*)e);break;case QEvent::ChildAdded:case QEvent::ChildPolished:case QEvent::ChildRemoved:childEvent((QChildEvent*)e);break;// ...default:if (e->type() >= QEvent::User) {customEvent(e);break;}return false;}return true;
}
所以,如果你希望在事件分发之前做一些操作,就可以重写这个event()函数了。
bool CustomTextEdit::event(QEvent *e)
{if (e->type() == QEvent::KeyPress) {QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);if (keyEvent->key() == Qt::Key_Tab) {qDebug() << "You press tab.";return true;//结束时间转发}}return QWidget::event(e);//必要的,转发事件给父类处理
}
2.3 事件过滤器
Qt 创建了QEvent事件对象之后,会调用QObject的event()函数处理事件的分发。显然,我们可以在event()函数中实现拦截的操作。由于event()函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很多个event()函数。这当然相当麻烦,更不用说重写event()函数还得小心一堆问题。
QObject有一个eventFilter()函数,用于建立事件过滤器。函数原型如下:
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );
这个函数是一个“事件过滤器”,会检查接收到的事件。如果这个事件是我们感兴趣的类型,就进行我们自己的处理;如果不是,就继续转发。这个函数返回一个 bool 类型,如果你想将参数 event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false。事件过滤器的调用时机是目标对象(也就是参数里面的watched对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么,watched对象以及以后所有的事件过滤器根本不会知道这么一个事件。
事件过滤器使用举例:
class MainWindow : public QMainWindow{public:MainWindow();protected:bool eventFilter(QObject *obj, QEvent *event);//重写事件过滤器private:QTextEdit *textEdit;};bool MainWindow::eventFilter(QObject *obj, QEvent *event){if (obj == textEdit) {if (event->type() == QEvent::KeyPress) {QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);qDebug() << "Ate key press" << keyEvent->key();return true;} else {return false;//}} else {// pass the event on to the parent classreturn QMainWindow::eventFilter(obj, event);}}MainWindow::MainWindow(){textEdit = new QTextEdit;setCentralWidget(textEdit);textEdit->installEventFilter(this);//安装事件过滤器}
installEventFilter()函数是QObject的函数,QApplication或者QCoreApplication对象都是QObject的子类,因此,我们可以向QApplication或者QCoreApplication添加事件过滤器。这种全局的事件过滤器将会在所有其它特性对象的事件过滤器之前调用。尽管很强大,但这种行为会严重降低整个应用程序的事件分发效率。因此,除非是不得不使用的情况,否则的话我们不应该这么做。
事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。
2.4 重新实现事件处理函数
对于一些简单事件,我们可以直接继承实现该事件的处理函数,在事件处理流程的最终环节给自定义处理,如以下代码,重新实现了鼠标的移动、按压和释放等事件。
class EventLabel : public QLabel
{
protected:void mouseMoveEvent(QMouseEvent *event);void mousePressEvent(QMouseEvent *event);void mouseReleaseEvent(QMouseEvent *event);
};void EventLabel::mouseMoveEvent(QMouseEvent *event)
{
this->setText(QString("<center><h1>Move: (%1, %2)
</h1></center>").arg(QString::number(event->x()),QString::number(event->y())));
}void EventLabel::mousePressEvent(QMouseEvent *event)
{this->setText(QString("<center><h1>Press:(%1, %2)
</h1></center>").arg(QString::number(event->x()),QString::number(event->y())));
}void EventLabel::mouseReleaseEvent(QMouseEvent *event)
{QString msg;msg.sprintf("<center><h1>Release: (%d, %d)</h1></center>",event->x(), event->y());this->setText(msg);
}int main(int argc, char *argv[])
{QApplication a(argc, argv);EventLabel *label = new EventLabel;label->setWindowTitle("MouseEvent Demo");label->resize(300, 200);label->show();return a.exec();
}
三、 QEventLoop
3.1事件循环基本概念
事件循环本质上就是一个无限循环,不停地去获取下一个事件,然后做出处理;直到 quit 事件发生,循环结束 。
3.2 QEventLoop基本使用
四、update异步刷新流程
4.1 刷新事件的异步投递
1.检查条件:检查控件是否隐藏或者刷新禁用,则直接返回。
2.计算更新区域:无参update则需要刷新整个区域,对于代参的则更新指定矩形区域。
3.如果控件支持BackingStore,则标记控件所属的顶层窗口(TLW)区域为“脏”,表示该部分需要重绘。
BackingStore:Qt框架中用于优化界面重绘的机制,Qt自动管理,主要作为TLW做缓冲区,暂存绘制内容;当某个区域需要更新时首先在缓冲区上重绘,完成后在复制到屏幕,提高重绘效率减少屏幕闪烁,支持局部更新。
4.事件投递:Qt向顶层窗口投递一个QEvent::UpdateRequest事件,将其放入事件队列;表示顶层窗口某个区域要重绘。
标脏函数markDirty:
4.2 刷新事件处理流程
1.事件分发:notify函数会会处理事件,将UpdateRequest分发给receiver 即TLW;
2.事件处理:TLW接受事件后会在event函数中处理,对于UpdateRequest事件调用syncBackingStore,触发重绘操作。
4.3 重绘到内存Image
1.syncBackingStore 过程中会在绘制设备一般时内存Image,绘制背景(drawWidget)、绘制前景(文本、图标 通常时paint事件)、绘制子控件(递归drawWidget);
2.绘制完之后调用系统API将内存上的Image刷新到屏幕。windows调用BitBlt。
4.4 BackingStore和双缓冲
BackingStore:Qt框架中用于优化界面重绘的机制,Qt自动管理,主要作为TLW做缓冲区,暂存绘制内容;当某个区域需要更新时首先在缓冲区上重绘,完成后在复制到屏幕,提高重绘效率减少屏幕闪烁,支持局部更新。
双缓冲:常用的图形处理技术,主要应用于频繁更新屏幕内容场景,如动画、游戏和GUI中。使用两个缓冲分别存储正在显示的图像和即将显示的图像,避免在绘制中直接修改屏幕内容导致闪烁和撕裂现象。
4.5 树形绘制
Setparient setchilden raise lower