(8条消息) 第七章 Qt事件(event)处理
一、事件处理器
事件是由窗口系统或者Qt自身产生的,以响应各类事件。当用户按下键盘或者鼠标就会产生相关事件,系统会捕捉到该事件。在使用Qt进行编程时,基本不需要考虑事件,因为发生事件时,Qt会自己发出信号,不过自定义窗口就要特别注意了。
不应该混淆“事件“和”信号”这两个概念。一般情况下,在使用窗口部件时候,信号是十分有用的;而实现窗口部件,事件则十分有用哦~
换句话说,在使用QPushButton时候,clicked()信号会更有用,但是需要实现类似QPushButton功能的类编写事件代码会更有效。
Qt中,事件就是QEvent子类的一个实例。Qt处理的事件类型非常多,每一种事件都有一个枚举类型值对应。例如:QEvent::type()可以返回处理鼠标事件的QEvent::MouseButtonPress。
通过重新实现事件函数可以打到你自己的目的。在前面几章中也有事件使用实例,回顾一下,温故知新。
这里说明另外一种事件,定时器事件。定时器事件允许应用程序可以再一定的时间间隔后执行事件处理。定时器事件可以用来实现光标的闪烁和其他的动画播放,或者只是简单的刷新。下面给出一个滚动字母例子:
头文件
#ifndef TICKER_H
#define TICKER_H
#include <QWidget>
class Ticker : public QWidget
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
public:
Ticker(QWidget *parent = 0);
void setText(const QString &newText);
QString text() const { return myText; }
QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent *event);
void timerEvent(QTimerEvent *event);
void showEvent(QShowEvent *event);
void hideEvent(QHideEvent *event);
private:
QString myText;
int offset;
int myTimerId;
};
#endif
从新实现事件处理器:
protected:
void paintEvent(QPaintEvent *event);
void timerEvent(QTimerEvent *event);
void showEvent(QShowEvent *event);
void hideEvent(QHideEvent *event);
尤其是timerEvent,事件参数大多都是QXXXEvent类型的。
实现文件:
#include <QtGui>
#include "ticker.h"
Ticker::Ticker(QWidget *parent)
: QWidget(parent)
{
offset = 0;
myTimerId = 0;
}
void Ticker::setText(const QString &newText)
{
myText = newText;
update();
updateGeometry();
}
QSize Ticker::sizeHint() const
{
return fontMetrics().size(0, text());
}
void Ticker::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
int textWidth = fontMetrics().width(text());
if (textWidth < 1)
return;
int x = -offset;
while (x < width()) {
painter.drawText(x, 0, textWidth, height(),
Qt::AlignLeft | Qt::AlignVCenter, text());
x += textWidth;
}
}
void Ticker::showEvent(QShowEvent * /* event */)
{
myTimerId = startTimer(30);
}
void Ticker::timerEvent(QTimerEvent *event)
{
if (event->timerId() == myTimerId) {
++offset;
if (offset >= fontMetrics().width(text()))
offset = 0;
scroll(-1, 0);
} else {
QWidget::timerEvent(event);
}
}
void Ticker::hideEvent(QHideEvent * /* event */)
{
killTimer(myTimerId);
myTimerId = 0;
}
构造函数吧offset变量初始化为0,。用来绘制文本x坐标就去自与这个offset数值。定时器的ID通常是非0的,所以可以使用0表示定时器还没有启动。
setText()调用update()强制执行一个重绘操作。
void Ticker::showEvent(QShowEvent * /* event */)
函数用来提供一个定时器,QObject::startTimer()调用会返回一个ID数字,可以在以后用这个数字识别该定时器。调用之后,大约没30毫秒产生一个定时器事件,切记精度并不是实际上的30毫秒,与操作系统有关的嘞。
void Ticker::timerEvent(QTimerEvent *event)
系统每隔一定时间都会调用一次该函数,通过在offset上加1来模拟移动,从而形成文本宽度的连续滚动。然后,使用scroll()执行滚动一个像素的操作。
void Ticker::hideEvent(QHideEvent * /* event */)
可以用来停止定时器。
main.cpp
#include <QApplication>
#include "ticker.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Ticker ticker;
ticker.setWindowTitle(QObject::tr("Ticker"));
ticker.setText(QObject::tr("How long it lasted was impossible to "
"say ++ "));
ticker.show();
return app.exec();
}
二、安装事件过滤器
二、安装事件过滤器
Qt事件模型是一个非常强大的功能,QObject实例在看到他自己的事件之前,可以通过设置另一个QObject实例先监视这些事件。
Qt创建了QEvent事件对象之后,会调用QObject的event()函数做事件的分发。有时候,你可能需要在调用event()函数之前做一些另外的操作,比如,对话框上某些组件可能并不需要响应回车按下的事件,此时,你就需要重新定义组件的event()函数。如果组件很多,就需要重写很多次event()函数,这显然没有效率。为此,你可以使用一个事件过滤器,来判断是否需要调用event()函数。QOjbect有一个eventFilter()函数,用于建立事件过滤器。这个函数的签名如下:
virtual bool QObject::eventFilter(QObject * watched, QEvent * event)
如果watched对象安装了事件过滤器,这个函数会被调用并进行事件过滤,然后才轮到组件进行事件处理。在重写这个函数时,如果你需要过滤掉某个事件,例如停止对这个事件的响应,需要返回true。
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 class
return QMainWindow::eventFilter(obj, event);
}
}
上面的例子中为MainWindow建立了一个事件过滤器。为了过滤某个组件上的事件,首先需要判断这个对象是哪个组件,然后判断这个事件的类型。例如,我不想让textEdit组件处理键盘事件,于是就首先找到这个组件,如果这个事件是键盘事件,则直接返回true,也就是过滤掉了这个事件,其他事件还是要继续处理,所以返回false。对于其他组件,我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数。
在创建了过滤器之后,下面要做的是安装这个过滤器。安装过滤器需要调用installEventFilter()函数。这个函数的签名如下:
void QObject::installEventFilter ( QObject * filterObj )
这个函数是QObject的一个函数,因此可以安装到任何QObject的子类,并不仅仅是UI组件。这个函数接收一个QObject对象,调用了这个函数安装事件过滤器的组件会调用filterObj定义的eventFilter()函数。例如,textField.installEventFilter(obj),则如果有事件发送到textField组件是,会先调用obj->eventFilter()函数,然后才会调用textField.event()。
当然,你也可以把事件过滤器安装到QApplication上面,这样就可以过滤所有的事件,已获得更大的控制权。不过,这样做的后果就是会降低事件分发的效率。
如果一个组件安装了多个过滤器,则最后一个安装的会最先调用,类似于堆栈的行为。
注意,如果你在事件过滤器中delete了某个接收组件,务必将返回值设为true。否则,Qt还是会将事件分发给这个接收组件,从而导致程序崩溃。
事件过滤器和被安装的组件必须在同一线程,否则,过滤器不起作用。另外,如果在install之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。
事件的调用最终都会调用QCoreApplication的notify()函数,因此,最大的控制权实际上是重写QCoreApplication的notify()函数。由此可以看出,Qt的事件处理实际上是分层五个层次:重定义事件处理函数,重定义event()函数,为单个组件安装事件过滤器,为QApplication安装事件过滤器,重定义QCoreApplication的notify()函数。这几个层次的控制权是逐层增大的。
参考资料:
QT学习 之 事件与事件过滤器 点击打开链接
Qt怎样使用事件过滤器 点击打开链接
Qt的事件过滤器 点击打开链接
三、处理密集时的响应保持
当调用了QApplication::exec()时就启动了Qt的时间循环。在开始的时候,Qt会发出一些时间命令来显示和绘制窗口部件。在这之后,事件循环就开始运行,他不断的检查是否有事件发生并且把这些时间发送给应用程序中的QObject。
当处理一个时间时,也可能会同时产生一些其他的事件并且会追加到Qt的事件队列中。若在处理一个特定事件耗费太多时间就会让界面无法响应。当然可以用多线程解决问题,但是我不会哈哈哈~还可以用QApplication::processEvent()。这个函数告诉Qt处理所有那些还没有被处理的各类事件,然后再将控制权返回给调用者。实际上QApplication::exce()就是一个不停调用processEvent的while循环。
参考:点击打开链接