Qt Event http://blog.163.com/huangwei820410@126/blog/static/240704922009610104424725/
关于QT事件(event)的简单介绍
事件(event)是有系统或者Qt本身在不同的时刻发出的。当用户按下鼠标,敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件是在对用户操作做出响应的时候发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
一般来说,使用Qt编程时,我们并不会把主要精力放在事件上,因为在Qt中,需要我们关心的事件总会发出一个信号。比如,我们关心的是QPushButton的鼠标点击,但我们不需要关心这个鼠标点击事件,而是关心它的clicked()信号。这与其他的一些框架不同:在Swing中,你所要关心的是JButton的ActionListener这个点击事件。
Qt的事件很容易和信号槽混淆。这里简单的说明一下,signal由具体对象发出,然后会马上交给由connect函数连接的slot进行处理;而对于事件,Qt使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部,前一个事件完成后,取出后面的事件进行处理。但是,必要的时候,Qt的事件也是可以不进入事件队列,而是直接处理的。并且,事件还可以使用“事件过滤器”进行过滤。总的来说,如果我们使用组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。因为我们可以通过事件来改变组件的默认操作。比如,如果我们要自定义一个QPushButton,那么我们就需要重写它的鼠标点击事件和键盘处理事件,并且在恰当的时候发出clicked()信号。
还记得我们在main函数里面创建了一个QApplication对象,然后调用了它的exec()函数吗?其实,这个函数就是开始Qt的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件。当事件发生时,Qt将创建一个事件对象。Qt的所有事件都继承于 QEvent类。在事件对象创建完毕后,Qt将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(eventhandler)。
在所有组件的父类QWidget中,定义了很多事件处理函数,如keyPressEvent()、keyReleaseEvent()、mouseDoubleClickEvent()、mouseMoveEvent ()、mousePressEvent()、mouseReleaseEvent()等。这些函数都是protected virtual的,也就是说,我们应该在子类中重定义这些函数。下面来看一个例子。
#include
#include
#include
#include
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("
Move:
(%1, %2)
")
.arg(QString::number(event->x()), QString::number(event->y())));
}
void
EventLabel::mousePressEvent(QMouseEvent *event)
{
this->setText(QString("
Press: (%1, %2)
")
.arg(QString::number(event->x()), QString::number(event->y())));
}
void
EventLabel::mouseReleaseEvent(QMouseEvent *event)
{
QString msg;
msg.sprintf("
Release: (%d, %d)
",
event->x(), event->y());
this->setText(msg);
}
int
main(int argc, char *argv[])
{
QApplication app(argc, argv);
EventLabel *label = new EventLabel;
label->setWindowTitle("MouseEvent Demo");
label->resize(300, 200);
label->show();
return app.exec();
}
这里我们继承了QLabel类,重写了mousePressEvent、mouseMoveEvent和MouseReleaseEvent三个函数。我们并没有添加什么功能,只是在鼠标按下(press)、鼠标移动(move)和鼠标释放(release)时把坐标显示在这个Label上面。注意我们在mouseReleaseEvent函数里面有关QString的构造。我们没有使用arg参数的方式,而是使用C语言风格的sprintf来构造QString对象,如果你对C语法很熟悉(估计很多C+++程序员都会比较熟悉的吧),那么就可以在Qt中试试熟悉的C格式化写法啦!
event filter是什么?
EventFilter即所谓事件过滤器,在Qt中是一个比较重要的概念,它的功能是把所有事件在到达watchee(被监控者)之前全部传递给另一个watcher(监控者),由watcher先行处理并决定是否继续传递该事件,如果继续传递, 则事件将回传给watchee来处理。 可能很多人已经知道怎么用event filter处理事件了,不过你也别嫌本文太简单, 毕竟总是不断的有新人提出类似的问题, 就当是复习一下吧。
event filter的常见应用场合
用来处理热键 -- 比如一个界面上可以由用户热键来触发的多个按钮。 由于只有得到焦点的控件才能获得键盘的事件, 如果不用event filter就需要给每个button都加上键盘事件的处理, 还要在button里去访问兄弟button的指针,逻辑非常混乱。如果由主窗体做各个按钮的eventFilter, 则只需要在主窗体里去处理键盘事件就好,而且主窗体可以很容易的访问到各个button的指针,很方便。
用来代替派生和重写虚函数 -- Qt里的键盘鼠标事件基本上都是以虚函数的方式来处理,要想重写虚函数则必须派生一个子类,这样的话如果只是一个简单的事件处理也去派生子类代价未免大了些,这时候就值得用用eventFilter。比如我的MDI界面想在每个子窗体关闭的时候做一些统一的操作,一般的做法是处理子窗体的closeEvent。但显然给每个子窗体都去派生个子类太不现实,最好的方法是把mainwindow作为子窗体的eventFilter去处理CloseEvent事件。
这里只举了两个例子, 相信聪明的同学们能在自己的程序中找到适合eventFilter发挥的位置。
如何使用event filter处理事件?
在前面的一篇blog中, 作者bug介绍了Qt中事件传递的顺序(如果没看过的先来扫扫盲),其中就提到了两个级别的event filter, 一个是为QApplication类安装一个eventFilter, 另一个是针对某个控件来安装eventFilter。 本质上这两个级别的event filter调用的都是QObject类提供的API:“installEventFilter”。
event filter的使用有点曲折, 有两个步骤要做。一是要调用watchee的installEventFilter以watcher指针为参数
watchee->installEventFilter(watcher);
二是在watcher的类中实现bool eventFilter(QObject*, QEvent*) 这个虚函数,在此函数中处理事件。
class KeyPressEater : public QObject { Q_OBJECT ... protected: bool eventFilter(QObject *obj, QEvent *event); }; bool KeyPressEater::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); qDebug("Ate key press %d", keyEvent->key()); return true; } else { // standard event processing return QObject::eventFilter(obj, event); } }
上面的例子代码是直接从Qt文档里抄的, 呵呵, 其实文档说的实在是很明白, 不知道为啥还是有很多人不懂这个怎么用。
其中几点需要注意的地方:
- watchee和watcher必须都是从QObject派生的子类实例
- watcher的类必须是一个自定义的子类(因为需要重新实现虚函数, 用现成的类是不行的。)
- watcher类的eventFilter函数返回true时表示该事件处理完毕,不再继续传递;返回false表示该事件仍然传递
- eventFilter的实现的最后必须调用watcher基类的eventFilter函数以传递事件。如果不调的话watcher的所有事件都将丢失。
- 注意eventFilter的声明必须和文档里的一模一样, 写的时候注意返回值、大小写和参数类型
其他
在Qt中还有一些应用程序级别的eventFilter函数, 是和平台相关的,如x11EventFilter可以截获程序得到的所有X事件。相应的Windows平台下用winEventFilter… 大家不要被函数的名字给误导了, 这些函数并不是全局级别的,充其量只能拿到应用程序内部的事件,想做系统级别的事件过滤还是差着档次…