Qt事件系统

一、Qt事件简介:
1、 事件是对应用程序内部或者外部产生的事情或者动作的通称。

2、 Qt程序是事件驱动的,Qt事件的产生和处理是程序运行的主线,存在于程序的整个生命周期。

3、 Qt事件被封装为对象,所有的Qt事件均继承自抽象类QEvent。

4、 常见的事件如下:
(1)键盘事件: QKeyEvent
(2)鼠标事件: QMouseEvent
(3)拖拽事件: QDragEnterEvent
(4)滚轮事件: QWheelEvent
(5)绘制事件:QPaintEvent
(6)定时器事件: QTimerEvent
(7)焦点事件:QFocusEvent
(8)关闭事件:QCloseEvent
(9)窗口大小位置引起的事件:QResizeEvent
(10)上下文菜单事件:QContextMenuEvent

5、 一个事件可能包含多种事件类型,这些事件类型统一由QEvent::Type枚举类型表示,通过QEvent类的type()可获得具体的事件类型。如鼠标事件QMouseEvent又分为鼠标按下、鼠标释放、鼠标移动、鼠标双击事件类型。

6、 QEvent子类只能表示事件,但不能处理事件,在Qt中,任何QObject子类实例都可以接收和处理事件。(QObject三大职责: 内存管理、事件处理、内省(intropection))

7、 可在Qt帮助文档中搜索The Event System关键字查看。

二、Qt事件的产生和发送:
1、 事件可能由应用程序外部产生,即操作系统事件; 也可能由应用程序内部产生。

2、 由应用程序外部产生:
(1)、一般用户操作,如鼠标按下、键盘按键等,操作系统(的设备驱动程序)感知,向应用程序发送系统消息。
(2)、Qt的事件循环(非GUI程序的QCoreApplication或GUI程序的QApplication的exec()),监听到事件后生成一个事件对象(QEvent子类对象),放入到事件循环队列中。
(3)、Qt(QCoreApplication或QApplication)从事件循环队列中,取出一个事件对象,通过层层传递,最终会发送给目标QObject对象(一般是获得焦点的QWidget)。
(4)、如果目标QObject对象有自定义安装的事件过滤器,可能会在自定义事件过滤器中处理掉事件;如果没有自定义事件过滤器,则事件会到达目标QObject的event(QEvent *)函数。
(5)、event(QEvent *)函数会根据事件类型(QEvent的type())调用不同的事件处理函数。
(6)、在事件处理函数中发送预定义的信号,从而调用与信号关联的槽函数。

3、 由应用程序内部产生:
(1)、应用程序自己产生事件,有两种方式。
一种是调用QCoreApplication的postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority);
另一种是调用QCoreApplication的sendEvent(QObject *receiver, QEvent *event)。
(2)、postEvent和sendEvent区别:
a、sendEvent会使用notify()函数直接给receiver发送事件; postEvent会向事件队列中添加receiver和event。
b、sendEvent使用同步处理事件(阻塞式的),postEvent使用异步处理事件(非阻塞式的)。
c、sendEvent支持栈/堆空间的事件对象,postEvent只支持堆空间的事件对象。(堆空间事件对象被处理后,会由Qt内部自动摧毁)

4、 事件是发生了某个事情或者某个动作产生的,而信号是由某个对象发射的。
比如单击鼠标这个动作产生了QMouseEvent事件; 而因为单击鼠标时,鼠标的光标正好在按钮上,按钮被按下了,所以按钮会发射clicked()信号。

5、 QApplication是继承自QCoreApplication的,QCoreApplication中的函数QApplication一般都会拥有;
QCoreApplication是非GUI程序,而QApplication是GUI程序,一般QApplication用的多,以下都用QApplication举例。

三、Qt事件的传递和处理:
1、 事件传递是从QApplication开始,层层往下最终到达目标QObject对象(一般是获得焦点的QWidget);如果目标QObject对象处理不了,事件又被层层传递给父Widget。

