Qt的事件循环机制

问题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()将事件发送出去,直接进入事件的传送和处理流程。

事件处理流程如图所示:

例程1:sendEvent同步事件分发


 
 
  1. /*!
  2. * \brief Widget::Widget 使用sendEvent同步分发事件
  3. * 使用QPushButton模拟键盘的回删和向前删除按键
  4. * \param parent
  5. */
  6. Widget::Widget(QWidget *parent) :
  7. QWidget(parent),
  8. ui( new Ui::Widget)
  9. {
  10. ui->setupUi( this);
  11. }
  12. Widget::~Widget()
  13. {
  14. delete ui;
  15. }
  16. void Widget::on_button_back_clicked()
  17. {
  18. int key = Qt::Key_Backspace; //
  19. QKeyEvent EventPress(QEvent::KeyPress, key, Qt::NoModifier);
  20. QApplication::sendEvent(ui->textEdit, &EventPress);
  21. QKeyEvent EventRelease(QEvent::KeyRelease, key, Qt::NoModifier);
  22. QApplication::sendEvent(ui->textEdit, &EventRelease);
  23. }
  24. void Widget::on_button_delete_clicked()
  25. {
  26. int key = Qt::Key_Delete; //
  27. QKeyEvent EventPress(QEvent::KeyPress, key, Qt::NoModifier);
  28. QApplication::sendEvent(ui->textEdit, &EventPress);
  29. QKeyEvent EventRelease(QEvent::KeyRelease, key, Qt::NoModifier);
  30. QApplication::sendEvent(ui->textEdit, &EventRelease);
  31. }

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

例程2:postEvent异步事件分发


 
 
  1. int count = 0;
  2. /*!
  3. * \brief Widget::Widget 使用postEvent异步分发事件
  4. * 连续分发10个事件,在事件处理函数中逐个处理
  5. * \param parent
  6. */
  7. Widget::Widget(QWidget *parent) :
  8. QWidget(parent),
  9. ui( new Ui::Widget)
  10. {
  11. ui->setupUi( this);
  12. int i = 1;
  13. while(i <= 10)
  14. {
  15. //postEvent传递的事件必须是通过new创建的
  16. qDebug() << "分发第" << i << "个事件";
  17. QApplication::postEvent( this, new QEvent(NewType));
  18. i++;
  19. }
  20. }
  21. void Widget::customEvent(QEvent *event)
  22. {
  23. //使用延时模拟处理过程
  24. if(event->type() == NewType)
  25. {
  26. qDebug() << "现在时间:" <<
  27. QTime::currentTime().toString( "hh::mm:ss.zzz");
  28. qDebug() << "第" << ++count << "次收到了事件!处理事件需要一点时间!";
  29. Delay( 1000* 2);
  30. }
  31. QWidget::customEvent(event);
  32. }
  33. Widget::~Widget()
  34. {
  35. delete ui;
  36. }
  37. void Widget::Delay( unsigned int msec)
  38. {
  39. QTime start = QTime::currentTime();
  40. QTime end;
  41. do{
  42. end = QTime::currentTime();
  43. } while(start.msecsTo(end) <= msec);
  44. }

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

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

例程3:事件过滤器 


 
 
  1. /*!
  2. * \brief Widget::Widget 事件过滤器
  3. * 不借助Tab键的情况下使用Space键实现控件跳转
  4. * \param parent
  5. */
  6. Widget::Widget(QWidget *parent) :
  7. QWidget(parent),
  8. ui( new Ui::Widget)
  9. {
  10. ui->setupUi( this);
  11. ui->lineEdit_user->setText(QString( "lee"));
  12. focusNextChild();
  13. ui->lineEdit_password->setText(QString( "*******"));
  14. //监听控件
  15. ui->lineEdit_user->installEventFilter( this);
  16. ui->lineEdit_password->installEventFilter( this);
  17. ui->button_accept->installEventFilter( this);
  18. ui->button_cancel->installEventFilter( this);
  19. }
  20. bool Widget::eventFilter(QObject *watched, QEvent *event)
  21. {
  22. //定义事件处理动作
  23. if (watched == ui->lineEdit_user || watched == ui->lineEdit_password
  24. || watched == ui->button_accept || watched == ui->button_cancel)
  25. {
  26. if (event->type() == QEvent::KeyPress)
  27. {
  28. QKeyEvent *e = static_cast<QKeyEvent *>(event);
  29. if(e->key() == Qt::Key_Space)
  30. {
  31. focusNextChild();
  32. return true;
  33. }
  34. }
  35. }
  36. return QWidget::eventFilter(watched, event);
  37. }
  38. Widget::~Widget()
  39. {
  40. delete ui;
  41. }
  42. void Widget::on_button_cancel_clicked()
  43. {
  44. qApp->quit();
  45. }

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

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

