Qt之事件处理机制

目录

一、事件简介

二、事件的处理

1.重写notify处理函数

2.事件过滤器

3.重写event处理函数

4.重写特定事件处理函数

三、事件的发送


一、事件简介

Qt 是一个基于 C++ 的框架,主要用来开发带窗口的应用程序。使用的基于窗口的应用程序都是基于事件,其目的主要是用来实现回调(因为只有这样程序的效率才是最高的)。所以在 Qt 框架内部提供了一些列的事件处理机制,当窗口事件产生之后,事件会经过:事件派发 -> 事件过滤->事件分发->事件处理四个阶段。Qt 窗口中对于产生的一系列事件都有默认的处理动作,如果有特殊需求就需要在合适的阶段重写事件的处理动作

事件(event)是由系统或者 Qt 本身在不同的场景下发出的。当用户按下 / 移动鼠标、敲下键盘,或者是窗口关闭 / 大小发生变化 / 隐藏或显示都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如鼠标 / 键盘事件等;另一些事件则是由系统自动发出,如计时器事件。

每一个 Qt 应用程序都对应一个唯一的 QApplication 应用程序对象,然后调用这个对象的 exec() 函数,这样 Qt 框架内部的事件检测就开始了(程序将进入事件循环来监听应用程序的事件)

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow* w = new MainWindow;
    w.show();
    return a.exec();
}

事件在 Qt 中产生后的分发过程:

Qt之事件处理机制_第1张图片

 1.当事件产生之后,Qt 使用用应用程序对象调用 notify() 函数将事件发送到指定的窗口:

[override virtual] bool QApplication::notify(QObject *receiver, QEvent *e);

2.事件在发送过程中可以通过事件过滤器进行过滤默认不对任何产生的事件进行过滤

// 需要先给窗口安装过滤器, 该事件才会触发
[virtual] bool QObject::eventFilter(QObject *watched, QEvent *event)

3.当事件发送到指定窗口之后,窗口的事件分发器会对收到的事件进行分类:

[override virtual protected] bool QWidget::event(QEvent *event);

 4.事件分发器会将分类之后的事件(鼠标事件、键盘事件、绘图事件...)分发给对应的事件处理器函数进行处理每个事件处理器函数都有默认的处理动作,也可以重写这些事件处理器函数

// 鼠标按下
[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event);
// 鼠标释放
[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event);
// 鼠标移动
[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event);

二、事件的处理

实际应用中事件处理的四层不会对其全部重写,而是根据实际情况选择合适位置重写事件处理的方法。

1.重写notify处理函数

程序中事件循环QApplication类中捕获事件的总接口notify函数

重定义方法:继承QApplication类,重写notify函数

//继承QApplication类
class MyApplication:public QApplication{
public:
    MyApplication(int argc,char *argv[]):QApplication(argc,argv)
    {

    }

    //重写notify
    virtual bool notify(QObject *reciever,QEvent *event)
    {
        //捕获关心的事件
        //鼠标点击按钮事件
        if(event->type()==QEvent::MouseButtonPress){
            qDebug()<<"4.1捕获到了QEvent::MouseButtonPress "<type()==QEvent::MouseMove){
            //qDebug()<<"4.2捕获到了QEvent::MouseMove";
        }

        //调用父类的事件处理函数
        return QApplication::notify(reciever,event);
    }
};

//还需要修改入口程序里面的Application
MyApplication a(argc, argv);

2.事件过滤器

除了使用事件分发器来过滤 Qt 窗口中产生的事件,还可以通过事件过滤器过滤相关的事件。当 Qt 的事件通过应用程序对象发送给相关窗口之后,窗口接收到数据之前这个期间可对事件进行过滤,过滤掉的事件就不能被继续处理了。QObject 有一个 eventFilter () 函数,用于建立事件过滤器。函数原型如下:

[virtual] bool QObject::eventFilter(QObject *watched, QEvent *event);

watched:要过滤的事件的所有者对象
event:要过滤的具体的事件
返回值:如果想过滤掉这个事件,停止它被进一步处理,返回 true,否则返回 false

