Loading

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),当事件被发送后事件队列会自动删除它。

posted @ 2021-03-04 21:41  小森林呐  阅读(562)  评论(0编辑  收藏  举报