Qt的事件循环机制

原文:https://blog.csdn.net/simonyucsdy/article/details/82749539

问题1:Qt中常见的事件有哪些?

答:鼠标事件(QMouseEvent)、键盘事件(QKeyEvent)、绘制事件(QPaintEvent)、窗口尺寸改变(QResizeEvent)、滚动事件(QScrollEvent)、控件显示(QShowEvent)、控件隐藏(QHideEvent)、定时器事件(QTimerEvent)等等。。

问题2:Qt是事件驱动的,这句话该怎么理解呢?

Qt将系统产生的信号(软件中断)转换成Qt事件,并且将事件封装成类,所有的事件类都是由QEvent派生的,事件的产生和处理就是Qt程序的主轴,且伴随着整个程序的运行周期。因此我们说,Qt是事件驱动的。

问题3:Qt事件是由谁产生的?Qt是如何将信号转换成事件的?

答:Qt的官方手册说,事件有两个来源:程序外部和程序内部,多数情况下来自操作系统并且通过spontaneous()函数返回true来获知事件来自于程序外部,当spontaneous()返回false时说明事件来自于程序内部,就像例程1创建一个事件并把它分发出去。

问题4:Qt事件是由谁接收的?

答:QObject!它是所有Qt类的基类!是Qt对象模型的核心!QObject类的三大核心功能其中之一就是:事件处理。QObject通过event()函数调用获取事件。所有的需要处理事件的类都必须继承自Qobject,可以通过重定义event()函数实现自定义事件处理或者将事件交给父类。

问题5:事件处理的流程是什么样的?

答:事件有别于信号的重要一点:事件是一个类对象具有特定的类型,事件多数情况下是被分发到一个队列中(事件队列),当队列中有事件时就不停的将队列中的事件发送给QObject对象,当队列为空时就阻塞地等待事件,这个过程就是事件循环

QCoreApplication::exec()开启了这种循环,一直到QCoreApplication::exit()被调用才终止,所以说事件循环是伴随着Qt程序的整个运行周期!

另外一种同步处理情形是通过sendEvent()将事件发送出去,直接进入事件的传送和处理流程。

事件处理流程如图所示:

img

例程1:sendEvent同步事件分发

/*!
 * \brief Widget::Widget 使用sendEvent同步分发事件
 * 使用QPushButton模拟键盘的回删和向前删除按键
 * \param parent
 */
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::on_button_back_clicked()
{
    int key = Qt::Key_Backspace; //
 
    QKeyEvent EventPress(QEvent::KeyPress, key, Qt::NoModifier);
    QApplication::sendEvent(ui->textEdit, &EventPress);
 
    QKeyEvent EventRelease(QEvent::KeyRelease, key, Qt::NoModifier);
    QApplication::sendEvent(ui->textEdit, &EventRelease);
}
 
void Widget::on_button_delete_clicked()
{
    int  key = Qt::Key_Delete; //
 
    QKeyEvent EventPress(QEvent::KeyPress, key, Qt::NoModifier);
    QApplication::sendEvent(ui->textEdit, &EventPress);
 
    QKeyEvent EventRelease(QEvent::KeyRelease, key, Qt::NoModifier);
    QApplication::sendEvent(ui->textEdit, &EventRelease);
}

postEvent和sendEvent的关系就像Qt::QueuedConnection和Qt::DirectConnection的关系,只不过前两者是分发事件后两者是发送消息罢了,机制上postEvent和QueuedConnected是异步通信,而另外两种是同步通信。

img

例程2:postEvent异步事件分发

int count = 0;
 
/*!
 * \brief Widget::Widget 使用postEvent异步分发事件
 * 连续分发10个事件,在事件处理函数中逐个处理
 * \param parent
 */
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
 
    int i = 1;
    while(i <= 10)
    {
        //postEvent传递的事件必须是通过new创建的
        qDebug() << "分发第" << i << "个事件";
        QApplication::postEvent(this, new QEvent(NewType));
        i++;
    }
}
 
void Widget::customEvent(QEvent *event)
{
    //使用延时模拟处理过程
    if(event->type() == NewType)
    {
        qDebug() << "现在时间:" <<
                    QTime::currentTime().toString("hh::mm:ss.zzz");
        qDebug() << "第" << ++count << "次收到了事件!处理事件需要一点时间!";
        Delay(1000*2);
    }
 
    QWidget::customEvent(event);
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::Delay(unsigned int msec)
{
    QTime start = QTime::currentTime();
 
    QTime end;
    do{
        end = QTime::currentTime();
    } while(start.msecsTo(end) <= msec);
}

问题6:事件过滤器机制?

事件的传送和处理流程的第一站是事件过滤器eventFilter(),某个对象A可以通过给另一个对象B安装事件处理器,实现对对象B事件的监听或者拦截功能。我们可以给A取名监听器,B取名接收器。一个对象可以监听多个对象,一个对象也可以被多个事件监听。事件过滤器返回true则表示事件已经处理完毕,否则传递给下一个监听器或者接收器本身。

img

例程3:事件过滤器

/*!
 * \brief Widget::Widget 事件过滤器
 * 不借助Tab键的情况下使用Space键实现控件跳转
 * \param parent
 */
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
 
    ui->lineEdit_user->setText(QString("lee"));
    focusNextChild();
    ui->lineEdit_password->setText(QString("*******"));
 
    //监听控件
    ui->lineEdit_user->installEventFilter(this);
    ui->lineEdit_password->installEventFilter(this);
    ui->button_accept->installEventFilter(this);
    ui->button_cancel->installEventFilter(this);
}
 
