Qt之事件处理机制

news/2024/5/17 14:41:52/文章来源:https://blog.csdn.net/qq_53144843/article/details/127144955

目录

一、事件简介

二、事件的处理

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 中产生后的分发过程:

 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){}//重写notifyvirtual bool notify(QObject *reciever,QEvent *event){//捕获关心的事件//鼠标点击按钮事件if(event->type()==QEvent::MouseButtonPress){qDebug()<<"4.1捕获到了QEvent::MouseButtonPress "<<reciever;}else if(event->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 && 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 的窗口中有多层嵌套的窗口

这四层窗口的关系:

顶层窗口 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;

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

事件分发器的分发流程,

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先进入事件处理队列 

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

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

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_206598.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网课查题公众号题库接口

网课查题公众号题库接口 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xf…

05-Elasticsearch-DSL高级检索[分页, 分词, 权重, 多条件, 过滤, 排序, 关键词高亮, 深度分页, 滚动搜索, 批量Mget]

DSL搜索 词库准备骚年 帅气 新闻网 新闻 闻网 新 闻 网索引准备PUT /shop {"settings": {"number_of_shards": 5,"number_of_replicas": 0} } POST /shop/_mapping {"properties": {"id": {"type": "long&qu…

JavaSE_第8章 异常(尚)

JavaSE_【异常】 主要内容 异常的体系结构常见异常throw关键字&#xff08;手动创建并抛出异常&#xff09;异常处理机制一&#xff1a;try&#xff08;掌握&#xff09;异常处理机制二&#xff1a;throws&#xff08;掌握&#xff09;自定义异常 学习目标 能够辨别程序中异…

03-Elasticsearch-基本语法

查询[ES] 查询ES信息GET /查询集群健康状态GET /_cluster/health增删改索引 创建索引并指定主分片和副本数PUT /my_doc {"settings": {"number_of_shards": 1,"number_of_replicas": 0} }创建索引并指定映射PUT /index_mappings {"mappings…

Redis详解

Redis介绍1.Redis 是一个基于内存的高性能 key-value 数据库。是完全开源免费的,用C语言编写的,遵守BSD协议2.Redis 特点:1)Redis 是基于内存操作的,吞吐量非常高,可以在 1s内完成十万次读写操作      2)Redis 的读写模块是单线程,每个操作都具原子性      …

图的遍历 —— 广度优先遍历

与树的遍历类似&#xff0c;图的遍历指从图的某一节点出发&#xff0c;按照某种搜索方式对图中的所有节点都仅访问一次。图的遍历可以解决很多搜索问题&#xff0c;实际应用非常广泛。图的遍历根据搜索方式的不同&#xff0c;分为广度优先遍历和深度优先遍历。 图的遍历 —— 广…

PATHWAYS: ASYNCHRONOUS DISTRIBUTED DATAFLOW FOR ML论文阅读笔记

PATHWAYS: ASYNCHRONOUS DISTRIBUTED DATAFLOW FOR ML 针对机器学习的异步分布式数据流阅读笔记 B站李沐视频讲解&#xff1a;Pathways-论文精读-李沐 摘要 ​ 本篇论文介绍了用于加速器的新的大规模的编排层orchestration layer的设计——Pathways。论文新型异步分布式数据…

Redis 缓存穿透, 缓存击穿, 缓存雪崩的解决方案与布隆过滤器

缓存穿透解决方案 设置空值布隆过滤器优点可以将存在的缓存, 位置设置为1, 然后当不存在的参数过来的时候, 会匹配到0上,这样就会直接返回不存在缺点存在错误判断, hash冲突 删除缓存时无法删除指定的1的位置, 应为存在多数据,同一hash, 所以无法删除 增加开发成本, 维护成本提…

线段树的基本操作

一 基本概念 线段树是一种基于分治思想的二叉树&#xff0c;它的每个节点对应一个[L,R]区间,叶子节点的区间 LR。每个非叶子节点[L,R]的左孩子区间为&#xff3b;L,(LR)/2]&#xff0c;右孩子区间为&#xff3b;(LR)/2,R]。[1,10] 区间的线段树如下。 二 线段树的存储方式 对…

初识ansible和ad-hoc

基本环境要求&#xff1a; 管理节点 被管理节点 openssh openssh python > 2.6 python >2.4 ansible 安装ansible 用最简单的方式进行安装 环境 CentOS Linux release 7.3.1611 内核版本&#xff1a; 3.10.0-514.el7.x86_64 centos7 需要联网 yum install epel-relea…

【ACM-ICPC】NEERC-2017(Clone Contest)

【ACM-ICPC】NEERC-2017(Clone Contest)A. Auxiliary Project (思维贪心)K. Kotlin Island (找规律构造)B. Boolean Satisfiability (逻辑或的性质)C. Consonant Fencity (下标映射二进制枚举构造)I. Intelligence in Perpendicularia(学霸题)简单记录一下比赛中AC的题目和思路…

Android Camera性能分析 - 第21讲 录像Buffer Path详解

本讲是Android Camera性能分析专题的第21讲 ​&#xff0c;我们介绍录像Buffer Path详解&#xff0c;包括如下内容&#xff1a; Android Codec2 简介Video Codec MediaRecorder.getSurface 录像Buffer PathVideo Codec2 MediaRecorder.getSurface 录像Buffer PathVideo Code…

【吴恩达深度学习】——NLP和Word Embedding

NLP和词嵌入思维导图词汇表征one-hot表征特征表征&#xff1a;词嵌入使用word Embeddings命名实体识别的例子&#xff1a;词嵌入的迁移学习&#xff1a;词嵌入和人脸编码词嵌入的特性类比推理的特性相似度函数&#xff1a;嵌入矩阵学习词嵌入其它的上下文和目标词对Word2VecSki…

C++ -------- 类型转换

目录 1.C语言中的类型转换 2.为什么C需要四种类型转换 3.C强制类型转换 (1)static_cast (2)reinterpret_cast (3)const_cast (4)dynamic_cast 4.explicit 5.RTTI 6.常见测试题 1.C语言中的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0…

MATLAB APP Desinger 使用方法介绍(下)---开发技巧常用示例补充整理以及app文件的发布和部署方法

本系列文章主要介绍使用MATLAB APP Desinge进行app或者说GUI界面开发的方法介绍&#xff0c;包括&#xff08;上&#xff09;和&#xff08;下&#xff09; 两篇文章&#xff0c;上篇中主要介绍常用的GUI组件的使用方法&#xff0c;下篇是对上篇的补充&#xff0c;主要介绍开发…

计算机网络原理(三):运输层

运输层服务 多路复用与多路分解 无连接运输:UDP 可靠数据传输原理 面向连接的运输:TCP 拥塞控制原理及TCP拥塞控制 一、运输层服务 1.1运输层服务 运 输层协议为运行在不同主机上的应用进程之间提供了逻辑通信(logic communica-tion)功能,从应用层的角度看,通过逻辑通信,运…

Docker镜像加速器的配置

目录 一、登录阿里云 1.1、点击右上角的控制台 1.2、再点击左上角​编辑 1.3、点击容器镜像服务 1.4、点击镜像工具&#xff0c;选择镜像加速器 1.5、加速器地址 1.6、选择自己使用的linux 二、配置镜像加速器 2.1、创建/etc/docker目录 2.2、配置镜像加速器 2.3、激…

谷粒学院16万字笔记+1600张配图(十七)——课程支付

项目源码与所需资料 链接&#xff1a;https://pan.baidu.com/s/1azwRyyFwXz5elhQL0BhkCA?pwd8z59 提取码&#xff1a;8z59 文章目录demo17-课程支付1.分析1.1两种情况1.2免费1.3收费2.新建订单微服务2.1创建子子模块service_order2.2创建数据表2.3生成代码2.4配置application.…

H3C设备全网通

【实验要求】 1、根据拓扑配置网络地址&#xff0c;实现全网通 2、在server上配置Telnet 3、AB能访问Internet , CD不能访问 4、CD能访问server, AB不能访问 【实验步骤】 1、各PC: 设置ip 设置默认路由&#xff08;吓一跳为网关&#xff09; 用路由器代替PC&#xff0c;[H3…

【过程记录】ssh配置免密登录/anaconda环境迁移/gcc_g++安装切换

前言 组里有两台服务器&#xff0c;想要将一台服务器上的anaconda环境迁移到另一台无法联网的服务器上&#xff0c;本篇就来记录快速迁移过程。 ssh配置免密登录 每次使用scp传文件需要输入密码&#xff0c;太过麻烦&#xff0c;可以通过配置ssh免密的方式来避免反复输入密码…