widget.cpp 


 
 
  1. /*!
  2. * \brief Filter::eventFilter 用于监听qApp的监听器
  3. * \return
  4. */
  5. bool Filter::eventFilter(QObject *obj, QEvent *event)
  6. {
  7. //阻止所有的鼠标点击事件
  8. if(event->type() == QEvent::MouseButtonPress)
  9. {
  10. qDebug() << "sorry everybody, I gonna filter all the mouse event!";
  11. return true;
  12. }
  13. return QObject::eventFilter(obj,event);
  14. }
  15. /*!
  16. * \brief Widget::Widget
  17. * \param parent
  18. */
  19. Widget::Widget(QWidget *parent) :
  20. QWidget(parent),
  21. ui( new Ui::Widget)
  22. {
  23. ui->setupUi( this);
  24. }
  25. Widget::~Widget()
  26. {
  27. delete ui;
  28. }
  29. void Widget::mousePressEvent(QMouseEvent *event)
  30. {
  31. qDebug() << "mouse press!";
  32. QWidget::mousePressEvent(event);
  33. }

main.c


 
 
  1. #include "widget.h"
  2. #include <QApplication>
  3. int main(int argc, char *argv[])
  4. {
  5. QApplication a(argc, argv);
  6. Filter filter;
  7. a.installEventFilter(&filter);
  8. Widget w;
  9. w.show();
  10. return a.exec();
  11. }

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

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

 newapplication.h


 
 
  1. #ifndef NEWAPPLICATION_H
  2. #define NEWAPPLICATION_H
  3. #include <QApplication>
  4. class NewApplication : public QApplication
  5. {
  6. public:
  7. NewApplication( int argc, char **argv) : QApplication(argc,argv) {}
  8. virtual bool notify(QObject *, QEvent *);
  9. };
  10. #endif // NEWAPPLICATION_H

newapplication.cpp


 
 
  1. #include "newapplication.h"
  2. #include <QMouseEvent>
  3. #include <QDebug>
  4. bool NewApplication::notify(QObject *receiver, QEvent *event)
  5. {
  6. if(event->type() == QMouseEvent::MouseButtonPress)
  7. {
  8. qDebug() << "sorry everybody I gonna filter all the mouse press event";
  9. return true;
  10. }
  11. return QApplication::notify(receiver,event);
  12. }

widget.h 


 
 
  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3. #include <QWidget>
  4. #include <QMouseEvent>
  5. namespace Ui {
  6. class Widget;
  7. }
  8. class Widget : public QWidget
  9. {
  10. Q_OBJECT
  11. public:
  12. explicit Widget(QWidget *parent = 0);
  13. ~Widget();
  14. protected:
  15. void mousePressEvent(QMouseEvent *event);
  16. private slots:
  17. void on_pushButton_clicked();
  18. private:
  19. Ui::Widget *ui;
  20. };
  21. #endif // WIDGET_H

widget.cpp


 
 
  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. #include <QDebug>
  4. Widget::Widget(QWidget *parent) :
  5. QWidget(parent),
  6. ui( new Ui::Widget)
  7. {
  8. ui->setupUi( this);
  9. }
  10. Widget::~Widget()
  11. {
  12. delete ui;
  13. }
  14. void Widget::mousePressEvent(QMouseEvent *event)
  15. {
  16. qDebug() << "I am mainwindow Widget, I got a mouse event!";
  17. QWidget::mousePressEvent(event);
  18. }
  19. void Widget::on_pushButton_clicked()
  20. {
  21. qDebug() << "I am push button , I got a mouse event!";
  22. }

 main.cpp


 
 
  1. #include "widget.h"
  2. #include <QApplication>
  3. #include "newapplication.h"
  4. int main(int argc, char *argv[])
  5. {
  6. // QApplication a(argc, argv);
  7. NewApplication a(argc, argv);
  8. Widget w;
  9. w.show();
  10. return a.exec();
  11. }

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

小结:事件处理的方式

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

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

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

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

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

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

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

自定义事件

newmouseevent.h


 
 
  1. #ifndef MYEVENT_H
  2. #define MYEVENT_H
  3. #include <QEvent>
  4. #include <QString>
  5. class NewMouseEvent : public QEvent
  6. {
  7. public:
  8. explicit NewMouseEvent() : QEvent(MouseTenClick) {}
  9. const static Type MouseTenClick = static_cast<Type>(QEvent::User+ 0x10);
  10. };
  11. #endif // MYEVENT_H

