Qt 事件系统(转)

转自http://bbs.csdn.net/topics/390316264

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
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