过滤传递中的事件,主要分为两步:

给要被过滤事件的类对象安装事件过滤器

void QObject::installEventFilter(QObject *filterObj);

假设调用 installEventFilter() 函数的对象为当前对象,那么就可以基于参数指定的 filterObj 对象来过滤当前对象中的指定的事件了。

②在要进行事件过滤的类中(filterObj 参数对应的类)重写从QObject类继承的虚函数eventFilter()

在一个窗口中有一个多行文本输入框 QTextEdit,需要屏蔽掉键盘上的回车键。

三种解决方案:

1.自定义一个新的类让其继承 QTextEdit,在这个子类中重写键盘事件 keyPressEvent,在这个函数里边屏蔽掉回车键

2.自定义一个新的类让其继承 QTextEdit,在这个子类中重写事件分发器 event,在这个函数里边屏蔽掉回车键

3.给 QTextEdit 安装事件过滤器,基于 QTextEdit 的父窗口对这个控件的事件进行过滤

最简单的方式是第三种,因为不需要再定义出一个子类就可以完成控件事件的过滤。

ui->textEdit->installEventFilter(this); //安装过滤器,以本窗口为观察者对象
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
    // 判断对象和事件
    if(watched == ui->textEdit)  //判断对象
    {
        if(event->type() == QEvent::KeyPress) //判断事件
        {
            QKeyEvent* keyEv = (QKeyEvent*)event;
            if(keyEv->key() == Qt::Key_Enter ||         // 小键盘确认
                keyEv->key() == Qt::Key_Return)     // 大键盘回车
            {
                qDebug() << "我是回车, 被按下了...";
                return true;  //过滤事件
            }
        }
    }
    return false;
}

主窗口通过重写事件过滤器函数,对多行编辑框控件进行事件的过滤,还需要给多行编辑框控件安装了事件过滤器,由 this 对应的主窗口进行事件的过滤。通过这样的处理,事件在被应用程序对象发送出去之后,进入到对应的窗口之前就被其父窗口过滤掉了。

如果在 Qt 的窗口中有多层嵌套的窗口

Qt之事件处理机制_第2张图片

这四层窗口的关系:

顶层窗口 A 的直接子窗口是 B,间接子窗口是 C,QTextEdit
二级窗口 B 的直接子窗口是 C,间接子窗口是 QTextEdit
三级窗口 C 的直接子窗口是 QTextEdit

在这种多层嵌套窗口中如果想要过滤掉 QTextEdit 的某些事件可以交给 A 或者 B 或者 C 去处理,当然也可以给 QTextEdit 同时安装多个过滤器:

ui->textEdit->installEventFilter(窗口A对象);
ui->textEdit->installEventFilter(窗口B对象);
ui->textEdit->installEventFilter(窗口C对象);

如果一个对象存在多个事件过滤器,那么最后一个安装的会第一个执行,也就是说窗口C先进行事件过滤,然后窗口B,最后窗口A。 

注意:事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。

3.重写event处理函数

当事件产生被发送到对应的窗口之后,窗口并不会直接处理这个事件,而是对这些事件进行细分,然后根据事件的类型再次进行分发,对应的事件处理器函数得到这个分发的事件之后就开始处理这个事件。

关于窗口事件的分发,对应一个事件分发器,叫做 event

[override virtual protected] bool QWidget::event(QEvent *event);

通过事件分发器的函数原型可以得知,关于事件类型的判断是基于参数完成的,这个参数是一个 QEvent 类型的对象,这个类中常用的一些 API 函数:

void QEvent::accept();

作用:让窗口接受传递过来的事件,事件不会向上层窗口(父窗口)传递

void QEvent::ignore();

作用:让窗口忽略传递过来的事件,事件被传递给父窗口(向上传递)。

bool QEvent::isAccepted() const;
void QEvent::setAccepted(bool accepted);

作用:设置传递过来的事件是被接受还是被忽略
setAccepted(true) == accept()
setAccepted(false) == ignore()

QEvent::Type QEvent::type() const;