2、 在传递过程中,事件可以被处理,就对应着不同级别的事件处理方法,有5种不同级别的处理方法,从上往下如下:
(1)、继承QApplication,重写notify()。(bool QCoreApplication::notify(QObject *receiver, QEvent *event))
a、Qt调用notify()来发送事件。
b、重写notify()是可以得到所有事件的方法。
c、在其它事件过滤器之前得到事件。
d、但notify()一次只能处理一个事件。
(2)、给QApplication对象安装事件过滤器。
a、一个程序中,QApplication对象只有一个。
b、如果QApplication对象安装了事件过滤器,那么程序中所有对象的事件都要送到eventFilter()函数中。
c、可以同时处理多个事件。
(3)、在目标QObject对象上安装事件过滤器(详情看四)。
a、目标QObject对象调用installEventFilter()函数,目的是在本对象上安装一个事件监控(过滤)对象。(void QObject::installEventFilter(QObject *filterObj))
b、在事件监控(过滤)对象的eventFilter()函数中处理目标对象的事件。(bool QObject::eventFilter(QObject *watched, QEvent *event))
c、事件在到达目标QObject对象时,优先会调用事件监控(过滤)对象的eventFilter()函数。(本质是事件过滤对象的指针被保存在目标QObject对象中,并且eventFilter()函数是公有的可以被调用。)
d、可以在一个界面类中处理不同子部件的不同事件。
(4)、重写目标QObject对象的event(QEvent *)函数。
a、bool QObject::event(QEvent *e)函数会在事件过滤器之后,默认事件处理函数之前调用。
b、event()函数会根据事件类型,将事件分发给不同事件处理函数。
c、event()可以处理那些没有特定事件处理函数的少见的事件。
(5)、重写目标QObject对象的事件处理函数如paintEvent()、mousePressEvent()。
a、最常用但是只能处理特定事件。

3、 2中的(5)、(4)、(3)最常用,但是要注意函数返回值或事件状态的意义,涉及到事件继续往下或往上传递的过程。

四、事件过滤器:
0、 前提:
(1)、名词"组件"、“部件”、“控件”、"QWidget"是一个意思,最顶层父类都是QObject。
(2)、只要是QObject对象,就可以实现监控和被监控。
(3)、注意父类和父组件的区别: 父类表示子类继承的父类; 父组件表示子组件的parent组件。

1、 事件过滤器可以对组件接收到的事件进行监控和过滤。

2、 组件要想被监控,需要调用void QObject::installEventFilter(QObject * filterObj),来安装事件监控(过滤)对象。(本质会将事件监控(过滤)对象的指针存储在组件成员变量中)

3、 事件监控(过滤)对象(一般是父组件)需要重写public函数bool QObject::eventFilter(QObject * watched, QEvent * event),此函数即为事件过滤器。

4、 事件到达组件时,组件都会先调用事件监控(过滤)对象的eventFilter()函数。

5、 eventFilter()函数返回值:
(1) return true; 表明该事件已被处理,不会再将该事件交给组件的其它事件过滤器或event()函数。
(2) return false; 表明该事件需要进一步处理,会将该事件交给组件的其它事件过滤器或event()函数。
(3) return 父类::eventFilter(obj, event); 注意父类是事件监控(过滤)对象的父类。表明不太关注此事件,将此事件交给父类默认的事件过滤器处理,不知返回true还是false。

6、 一个组件可以安装多个事件监控(过滤)对象,会按照安装顺序调用相应的事件过滤器。

7、 举例:

   //Widget.h
   #pragma once

   #include <QWidget>
   namespace Ui { class Widget; };

   class Widget : public QWidget
   {
    Q_OBJECT

   public:
    Widget(QWidget *parent = Q_NULLPTR);
    ~Widget();

    bool eventFilter(QObject * obj, QEvent * event) override;

   private:
     Ui::Widget *ui;
   };
   
   //Widget.cpp
   #include "Widget.h"
   #include "ui_Widget.h"
   #include <QKeyEvent>
   #include <QWheelEvent>


   Widget::Widget(QWidget *parent) : QWidget(parent)
   {
    ui = new Ui::Widget();
    ui->setupUi(this);

    ui->textEdit->installEventFilter(this);   //组件安装事件监控(过滤)对象
    ui->spinBox->installEventFilter(this);    //组件安装事件监控(过滤)对象
   }

   Widget::~Widget()
   {
    delete ui;
   }

   bool Widget::eventFilter(QObject * obj, QEvent * event)  //一个事件过滤器可以监控多个组件的事件
   {
    if (obj == ui->textEdit)
    {
     if (event->type() == QEvent::Wheel)
     {
      QWheelEvent * wheelEvent = dynamic_cast<QWheelEvent *>(event);
      if (wheelEvent->delta() > 0)
      {
       ui->textEdit->zoomIn();
      }
      else
      {
       ui->textEdit->zoomOut();
      }
      return true;   //该事件已被处理,不会再继续传递给组件的其它事件过滤器或者event()函数
     }
     else
     {
      return false;   //其它事件类型则需要进一步处理,会继续传递给组件的其它事件过滤器或event()函数
     }
    }
    else if (obj == ui->spinBox)
    {
     if (event->type() == QEvent::KeyPress)
     {
      QKeyEvent * keyEvent = dynamic_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);
    }
   }