bool Widget::eventFilter(QObject *watched, QEvent *event)
{
    //定义事件处理动作
    if (watched == ui->lineEdit_user || watched == ui->lineEdit_password
        || watched == ui->button_accept || watched == ui->button_cancel)
    {
        if (event->type() == QEvent::KeyPress)
        {
            QKeyEvent *e = static_cast<QKeyEvent *>(event);
            if(e->key() == Qt::Key_Space)
            {
                focusNextChild();
                return true;
            }
        }
    }
    return QWidget::eventFilter(watched, event);
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::on_button_cancel_clicked()
{
    qApp->quit();
}

*值得注意的一点是QCoreApplication虽然负责事件分发,但本身也是继承自QObject的,所以在分发事件之前,也要检查自身是否被别的对象安装了事件过滤器,事件过滤器可能会过滤掉一些事件不发布。*

例程4:QCoreApplication安装事件过滤器

widget.cpp

/*!
 * \brief Filter::eventFilter 用于监听qApp的监听器
 * \return
 */
bool Filter::eventFilter(QObject *obj, QEvent *event)
{
    //阻止所有的鼠标点击事件
    if(event->type() == QEvent::MouseButtonPress)
    {
        qDebug() << "sorry everybody, I gonna filter all the mouse event!";
        return true;
    }
    return QObject::eventFilter(obj,event);
}
 
/*!
 * \brief Widget::Widget
 * \param parent
 */
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::mousePressEvent(QMouseEvent *event)
{
    qDebug() << "mouse press!";
 
    QWidget::mousePressEvent(event);
}

main.c

#include "widget.h"
#include <QApplication>
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
 
    Filter filter;
    a.installEventFilter(&filter);
 
    Widget w;
    w.show();
 
    return a.exec();
}
————————————————
版权声明:本文为CSDN博主「ingy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/simonyucsdy/article/details/82749539

也可以通过重新实现QCoreApplication的notify(),自定义对事件的处理动作。

例程5:QCoreApplication子类化并重写notify

newapplication.h

#ifndef NEWAPPLICATION_H
#define NEWAPPLICATION_H
 
#include <QApplication>
 
class NewApplication : public QApplication
{
public:
    NewApplication(int argc, char **argv) : QApplication(argc,argv) {}
 
    virtual bool notify(QObject *, QEvent *);
 
};
 
#endif // NEWAPPLICATION_H
————————————————
版权声明:本文为CSDN博主「ingy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/simonyucsdy/article/details/82749539

newapplication.cpp

#include "newapplication.h"
#include <QMouseEvent>
#include <QDebug>
 
bool NewApplication::notify(QObject *receiver, QEvent *event)
{
    if(event->type() == QMouseEvent::MouseButtonPress)
    {
        qDebug() << "sorry everybody I gonna filter all the mouse press event";
        return true;
    }
 
    return QApplication::notify(receiver,event);
}
————————————————
版权声明:本文为CSDN博主「ingy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/simonyucsdy/article/details/82749539

widget.h

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
#include <QMouseEvent>
 
namespace Ui {
class Widget;
}
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
 
protected:
    void mousePressEvent(QMouseEvent *event);
 
private slots:
    void on_pushButton_clicked();
 
private:
    Ui::Widget *ui;
};
 
#endif // WIDGET_H
————————————————
版权声明:本文为CSDN博主「ingy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/simonyucsdy/article/details/82749539

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
 
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::mousePressEvent(QMouseEvent *event)
{
    qDebug() << "I am mainwindow Widget, I got a mouse event!";
 
    QWidget::mousePressEvent(event);
}
 
void Widget::on_pushButton_clicked()
{
    qDebug() << "I am push button , I got a mouse event!";
}
————————————————
版权声明:本文为CSDN博主「ingy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/simonyucsdy/article/details/82749539

main.cpp

#include "widget.h"
#include <QApplication>
#include "newapplication.h"
 
int main(int argc, char *argv[])
{
//    QApplication a(argc, argv);
    NewApplication a(argc, argv);
 
    Widget w;
    w.show();
 
    return a.exec();
}
————————————————
版权声明:本文为CSDN博主「ingy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/simonyucsdy/article/details/82749539

运行结果:点击界面的任意位置,事件都被qApp过滤。

img

小结:事件处理的方式

1.重新实现对象的特定事件处理函数,例如mousePressEvent、keyPressEvent 、showEvent等,处理完毕后将事件交给父类;

2.重新实现event函数,处理完毕后将事件交给父类;

3.在对象上安装事件过滤器,让其他对象控制此对象的事件行为;