作用得到传递的窗口的事件的类型,返回值是一个枚举类型

Qt之事件处理机制_第3张图片

事件分发器的分发流程,

bool QWidget::event(QEvent *ev)
{
    switch(ev->type())
    {
    // 鼠标移动
    case QEvent::MouseMove:		
        mouseMoveEvent((QMouseEvent*)event);
        break;
    // 鼠标按下
    case QEvent::MouseButtonPress:	
        mousePressEvent((QMouseEvent*)event);
        break;
    // 鼠标释放
    case QEvent::MouseButtonRelease:	
        mouseReleaseEvent((QMouseEvent*)event);
        break;
    // 鼠标双击
    case QEvent::MouseButtonDblClick:	
        mouseDoubleClickEvent((QMouseEvent*)event);
        break;
    // 键盘按键被按下事件
    case QEvent::KeyPress:
        break;
        ...
        ...
        ...
    default:
        break;
    }
}

 事件分发器在对事件进行判定之后会调用相关的事件处理器函数,这样事件就被最终处理掉了。如果不想让某些触发的事件进入到当前窗口中,可以在事件分发器中进行拦截,拦截之前先来了解一下事件分发器函数的返回值:

①如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是true,那么 Qt 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象,而是会继续处理事件队列中的下一事件。

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

所有如果想过滤某个事件,只需要在判断出这个事件之后直接返回 true 就可以了 。

在窗口中过滤掉鼠标按下的事件:

bool MainWindow::event(QEvent *ev)
{
    if(ev->type() == QEvent::MouseButtonPress ||ev->type() == QEvent::MouseButtonDblClick)
    {
        // 过滤调用鼠标按下的事件
        return true;
    }
    return QWidget::event(ev);
}

这样窗口就不会收到鼠标的单击和双击事件,对于这两个事件以外的其他事件是没有任何影响的,因为在重写的事件分发器函数的最后调用了父类的事件分发器函数 return QWidget::event(ev);这样就能保证其他事件按照默认的分发流程进行分发,并最终被窗口处理掉。

也可以在捕获到某个事件后,对该事件重写

    //重写所有事件的事件处理函数
    virtual bool event(QEvent *evt)
    {
        //鼠标点击按钮事件
        if(evt->type()==QEvent::MouseButtonPress)
        {
            //新的处理动作....
            qDebug()<<"捕获到了QEvent::MouseButtonPress";
        }
        else if(evt->type()==QEvent::MouseMove)
        {
            //新的处理动作....
            //qDebug()<<"捕获到了QEvent::MouseMove";
        }

        //调用父类的事件处理函数
        return QPushButton::event(evt);
    }

4.重写特定事件处理函数

每个事件处理器函数都对应一个唯一的事件,这为重新定义事件的处理动作提供了便利。另外,Qt 提供的这些事件处理器函数都是回调函数,也就是说作为使用者只需要指定函数的处理动作,关于函数的调用是不需要操心的,当某个事件被触发,Qt 框架会调用对应的事件处理器函数

QWidget 类是 Qt 中所有窗口类的基类,在这个类里边定义了很多事件处理器函数,它们都是受保护的虚函数。可以在 Qt 的任意一个窗口类中重写这些虚函数来重定义它们的行为

常用事件:

①鼠标事件

鼠标按下事件:当鼠标左键、鼠标右键、鼠标中键被按下,该函数被自动调用,通过参数可以得到当前按下的是哪个鼠标键

[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event);

鼠标释放事件:当鼠标左键、鼠标右键、鼠标中键被释放,该函数被自动调用,通过参数可以得到当前释放的是哪个鼠标键

[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event);

鼠标移动事件:当鼠标移动,也可以按住一个或多个鼠标键移动,该函数被自动调用,通过参数可以得到在移动过程中哪些鼠标键被按下了

[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event);

鼠标双击事件:当鼠标双击该函数被调用,通过参数可以得到是通过哪个鼠标键进行了双击操作。
[virtual protected] void QWidget::mouseDoubleClickEvent(QMouseEvent *event);

