Qt是事件驱动的, 程序每个动作都是由某个事件所触发。 Qt事件的类型很多,我们可以通过查看Qt的 manual中的Event System 和 QEvent 来获得各个事件的详细信息。
为了完整起见,一份Qt4.6的事件列表附在本文后面。
事件来源
- Spontaneous events(自发事件)
- 从系统得到的消息,比如鼠标按键,键盘按键等。Qt事件循环的时候读取这些事件,转化为QEvent后依次处理
- Posted events
- 有Qt或应用程序产生,放入消息队列
- QCoreApplication::postEvent()
- Sent events
- 由Qt或应用程序产生,不放入队列,直接被派发和处理
- QCoreApplication::sendEvent()
比如考虑重绘事件处理函数 paintEvent(),3种事件都能使得该函数被调用:
- 当窗口被其他窗口覆盖后,再次重新显示时,系统将产生 spontaneous 事件来请求重绘
- 当我们调用 update() 时,产生的是 Posted 事件
- 当我们调用 repaint() 时,产生的是 Sent 事件
事件派发
事件循环
while (!exit_was_called) { while (!posted_event_queue_is_empty) { process_next_posted_event(); } while (!spontaneous_event_queue_is_empty) { process_next_spontaneous_event(); } while (!posted_event_queue_is_empty) { process_next_posted_event(); } }
- 先处理Qt事件队列中的事件,直至为空
- 再处理系统消息队列中的消息,直至为空
- 在处理系统消息的时候会产生新的Qt事件,需要对其再次进行处理
不通过事件循环
sendEvent的事件派发不通过事件循环。QApplication::sendEvent()是通过调用QApplication::notify(),直接进入了事件的派发和处理环节,是同步的。
sendEvent与postEvent的使用
- 两个函数都是接受一个 QObject * 和一个 QEvent * 作为参数。
- postEvent 的 event 必须分配在 heep 上。用完后会被Qt自动删除
- sendEvent 的 event 必须分配在 stack 上。
例子(发送X按键事件到mainWin):
QApplication::postEvent(mainWin, new QKeyEvent(QEvent::KeyPress, Key_X, 'X', 0));
QKeyEvent event(QEvent::KeyPress, Key_X, 'X', 0); QApplication::sendEvent(mainWin, &event);
notify
所有的事件都最终通过 notify 派发到相应的对象中。
bool QCoreApplication::notify ( QObject * receiver, QEvent * event )
事件过滤
看看notify()调用的内部函数notify_helper()的源码部分:
- 先通过 Applicaton 安装的过滤器
- 如果未被过滤,再通过 receiver 安装的过滤器
-
如果仍未被过滤,才调用 receiver->event() 函数进行派发
/*!\internal Helper function called by notify() */ bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event) { // send to all application event filters if (sendThroughApplicationEventFilters(receiver, event)) return true; // send to all receiver event filters if (sendThroughObjectEventFilters(receiver, event)) return true; // deliver the event return receiver->event(event); }
事件在传递到对象之前(调用obj->event()函数之前),要先能通过 Applicaton 和 obj 安装的过滤器,那么过滤器是怎么安装的:
- 首先QObject中有一个类型为QObjectList的成员变量,名字为eventFilters
- 当某个QObject安装了事件过滤器之后, 它会将filterObj的指针保存在eventFilters中
monitoredObj->installEventFilter(filterObj);
- 在事件到达QObject::event()函数之前,会先查看该对象的eventFilters列表, 如果非空, 就先调用列表中对象的eventFilter()函数.
bool QObject::eventFilter ( QObject * watched, QEvent * event )
- 事件过滤器函数eventFilter()返回值是bool型
- 如果返回true, 则表示该事件已经被处理完毕, Qt将直接返回, 进行下一事件的处理
- 如果返回false, 事件将接着被送往剩下的事件过滤器或是目标对象进行处理
对于 QCoreApplication ,由于也是QObject 派生类,安装过滤器方式与前述相同。
事件转发
对于某些类别的事件, 如果在整个事件的派发过程结束后还没有被处理, 那么这个事件将会向上转发给它的父widget, 直到最顶层窗口.
如何判断一个事件是否被处理了呢? (有两个层次)
- QApplication::notify(), QObject::eventFilter(), QObject::event() 通过返回bool值来表示是否已处理. “真”表示已经处理, “假”表示事件需要继续传递
- 另一种是调用QEvent::ignore() 或 QEvent::accept() 对事件进行标识,accept表示事件被处理
为清楚起见,贴一点Qt的源码(来自 QApplication::notify()):
case QEvent::ToolTip: case QEvent::WhatsThis: case QEvent::QueryWhatsThis: { QWidget* w = static_cast<QWidget *>(receiver); QHelpEvent *help = static_cast<QHelpEvent*>(e); QPoint relpos = help->pos(); bool eventAccepted = help->isAccepted(); while (w) { QHelpEvent he(help->type(), relpos, help->globalPos()); he.spont = e->spontaneous(); res = d->notify_helper(w, w == receiver ? help : &he); e->spont = false; eventAccepted = (w == receiver ? help : &he)->isAccepted(); if ((res && eventAccepted) || w->isWindow()) break; relpos += w->pos(); w = w->parentWidget(); } help->setAccepted(eventAccepted); } break;
这儿显示了对 WhatsThis 事件的处理:先派发给 w,如果事件被accepted 或已经是顶级窗口,则停止;否则获取w的父对象,继续派发。
事件处理
- 重新实现一个特定的事件handler
QObject与QWidget提供了许多特定的事件handlers,分别对应于不同的事件类型。(如paintEvent()对应paint事件)
- 重新实现QObject::event()
event()函数是所有对象事件的入口,QObject和QWidget中缺省的实现是简单地把事件推入特定的事件handlers。
- 在QObject安装上事件过滤器
事件过滤器是一个对象,它接收别的对象的事件,在这些事件到达指定目标之间。
- 在aApp上安装一个事件过滤器,它会监视程序中发送到所有对象的所有事件
- 重新实现QApplication:notify(),Qt的事件循环与sendEvent()调用这个函数来分发事件,通过重写它,你可以在别人之前看到事件。
事件列表
Qt4.6的事件列表:
- QAccessibleEvent
- QActionEvent
- QChildEvent
- QCloseEvent
- QCustomEvent
- QDragLeaveEvent
- QDropEvent
- QDragMoveEvent
- QDragEnterEvent
- QDragMoveEvent
- QDynamicPropertyChangeEvent
- QFileOpenEvent
- QFocusEvent
- QGestureEvent
- QGraphicsSceneEvent
- QGraphicsSceneContextMenuEvent
- QGraphicsSceneDragDropEvent
- QGraphicsSceneHelpEvent
- QGraphicsSceneHoverEvent
- QGraphicsSceneMouseEvent
- QGraphicsSceneMoveEvent
- QGraphicsSceneResizeEvent
- QGraphicsSceneWheelEvent.
- QHelpEvent
- QHideEvent
- QHoverEvent
- QIconDragEvent
- QInputEvent
- QContextMenuEvent
- QKeyEvent
- QMouseEvent
- QTabletEvent
- QTouchEvent
- QWheelEvent
- QInputMethodEvent
- QMoveEvent
- QPaintEvent
- QResizeEvent
- QShortcutEvent
- QShowEvent
- QStatusTipEvent
- QTimerEvent
- QWhatsThisClickedEvent
- QWindowStateChangeEvent