Qt 事件机制
1.1 Qt中的事件
1.1.1 事件的处理
Qt中处理事件的五种方法:
方法一:重新实现部件的paintEvent()、mousePressEvent()等事件处理函数。
方法二:重新实现notify函数。但它一次只能处理一个事件。这种方法需要继承QApplication类。
方法三:向QApplication对象上安装事件过滤器。每个程序只有一个QApplication对象,功能与notify类似,但可同时处理多个事件。需要使用全局的事件过滤器。
方法四:重新实现event()函数。其可以在事件到达默认的事件处理函数之前获得该事件。
方案五:在对象上安装事件过滤器。使用事件过滤器可以在界面类中同时处理不同子部件的不同事件。
最常见的是方法一和方法五。
1.1.2 事件的传递
每个程序main()函数的最后都会调用QApplication类的exec()函数,它会使Qt应用程序进入事件循环,然后应用程序在运行时会接收发生的各种事件。一旦有事件发生,Qt会构建相应的QEvent子类的对象来表示它,然后将它传递给相对应的Qobject对象或者其子对象。
具体的例子可以参考霍亚飞的Qt快速入门第三版6-2源码。
结论是,事件的传递顺序是这样的:先是事件过滤器,然后是焦点部件的event()函数,最后是焦点部件的事件处理函数;如果焦点部件忽略了该事件,就执行父部件的事件处理函数。注意,event()函数和事件处理函数是在焦点部件内部定义的,而事件过滤器却是在焦点部件的父部件中定义的。
2.1 定时器事件与随机数
2.1.1 定时器
QTimerEvent类用来描述一个定时器事件。对于一个QObject的子类来说,只需要int QObject::startTimer(int interval)函数可以直接开启定时器,时间为毫秒级。函数返回整形数来代表这个定时器,当定时器溢出时可以在timerEvent()函数在进行操作。
QTimer是一个计时器类,它的使用分三步,创建对象,连接signal和slot函数,start()。
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1000);
其中,SIGNAL(timeout())表示:每当计时结束,计时器归零并重新计时,并发送一个信号激活slot函数。如果我们想让这个计时器只计时一次,那么必须使用void setSingleShot(bool singleShot)函数。
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->setsetSingleShot(true)
timer->start(60000);
这样计时器只会倒计时1分钟,然后结束。
2.1.2 随机数
Qt中,生成随机数的函数为qrand()。该函数是标准C++函数rand的线程安全版本。但是,如果我们仅仅只是调用该函数来生成随机数,那么每次得到的随机数都将是相同的,这是因为Qt生成的随机数严格来说是一个“伪随机”,它的产生是根据随机数种子计算得到的,如果种子相同,那么所得到的随机数也是相同的。因此,为了使我们的随机数看起来更为“随机”一些,我们在每次获取随机数之前都需要初始化一个不同的随机数种子,这就需要用到qsrand()函数,该函数可以为qrand()函数设置一个不同的初值。
qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
int rand = qrand() % 30; //产生30以内的正整数
这里使用QTime类的secsTo()函数,它表示两个时间点之间所包含的秒数,比如代码中就是指从零点整到当前时间所经历的秒数。当使用qrand()要获得一个范围内的数据时,一般是让它与一个整数取余,这里是30,所有生成的数值在0~29之间(包含0和29)。
3.1 事件过滤器与事件的发送
Qt中提供了事件过滤器来实现在一个部件中监控其他多个部件的事件。事件过滤器与其他部件不同,它不是一个类,只是由两个函数组成的一种操作,用来完成一个部件对其他部件的事件的监控。这两个函数分别是installEvenFilter()和eventFilter(),都是QObject类中的函数。
新建Qt Widget应用,将项目名称改成myeventfilter,基类选择QWidget,类名保存Widget不变。完成后向设计模式中向界面上拖入一个Text Edit和一个Spin Box。在widget.h文件中添加public函数声明:
bool eventFilter(QObject *obj, QEvent *event);
然后再widget.cpp文件中添加头文件:
#include <QKeyEvent>
#include <QWheelEvent>
在构造函数中添加代码:
ui->textEdit->installEventFilter(this);//为编辑部件在本窗口安装事件过滤器
ui->spinBox->installEventFilter(this);
要对一个部件使用事件过滤器,那么就要先使用其的installEventFilter()函数为其安装事件过滤器,其参数this表明要在本部件(即Widget)中监视textEdit和spinBox的事件。这样就要重新实现Widget类的eventFilter()函数,在其中截获并处理两个子部件的事件。
bool Widget::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->textEdit) //判断部件
{
if (event->type() == QEvent::Wheel) //判断事件
{
//将event强制转化为发送的事件的类型
QWheelEvent *wheelEvent = static_cast<QWheelEvent*>(event);
//QWheelEvent是指用户滚动鼠标滚轮或类似输入设备时触发的事件。
if (wheelEvent->delta() > 0)
{
//zoom-in是将视距缩小,相当于离开事物近一点距离去观察事物,那么看到的图片就会放大
ui->textEdit->zoomIn();
}
else
{
//zoom-out就是视距缩大,看到的图片就会缩小
ui->textEdit->zoomOut();
}
return true; //事件已经处理
}
else
{
return false; //如果是其他事件,可以进一步进行进一步的处理
}
}
else if (obj == ui->spinBox)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyevent = static_cast<QKeyEvent*>(event);
if (keyevent->key() == Qt::Key_Space)
{
ui->spinBox->setValue(0);
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
else
{
return QWidget::eventFilter(obj, event);
}
}
在这个事件过滤器中先判断部件的类型,然后再判断事件的类型,如果是需要的事件,那么就将其进行强制类型转换,然后进行相应的处理。如果要对一个特定的事件进行处理,而且不希望它在后面的传递过程中再被处理,那么就返回true,否则返回false。这个函数实现了textEdit部件中使用滚轮进行内容的放大或缩小。
Qt中提供了发送一个事件的功能,它由QCoreApplication类的sendEvent函数和postEvent函数来实现
bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
这两函数的区别是:
sendEvent()会立刻去处理指定的事件,而postEvent()则会将事件放到等待调度队列中,下一次Qt的主事件循环运行时才会处理它。除此之外,sendEvent对象参数在发送完成后无法自动删除,所以需要在栈上创建QEvent对象;而postEvent()中的QEvent对象参数必须在堆上进行创建(例如用new),当事件被发送后事件队列会自动删除它。