8、 事件过滤器的好处: 假设一个父控件上有很多不同类型的子控件,就可以不用重写全部子控件的事件处理函数了,只要将子控件都安装事件监控(过滤)对象(父控件),就可以统一在事件过滤器中处理各个子控件的事件了。

五、event():
1、 bool QObject::event(QEvent *e);

2、 事件经过目标QObject对象安装的事件过滤器后,会被传递给目标QObject对象的event()函数。

3、 默认的event()函数并不直接处理事件,主要用于事件的分发。根据事件对象的类型(调用QEvent的type()),分发给不同的事件处理器(event handler)。

4、 如果想要在事件分发之前做一些操作,我们需要重写event()函数。

5、 在event()函数中,事件的传递:
a、返回true,表明事件处理完毕,该事件不会再被传递。
b、返回false,表明事件会传递给父组件处理。
c、调用父类的event(),默认处理,返回结果不确定,可能会先传递给事件处理函数,然后返回。(常用)

6、 注意,在event()函数中,调用事件对象的accept()和ignore()没有作用,不会影响到事件的传播。

7、 可以调用isAccepted()来判断事件的状态。

六、事件处理函数:
1、 所有的事件处理函数,都是protected权限,并且是virtual函数,可供子类调用或重写。

2、 所有的事件处理函数都没有返回值。

3、 一般会在事件处理函数中,发送(emit)预定义的信号,调用与信号关联的槽函数。

4、 在事件处理函数中,事件的传递:
a、事件调用accept()返回,表明组件已经接受并处理该事件,事件不会再被传递(默认)。
b、事件调用ignore()返回,表明组件忽略该事件,该事件会被传递给父组件(parent)。
c、调用父类的默认事件处理函数返回,结果是默认处理,不确定具体状态。

5、 举例:

//MyLineEdit.h
#pragma once

#include <QLineEdit>

class MyLineEdit : public QLineEdit
{
 Q_OBJECT

public:
 explicit MyLineEdit(QWidget * parent = nullptr);

protected:
 void keyPressEvent(QKeyEvent * event);
};


//MyLineEdit.cpp
#include "MyLineEdit.h"
#include <QDebug>
#include <QKeyEvent>

MyLineEdit::MyLineEdit(QWidget * parent) : QLineEdit(parent)
{
}

void MyLineEdit::keyPressEvent(QKeyEvent * event)
{
 qDebug() << tr("MyLineEdit键盘按下事件");

 //执行父类QLineEidt的默认事件处理。(为了让行编辑器中能够输入字符, 否则行编辑器中无法正常输入字符。)
 //所以执行完父类QLineEidt的默认事件处理后, event是什么状态并不太确定, 可能是accept、可能是ignore、可能发送过信号。
 /*QLineEdit::keyPressEvent(event);*/

 //emit 信号

 //表明行编辑器忽略此事件, 此事件会传递给父组件(又是从父组件的事件过滤器开始)。
 //所以在行编辑器中每按下一个key,都会出现"MyLineEdit键盘按下事件"和"Widget键盘按下事件"。
 /*event->ignore();*/

 //表明行编辑器接受并处理此事件,此事件不会再被传递。(默认)
 //所以在行编辑器中每按下一个key,只会出现"MyLineEdit键盘按下事件"。
 /*event->accept();*/
}


//Widget.h
#include "ui_Widget.h"

class MyLineEdit;

class Widget : public QWidget
{
 Q_OBJECT

public:
 Widget(QWidget *parent = Q_NULLPTR);
 ~Widget();

protected:
 void keyPressEvent(QKeyEvent *event);

private:
 Ui::Widget ui;

 MyLineEdit * lineEdit;
};


//Widget.cpp
#include "Widget.h"
#include <QDebug>
#include "MyLineEdit.h"

Widget::Widget(QWidget *parent) : QWidget(parent)
{
 ui.setupUi(this);

 lineEdit = new MyLineEdit(this);
 lineEdit->move(100, 100);
}

Widget::~Widget()
{
}

void Widget::keyPressEvent(QKeyEvent * event)
{
 qDebug() << tr("Widget键盘按下事件");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小马兰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值