4.给主程序QCoreApplication安装事件过滤器,在调用notify进行事件分发之前,会根据过滤器判断对事件的处理(例如:丢弃);

5.子类化QCoreApplication,重新实现notify事件分发函数;

问题7.怎么使用自定义事件?

情景:自定义事件对于特定的操作是很有用的,定义一种连续点击10次鼠标的事件NewMouseEvent,连续点击10次屏幕唤醒屏幕校准程序。

自定义事件

newmouseevent.h

#ifndef MYEVENT_H
#define MYEVENT_H
 
#include <QEvent>
#include <QString>
 
class NewMouseEvent : public QEvent
{
public:
    explicit NewMouseEvent() :  QEvent(MouseTenClick) {}
    const static Type MouseTenClick = static_cast<Type>(QEvent::User+0x10);
};
 
#endif // MYEVENT_H
————————————————
版权声明:本文为CSDN博主「ingy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/simonyucsdy/article/details/82749539

widget.h

#ifndef MYEVENT_H
#define MYEVENT_H
 
#include <QEvent>
#include <QString>
 
class NewMouseEvent : public QEvent
{
public:
    explicit NewMouseEvent() :  QEvent(MouseTenClick) {}
    const static Type MouseTenClick = static_cast<Type>(QEvent::User+0x10);
};
 
#endif // MYEVENT_H
————————————————
版权声明:本文为CSDN博主「ingy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/simonyucsdy/article/details/82749539

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include "newmouseevent.h"
#include <QMouseEvent>
#include <QTimer>
 
/*!
 * \brief Widget::Widget
 * 创建并分发一种新的事件:鼠标连续点击10次
 * \param parent
 */
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
 
    ui->label->installEventFilter(this);
 
    ui->label->setText(tr("请连续点击屏幕以唤醒屏幕校准功能!"));
    ui->label->adjustSize();
 
    m_timer = new QTimer;
    m_timer->setInterval(1000);
    m_timer->start();
    connect(m_timer, SIGNAL(timeout()), SLOT(clearCount()));
 
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::mousePressEvent(QMouseEvent *event)
{
    QWidget::mousePressEvent(event);
}
 
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->button() != Qt::LeftButton)
        return;
 
    if(m_timer->isActive())
        m_timer->stop(); //如果计时器在运行,则停止然后重新开始
    m_timer->start();
 
    count++;
 
    if(10 == count)
    {
        count = 0;
 
        NewMouseEvent event;
        qApp->sendEvent(ui->label, &event);
    }
 
    QWidget::mouseReleaseEvent(event);
}
 
bool Widget::eventFilter(QObject *obj, QEvent *event)
{
    if(obj == ui->label && event->type()== NewMouseEvent::MouseTenClick)
    {
        ui->label->setText(tr("你连续点击了10次屏幕,校准程序正在启动!"));
        ui->label->adjustSize();
        return true;
    }
 
    return QWidget::eventFilter(obj,event);
}
 
void Widget::clearCount()
{
    count = 0;
}
————————————————
版权声明:本文为CSDN博主「ingy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/simonyucsdy/article/details/82749539

运行结果

连续点击10次鼠标算一次自定义事件

img

img

问题8:接受者对象中途被删除会发生什么?被监听者被删除会怎么样?

发送失败?程序崩溃?

widget.h

#ifndef WIDGET_H
#define WIDGET_H
 
#include <QWidget>
#include <QLabel>
 
namespace Ui {
class Widget;
}
 
class Widget : public QWidget
{
    Q_OBJECT
 
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
 
protected:
 
private slots:
    void slotSendEvent();
    void deleteLabel();
 
private:
    Ui::Widget *ui;
 
    QLabel *m_label;
 
};
 
#endif // WIDGET_H
————————————————
版权声明:本文为CSDN博主「ingy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/simonyucsdy/article/details/82749539

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
#include <QResizeEvent>
#include <QDebug>
 
/*!
 * \brief Widget::Widget
 * 在事件循环分发事件给接收者之前,接收者被删除
 * \param parent
 */
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
 
    //创建小窗口
    m_label = new QLabel(this);
    m_label->setStyleSheet(QString("border:1px solid red;"));
    m_label->setGeometry(QRect(0,0,200,100));
 
    //在qApp发送事件之前销毁小窗口
    QTimer::singleShot(1000, this, SLOT(deleteLabel()));
    //qApp发送事件给小窗口
    QTimer::singleShot(2000, this, SLOT(slotSendEvent()));
 
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::slotSendEvent()
{
    QResizeEvent re(QSize(300,200), QSize(200,100));
 
    qDebug() << "qApp发送了一个事件给小窗口!";
    qApp->sendEvent(m_label, &re);
}
 
void Widget::deleteLabel()
{
    qDebug() << "小窗口被销毁了!";
    delete m_label;
    m_label = NULL;
}
————————————————
版权声明:本文为CSDN博主「ingy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/simonyucsdy/article/details/82749539

运行结果

img

猜你喜欢

转载自www.cnblogs.com/schips/p/12536411.html