Qt__事件处理机制
一、Qt事件##
Qt会将系统消息(如鼠标按键、键盘按键等)转化为Qt事件,Qt事件被封装为对象且定义该对象的类均继承自抽象类QEvent。
二、Qt事件的产生##
1.操作系统产生###
- Spontaneous events(自发事件)
从系统得到的消息,比如鼠标按键,键盘按键等,放入系统消息队列中。
2.QT应用程序程序产生###
-
Posted events
由Qt或应用程序产生,放入Qt消息队列。
static void postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority);
-
Sent events
由Qt或应用程序产生,不放入队列,直接被派发和处理。
static bool sendEvent(QObject *receiver, QEvent *event);
注:两个函数都是接受一个 QObject * 和一个 QEvent * 作为参数。
前辈们说 sendEvent 的 event 可分配在 stack或者heep 上; postEvent 的 event 必须分配在 heep 上。
但我试的两个怎么都可以
代码如下:
QPointF pos(10,10);
QMouseEvent* mEvnPress = new QMouseEvent(QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(&label,mEvnPress);
例子###
比如考虑重绘事件处理函数 paintEvent(),3种事件都能使得该函数被调用:
当窗口被其他窗口覆盖后,再次重新显示时,系统将产生 spontaneous 事件来请求重绘。
当我们调用 update() 时,产生的是 Posted 事件。
当我们调用 repaint() 时,产生的是 Sent 事件。
三、Qt事件的调用##
当调用QApplication::exec()时,就进入了事件队列循环,不断地检测事件并调用事件。
- 先处理Qt事件队列中的事件, 直至为空。
- 再处理系统消息队列中的消息, 直至为空。
- 在处理系统消息的时候会产生新的Qt事件, 需要对其再次进行处理。
而在调用QApplication::sendEvent的时候, 消息会立即被处理,是同步的。实际上QApplication::sendEvent()是通过调用QApplication::notify(), 直接进入了事件的派发和处理环节。
四、事件的派发与处理##
事实上,Qt 事件的调用最终都会追溯到QCoreApplication::notify()函数。这个函数的声明是:
virtual bool QCoreApplication::notify ( QObject * receiver, QEvent * event );
该函数会将event发送给receiver,也就是调用receiver->event(event),这个函数就实现了事件的派发,根据event的类型将调用不同的事件处理器mousePressEvent(), keyPressEvent(), paintEvent()等等。
注意,QCoreApplication::notify()这个函数为任意线程的任意对象的任意事件调用,因此,它不存在事件过滤器的线程的问题。不过我们并不推荐这么做,因为notify()函数只有一个,而事件过滤器要灵活得多。
五、事件的过滤##
事件在经过notify()调用的内部函数notify_helper()的源码部分的源码如下:
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);
}
事件在传递到对象之前(调用receiver->event()函数之前),要先能通过 Applicaton 和 receiver 安装的过滤器,那么过滤器是怎么安装的:
QObject(A)->installEventFilter(QObject(B));
首先QObject(A)中有一个类型为QObjectList的成员变量,名字为eventFilters
当某个QObject(A)安装了事件过滤器之后, 它会将QObject(B)的指针保存在eventFilters中,在事件到达QObject::event()函数之前,会先查看该对象的eventFilters列表, 如果非空, 就先调用列表中对象的eventFilter()函数.
过滤器的定义如下:
bool QObject::eventFilter ( QObject * watched, QEvent * event )
事件过滤器函数eventFilter()返回值是bool型
如果返回true, 则表示该事件已经被处理完毕, Qt将直接返回, 进行下一事件的处理
如果返回false, 事件将接着被送往剩下的事件过滤器或是目标对象进行处理
如果使用installEventFilter()函数给一个对象安装事件过滤器,那么该事件过滤器只对该对象有效,只有这个对象的事件需要先传递给事件过滤器的eventFilter()函数进行过滤,其它对象不受影响。
给 QCoreApplication(由于也是QObject 派生类,安装过滤器方式与前述相同)安装的过滤器属于全局的事件过滤器对程序中的每一个对象都有效,任何对象的事件都是先传给eventFilter()函数。
事件过滤器的好处在于事件过滤器在目标对象接收到事件之前进行处理,如果我们将事件过滤掉,目标对象根本不会见到这个事件。
六、事件的转发##
对于某些类别的事件, 如果在整个事件的派发过程结束后还没有被处理, 那么这个事件将会向上转发给它的父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的父对象,继续派发。
七、总结##
现在我们可以总结一下 Qt 的事件处理,实际上是有五个层次:
- 1.重写paintEvent()、mousePressEvent()等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
- 2.重写event()函数。event()函数是所有对象的事件入口,QObject和QWidget中的实现,默认是把事件传递给特定的事件处理函数。
- 3.在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
- 4.在QCoreApplication::instance()上面安装事件过滤器。该过滤器将过滤所有对象的所有事件,因此和notify()函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
- 5.重写QCoreApplication::notify()函数。这是最强大的,和全局事件过滤器一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为QCoreApplication是单例的)。
为了进一步了解这几个层次的事件处理方式的调用顺序,我们可以编写一个测试代码:
#include <qapplication.h>
#include <QMainWindow>
#include <QPushButton>
#include <custombutton.h>
class Label : public QWidget
{
public:
Label()
{
installEventFilter(this);
}
bool eventFilter(QObject *watched, QEvent *event)
{
if (watched == this) {
if (event->type() == QEvent::MouseButtonPress) {
qDebug() << "eventFilter";
}
}
return false;
}
protected:
void mousePressEvent(QMouseEvent *)
{
qDebug() << "mousePressEvent";
}
bool event(QEvent *e)
{
if (e->type() == QEvent::MouseButtonPress) {
qDebug() << "event";
}
return QWidget::event(e);
}
};
class EventFilter : public QObject
{
public:
EventFilter(QObject *watched, QObject *parent = 0) :
QObject(parent),
m_watched(watched)
{
}
bool eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_watched) {
if (event->type() == QEvent::MouseButtonPress) {
qDebug() << "QApplication::eventFilter";
}
}
return false;
}
private:
QObject *m_watched;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Label label;
app.installEventFilter(new EventFilter(&label, &label));
QPointF pos(10,10);
QMouseEvent* mEvnPress = new QMouseEvent(QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(&label,mEvnPress);
label.show();
return app.exec();
}
运行结果:
因此可以知道,全局事件过滤器被第一个调用,之后是该对象上面的事件过滤器,其次是event()函数,最后是特定的事件处理函数。