第七章 Qt事件(event)处理

本文详细介绍了Qt中的事件处理,包括事件处理器的概念,如何安装和使用事件过滤器,以及在处理密集事件时如何保持界面响应。事件处理器用于响应用户的键盘、鼠标操作,而事件过滤器则提供了一种在事件分发前进行拦截和处理的机制。此外,还讨论了QApplication::processEvent()在避免长时间事件处理导致的界面无响应问题中的应用。

一、事件处理器    

    事件是由窗口系统或者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循环。


参考:点击打开链接


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值