鼠标进入事件:当鼠标进入窗口的一瞬间,触发该事件,注意:只在进入的瞬间触发一次该事件

[virtual protected] void QWidget::enterEvent(QEvent *event);

鼠标离开事件:当鼠标离开窗口的一瞬间,触发该事件,注意:只在离开的瞬间触发一次该事件

[virtual protected] void QWidget::leaveEvent(QEvent *event);

②键盘事件

键盘按下事件:当键盘上的按键被按下了,该函数被自动调用,通过参数可以得知按下的是哪个键

[virtual protected] void QWidget::keyPressEvent(QKeyEvent *event);

键盘释放事件:当键盘上的按键被释放了,该函数被自动调用,通过参数可以得知释放的是哪个键。

[virtual protected] void QWidget::keyReleaseEvent(QKeyEvent *event);

③窗口重绘事件

当窗口需要刷新的时候,该函数就会自动被调用。窗口需要刷新的情景很多,比如:窗口大小发生变化,窗口显示等,另外还可以通过该函数给窗口绘制背景图,总之这是一个需要经常被重写的一个事件处理器函数。 

[virtual protected] void QWidget::paintEvent(QPaintEvent *event);

④窗口关闭事件

当窗口标题栏的关闭按钮被按下并且在窗口关闭之前该函数被调用,可以通过该函数控制窗口是否被关闭。 

[virtual protected] void QWidget::closeEvent(QCloseEvent *event);

⑤重置窗口大小事件

当窗口的大小发生变化,该函数被调用。

[virtual protected] void QWidget::resizeEvent(QResizeEvent *event); 

 窗口处理函数特点

1.受保护的虚函数
2.函数名分为两部分: 事件描述+Event
3.函数带一个事件类型的参数

由于事件处理器函数都是虚函数,因此可以添加一个标准窗口类的派生类,这样不仅使子类继承了父类的属性,还可以在这个子类中重写父类的虚函数。在对象对应的类中重写特定事件的处理函数 即可。

     //重写特定事件(鼠标点击)的事件处理函数
    void MainWindow::mousePressEvent(QMouseEvent *event)
    {
        qDebug()<<"捕获到了mousePressEvent ";
        //新的处理动作
        .......
        //如果要保留父类的事件默认处理方式,可以调用父类对应的虚函数
        return MainWindow::mousePressEvent(event);
    }

    void MainWindow::mouseMoveEvent(QMouseEvent *event)
    {
          
        //新的处理动作
        .......

        //如果要保留父类的事件默认处理方式,可以调用父类对应的虚函数
        return MainWindow::mouseMoveEvent(event);
    }

注意这些虚函数需要在类中定义,然后再重写。

三、事件的发送

Qt除了预定义的类类型对象能够发送预定义事件以外,也可以手动通过代码发送事件,手动通过代码发送的函数定义在QCoreApplication类中。

[static] void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority);
[static] bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event);

//参数是给那个对象发送什么事件,第三个参数是优先级,可以不用设置

receiver:发送事件的对象

event:要发送的事件

使用方法  

1.创建事件

QEvent *e = new QEvent(...);//也可以继承自定义

2.发送事件

QCoreApplication::postEvent(发送的对象, e);

QCoreApplication::sendEvent(发送的对象, e);

区别:sendEvent直接发送,postEvent先进入事件处理队列 

Qt之事件处理机制_第4张图片首先QCoreApplication::exec()开启了事件循环,一直到QCoreApplication::exit()被调用才终止,所以说事件循环是伴随着Qt程序的整个运行周期,事件被分发到事件队列中,当队列中有事件时会不停的将事件发送给QObject对象,队列为空时就阻塞,以下为处理顺序。
 

sendEvent:使用notify()函数直接将事件发送给接收者,发送事件时不会删除该事件,通常是在栈上面创建事件,它是同步事件。

postEvent:将事件添加到事件队列中,并立即返回;事件必须在堆上分配,因为提交事件队列将获得事件的所有权,并在提交后删除它。在事件发布后访问该事件是不安全的,它是异步事件。

你可能感兴趣的:(QT,qt,开发语言)