widget.h


 
 
  1. #ifndef MYEVENT_H
  2. #define MYEVENT_H
  3. #include <QEvent>
  4. #include <QString>
  5. class NewMouseEvent : public QEvent
  6. {
  7. public:
  8. explicit NewMouseEvent() : QEvent(MouseTenClick) {}
  9. const static Type MouseTenClick = static_cast<Type>(QEvent::User+ 0x10);
  10. };
  11. #endif // MYEVENT_H

widget.cpp


 
 
  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. #include "newmouseevent.h"
  4. #include <QMouseEvent>
  5. #include <QTimer>
  6. /*!
  7. * \brief Widget::Widget
  8. * 创建并分发一种新的事件:鼠标连续点击10次
  9. * \param parent
  10. */
  11. Widget::Widget(QWidget *parent) :
  12. QWidget(parent),
  13. ui( new Ui::Widget)
  14. {
  15. ui->setupUi( this);
  16. ui->label->installEventFilter( this);
  17. ui->label->setText(tr( "请连续点击屏幕以唤醒屏幕校准功能!"));
  18. ui->label->adjustSize();
  19. m_timer = new QTimer;
  20. m_timer->setInterval( 1000);
  21. m_timer->start();
  22. connect(m_timer, SIGNAL(timeout()), SLOT(clearCount()));
  23. }
  24. Widget::~Widget()
  25. {
  26. delete ui;
  27. }
  28. void Widget::mousePressEvent(QMouseEvent *event)
  29. {
  30. QWidget::mousePressEvent(event);
  31. }
  32. void Widget::mouseReleaseEvent(QMouseEvent *event)
  33. {
  34. if(event->button() != Qt::LeftButton)
  35. return;
  36. if(m_timer->isActive())
  37. m_timer->stop(); //如果计时器在运行,则停止然后重新开始
  38. m_timer->start();
  39. count++;
  40. if( 10 == count)
  41. {
  42. count = 0;
  43. NewMouseEvent event;
  44. qApp->sendEvent(ui->label, &event);
  45. }
  46. QWidget::mouseReleaseEvent(event);
  47. }
  48. bool Widget::eventFilter(QObject *obj, QEvent *event)
  49. {
  50. if(obj == ui->label && event->type()== NewMouseEvent::MouseTenClick)
  51. {
  52. ui->label->setText(tr( "你连续点击了10次屏幕,校准程序正在启动!"));
  53. ui->label->adjustSize();
  54. return true;
  55. }
  56. return QWidget::eventFilter(obj,event);
  57. }
  58. void Widget::clearCount()
  59. {
  60. count = 0;
  61. }

运行结果

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

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

发送失败?程序崩溃? 

widget.h 


 
 
  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3. #include <QWidget>
  4. #include <QLabel>
  5. namespace Ui {
  6. class Widget;
  7. }
  8. class Widget : public QWidget
  9. {
  10. Q_OBJECT
  11. public:
  12. explicit Widget(QWidget *parent = 0);
  13. ~Widget();
  14. protected:
  15. private slots:
  16. void slotSendEvent();
  17. void deleteLabel();
  18. private:
  19. Ui::Widget *ui;
  20. QLabel *m_label;
  21. };
  22. #endif // WIDGET_H

widget.cpp


 
 
  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. #include <QTimer>
  4. #include <QResizeEvent>
  5. #include <QDebug>
  6. /*!
  7. * \brief Widget::Widget
  8. * 在事件循环分发事件给接收者之前,接收者被删除
  9. * \param parent
  10. */
  11. Widget::Widget(QWidget *parent) :
  12. QWidget(parent),
  13. ui( new Ui::Widget)
  14. {
  15. ui->setupUi( this);
  16. //创建小窗口
  17. m_label = new QLabel( this);
  18. m_label->setStyleSheet(QString( "border:1px solid red;"));
  19. m_label->setGeometry(QRect( 0, 0, 200, 100));
  20. //在qApp发送事件之前销毁小窗口
  21. QTimer::singleShot( 1000, this, SLOT(deleteLabel()));
  22. //qApp发送事件给小窗口
  23. QTimer::singleShot( 2000, this, SLOT(slotSendEvent()));
  24. }
  25. Widget::~Widget()
  26. {
  27. delete ui;
  28. }
  29. void Widget::slotSendEvent()
  30. {
  31. QResizeEvent re(QSize(300,200), QSize(200,100));
  32. qDebug() << "qApp发送了一个事件给小窗口!";
  33. qApp->sendEvent(m_label, &re);
  34. }
  35. void Widget::deleteLabel()
  36. {
  37. qDebug() << "小窗口被销毁了!";
  38. delete m_label;
  39. m_label = NULL;
  40. }

 运行结果

 

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值