当前位置: 首页 > news >正文

QT 学习笔记摘要(三)

第一节 事件

1. 概念

事件:是用户和应用软件间产生的一个 交互 操作,由用户操作产生或者系统内部产生,通过 事件循环 对事件进行处理,事件也可以用来在对象间进行信息交互

        信号槽 : 用户进行的各项操作,就可能会产生出信号,可以给某个信号指定槽函数,当信号触发时,就能够自动的执行到对应的槽函数

        事件: 用户进行的各种操作,也会产生事件,程序员同样可以给事件关联上处理函数(处理的逻辑),当事件触发的时候,就能够执行到对应的代码

        总结: 信号槽就是对于事件的进一步封装,事件是信号槽的底层机制

        在Qt平台中会将系统产生的消息转换为Qt事件 .事件本质上就是一个QEvent的对象

2. 为什么会出现事件

        在实际Qt开发程序的过程中,绝大部分和用户之间进行的交互都是通过"信号槽"来完成的

但是在有些特殊情况下,信号槽不一定能搞定(某个用户的动作行为,Qt没有提供对应的信号)

此时就需要通过重写事件处理函数的形式,来手动处理事件的响应逻辑

        让当前的类,重写某个事件处理函数,这里用到的是"多态"机制,创建子类,继承自Qt中已有的类
再在子类中重写父类的事件处理函数,后续事件触发的过程中,就会通过多态这样的机制,执行到我们自己写的子类函数中

3. 事件处理流程(重要)

3.1 事件产生

        用户操作的各种操作都会产生事件,比如鼠标点击,键盘输入,拖动窗口,而在qt中会将所有的事件包装成一个QEvent对象,并将对象放入事件队列

3.2 事件分发/事件过滤器

        Qt 的事件循环将事件分发给目标对象前,会检查目标对象是否安装了事件过滤器

当安装了:这个事件就需要先经过事件过滤器eventFilter(),事件能否向下进行,是需要通过eventFilter()返回值判断的

        当返回值为true:表明事件已经被处理完毕了,不会向下传递了

        当返回值为false:表明事件还没有处理完毕,会向下执行,并将事件分发给目标对象

当没有安装:这个事件就直接交给目标对象,通常是窗口控件等

3.3 事件处理

        走到这一步,表明该事件没有被过滤器拦截,它将被传递给目标对象的 event() 函数 当事件已经交给目标对象后,还需要判读目标对象是否存在处理这个事件的函数

存在时直接调用对应的事件处理函数处理

不存在时:该事件就会沿着继承链继续向上传递,交给目标对象的父对象去处理,以此类推

4. 代码演示

4.1 演示一: 绑定了信号与槽的控件 又重写了相同的事件函数

        首先新建一个mainwindow窗口的函数,并在ui界面中添加一个按钮,在转到槽,实现点击信号的槽函数,这个槽函数中实现打印一句话就行了

        之后再在项目中新建一个mypushbutton继承QPushbutton

再将ui界面中的按钮类型提升为我们自定义的mypushbutton类型 

运行项目得到:

 

说明一下:

  • 这个按钮此时是绑定了信号与槽的,而打印结果和我们预期一样

  接下来我们在子类中重写鼠标按下事件 

 然后再运行程序:

        通过打印结果我们发现,这个按钮本来绑定的信号槽没有触发了,而是执行的重写鼠标按下事件的函数 

结论/总结 

        当目标对象重写了事件处理函数以后,原本的槽函数没有被触发,这是因为在我们当前这个事件处理函数中,根本就没有发送信号

        而之前被触发,是因为父类的QPushButton中的event()在发送信号,而在这里如果想要触发槽函数,可以通过:

 方式一:自己手动发送信号(这里是emit clicked()) 

方式二(推荐)调用父类的事件处理函数(这里是QPushButton::mousePressEvent(event);)则父类就会帮我们发送信号

4.2 演示二:事件过滤器使用

补:事件过滤器,就是对指定事件进行过滤,增强事件处理函数

        和上面一样还是先创建一个自定义的类MyEvenFilter

 

说明一下:

  • 在我们自定义的事件过滤器对象中,必须重写eventFilter()事件过滤函数
  • watched参数表示:目标对象
  • event参数表示:事件对象

        接下来就是在主窗口中安装这个事件过滤器了,不过这个需要先在主窗口中创建一个我们自定义类的对象(就是添加一个我们自定义类型的成员函数),方便调用

说明一下:

  • 安装事件过滤器的函数是installEventFilter

   运行演示

 

 

说明一下:

  • 此时事件过滤器,没有过滤到鼠标按下的事件,然后就直接返回true,自然连按钮也不会出现了,同理信号槽也没有用了,按钮中重写的事件也没有用了 

  • 但是当我直接return false;就会继续向下执行事件

 

  • 上面代码表示捕捉鼠标按下事件,[进行增强处理], return ture;不再先下执行

  • 同理如果改成return fasle;就会向下处理

推荐返回父类的事件过滤器  

 

推荐返回值返回时:

  • 直接调用父类的事件过滤器

5.  事件VS信号

项目信号/槽事件
触发手动 emitQt 自动分发(系统或用户触发)
响应connect 后自动调用槽函数重写事件函数(如 mousePressEvent
用途对象通信,逻辑处理用户输入、窗口变化等底层操作

6. QEventLoop

        QEventLoop是Qt框架中的一个核心组件,用于实现局部或临时的事件循环。Qt中,主要的事件循环是由QCoreApplicationQGuiApplication提供的

 6.1 在某个函数中,期望5s后在继续执行后面逻辑

方式一:QThread::sleep

 

 

说明一下:

  • sleep()这种方式会卡死整个页面,它会将程序阻塞到这里,导致其他操作都无法执行 

 方式二:QEventLoop事件循环 

 

说明一下:

  • 可以配合定时器,当到一定时间后就发送信号,并关联时的发送退出事件循环信号
  • 使用QEventLoop事件循环机制将不会阻塞其他程序执行

 6.2 实现一个模态对话框

         模态:当弹出新窗口时,无法对父窗口进行任何操作

 --- 首先新建一个自定义类,然后让它继承自QDialog类

 

---- 再在页面中新建按钮,并关联点击信号打开对话框  

 

说明一下: 

  • 对应时间循环可以简单的理解为一个死循环,不过里面不仅仅只是死循环

7. 常见事件

7.1 QMouseEvent(鼠标事件) 

#ifndef LABEL_H
#define LABEL_H#include <QWidget>
#include <QLabel>
#include <QMouseEvent>
class Label : public QLabel
{Q_OBJECT
public:Label(QWidget* parent);void mousePressEvent(QMouseEvent *event);// 按下void mouseReleaseEvent(QMouseEvent *event);// 释放void mouseDoubleClickEvent(QMouseEvent *event);// 双击
};#endif // LABEL_H

 

#include "label.h"
#include <QDebug>
Label::Label(QWidget* parent) : QLabel(parent)
{;
}void Label::mousePressEvent(QMouseEvent *event)
{if(event->button() == Qt::LeftButton){//qDebug() << "按下左键";this->setText("按下左键");}else if(event->button() == Qt::RightButton){qDebug() << "按下右键";}
}void Label::mouseReleaseEvent(QMouseEvent *event)
{if(event->button() == Qt::LeftButton){qDebug() << "释放左键";}else if(event->button() == Qt::RightButton){qDebug() << "释放右键";}
}void Label::mouseDoubleClickEvent(QMouseEvent *event)
{if(event->button() == Qt::LeftButton){qDebug() << "双击左键";}else if(event->button() == Qt::RightButton){qDebug() << "双击右键";}
}

        这里需要再ui界面中把这个控件提升为我们自己写的类,具体操作是: 选中原来的控件,鼠标右键提升,选择新的类型

 

说明一下:

  • 这里是针对QLabel重写的事件,自然也只能在label控件中看到效果

说明一下:

  • 这里是在widget中重写的事件,则这个大窗口都会有效果  
  • Qt为了保证程序的流畅性,默认情况下不会对鼠标移动进行追踪,鼠标移动的时候不会调用
    mouseMoveEvent,除非显示告诉Qt要追踪鼠标位置

7.2 QWheelEvent(鼠标滚轮事件)   

7.3 QKeyEvent(键盘事件)  

 7.4 QTimerEvent(时间事件)

 

说明一下: 

  • 此处的timerId类似于linux中的文件描述符
  • QTimer的背后是QTimerEvent定时器事件进行支持的,所以要实现定时器,通常使用QTimer

 7.5 QMoveEvent(窗口移动事件) 

7.6 QResizeEvent(窗口尺寸事件) 

第二节 进程 && 线程

1. 进程

  • QProcess类:额外执行新的程序,执行程序就是一个新的进程执行
  • QProcess类:进程管理类,使用QProcess类可以操作进程
  • ​ start(程序的路径):启动进程

1.1 QProcess入门 

#include "mainwindow.h"#include <QApplication>
#include <QProcess>
#include <QDebug>
#include <QTextCodec>int main(int argc, char *argv[])
{QApplication a(argc, argv);MainWindow w;w.show();// 打开一个记事本进程// 实例化进程对象QProcess process;// 执行进程命令//process.start("notepad");// 执行ping 进程操作
//    process.start("ping www.baidu.com");process.start("ping",QStringList() <<"www.baidu.com");// 获取进程返回值// process.waitForFinished(5000) = 如果进程在5s之内没有执行结束,没有获取返回值,就说明该进程执行失败if(process.waitForFinished()){QByteArray data = process.readAllStandardOutput();QByteArray error = process.readAllStandardError();// 获取系统默认字符编码QTextCodec *codec = QTextCodec::codecForName("System");QString result = codec->toUnicode(data);qDebug() << result;}return a.exec();
}

 

1.2 进程间通信

本地:1. 共享内存 2. 共享文件

非本地:1.网络 2. 数据库

 1.3 共享内存

新建读内存项目

 

 

说明一下:

  • 读共享内存时,只需要关联相同的共享内存就行了,即名字要相同

新建写内存项目 

 

mainwindow.h 

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QSharedMemory>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void on_btn_write_clicked();private:Ui::MainWindow *ui;QSharedMemory *memory;
};
#endif // MAINWINDOW_H

  mainwindow.cpp

 

说明一下:

  •  写共享内存时,需要开辟共享内存的大小

程序运行  

 

注意运行时:

  • 需要先运行写内存项目(会创建共享内存),之后在运行读内存项目(这样才能关联共享内存)

2. 线程

2.1 线程介绍

        在Qt中提供了一个QThread类来描述线程,QThread提供了一个与平台无关的管理线程的方法,即一个QThread对象管理一个线程

2.2 为什么需要线程

原因一:进行耗时操作时,如果在UI线程(主线程) 里面进行耗时操作,界面不会响应用户操作,则会产生卡界面的现象,由于线程是异步的,则可以把这个耗时操作转移给子线程执行

原因二:为了提升性能,现在的电脑一般都是多核CPU,多线程的并行处理事务,将会大大提高程序性能

 2.3 注意事项

  • Qt的默认线程为UI线程(主线程)︰负责窗口事件处理或窗口控件数据的更新;
  • 子线程负责后台的业务逻辑,子线程不能对窗口对象做任何操作,这些事需要交给UI线程;
  • 主线程和子线程之间如果进行数据的传递,需要使用信号槽机制

2.4 四种使用方式

  • 继承QThread类,重写run()方法,实例化QThread子类实例对象,调用start()
  • 使用moveToThread将一个继承QObject的子类对象移至线程,内部槽函数均在线程中执行
  • 使用QThreadPool,搭配QRunnable (线程池)
  • 使用QtConcurrent(线程池)

2.5 继承QThread类 && 重写run函数

         先创建一个自定义的类,然后让它继承自QThread

        再在子线程中重写run函数,并将主线程中耗时的操作交个子线程中运行 

        之后就是在主线程中创建实例对象并start开始线程

说明一下:

  • MyThread* thread = new MyThread;只是实例化了一个对象,并没有创建子线程
  • thread->start();这段代码将会新建一个线程,并调用里面重写的run函数

既然本质上还是要调用重写的run函数,为什么不直接调用run函数,而是用thread->start()?

 -- 等价与:qt线程中Start()和run()的区别?

  • start()会另开一个线程,去异步执行业务逻辑,run()只是一个普通的成员函数,它不会另开线程,只会在当前线程中去同步执行业务逻辑

运行程序 

说明一下:

  • 从输出结果来看,线程是异步执行的,而不是同步/顺序执行的 

         QThread实例存在于实例化它的旧线程中,而不是调用run()的新线程中。这意味着QThread的所有绑定的信号槽和调用方法都将在旧线程中执行。

说明一下:

  • 由此可以证明,原来QThread实例化对象中所绑定的信号与槽,将只能在旧线程中调用

2.6  moveToThread函数 && 迁移子类对象到线程中

        默认情况下我们在代码中创建的对象都属于主线程,这个对象的槽函数在调用的时候,占用的都是主线程的时间,

        我们也可以将一个QObject类型的对象或子类对象通过moveToThread移动到子线程中去,这样当这个对象的槽函数被信号触发调用的时候,槽函数占用的就是子线程的时间。

         更改此对象及其子对象的线程关联性。如果对象有父对象,则不能移动该对象。事件处理将在TargetThread中继续

新建文件 

代码编写

        首先子定义的这个work是继承自QObject的,里面实现一个槽函数打印线程ID 

        而在Mainwindow2中,我们绑定信号与槽,并执行创建子线程,

注:我发起这个信号是通过ui界面中按钮的点击

        运行程序:

  • 此时因为worker对象属于当前主线程,因此它的槽函数是占主线程时间的,如果想让槽函数占子线程时间就可以使用moveToThread()将对象Worker移动到子线程中去

2.7  QThreadPool && QRunnable (线程池)

        QThreadpool管理多个线程的集合。QThreadpool管理和回收单个QThread对象,以帮助降低使用线程的程序中的线程创建成本。

        每个Qt应用程序都有一个全局QThreadpool对象,可以通过调用globallnstance()来访问。要使用QThreadpool线程之一,子类QRunnable并实现run()虚函数。然后创建该类的一个对象并将其传递QThreadpool:start().

新建文件

编写代码

 myrunnable.h

#ifndef MYRUNNABLE_H
#define MYRUNNABLE_H
#include <QRunnable>
/*** @brief The MyRunnable class* MyRunnable需要去继承QRunnable这个类,这个类就是去告诉线程池,我获取线程是要去做什么任务的*/
class MyRunnable : public QRunnable
{
public:MyRunnable();void run();
};#endif // MYRUNNABLE_H

myrunnable.cpp

#include "myrunnable.h"
#include <QDebug>
#include <QThread>MyRunnable::MyRunnable()
{}// 子线程执行业务逻辑的
void MyRunnable::run()
{qDebug() << "线程池的任务,执行业务逻辑  " <<QThread::currentThreadId();
}

mainwindow3.h

#ifndef MAINWINDOW3_H
#define MAINWINDOW3_H#include <QMainWindow>namespace Ui {
class MainWindow3;
}class MainWindow3 : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow3(QWidget *parent = nullptr);~MainWindow3();private:Ui::MainWindow3 *ui;
};#endif // MAINWINDOW3_H

 mainwindow3.cpp

#include "mainwindow3.h"
#include "ui_mainwindow3.h"
#include <QThreadPool>
#include "myrunnable.h"
#include <QDebug>MainWindow3::MainWindow3(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow3)
{ui->setupUi(this);qDebug() << "主线程开始执行:" << QThread::currentThreadId();// 线程的使用方式三: QThreadPool// 1 实例化QThreadPool实例化对象// 实例化QThreadPool方式有两种://QThreadPool *pool = new QThreadPool;// 方式二: qt中会提供一个全局的线程池对象QThreadPool *pool = QThreadPool::globalInstance();//pool->setMaxThreadCount();// 2 通过线程池执行任务MyRunnable *task = new MyRunnable;for(int i=0;i<10;i++){// 从线程池中拿一个线程出来执行任务pool->start(task);}qDebug() << "主线程执行结束:" << QThread::currentThreadId();
}MainWindow3::~MainWindow3()
{delete ui;
}

线程池对象创建方法:

  • 方式一:new QThreadPool
  • 方式二:QThreadPool::globalInstance(全局静态函数)

程序运行 

 

 说明一下:

  • 为了更好的观察到现象,可以把任务数增多
  • 此时就可以发现有相同的线程ID被重复使用了

2.8 [最简单]QtConcurrent(线程池) 

        通过QtConcurrent命名空间提供高级APl,可以在不使用互斥锁、读写锁、等待条件或信号量等低级线程原语的情况下编写多线程程序。使用QtConcurrent编写的程序会根据可用*处理器内核的数量自动调整所使用的线程数

        说简单点,就是我们使用QtConcurrent实现多线程时,可以不用考虑对共享数据的保护问题。而且,它可以根据CPU的能力,自动优化线程数。

和QThreadPool区别:

  1. QThreadPool自己计算和设置最佳的线程池个数,而QtConcurrent会自动去优化线程池个数
  2. QThreadPool是不能接收子线程返回结果的,但是QtConcurrent可以接收子线程执行结果的

        使用时需要加入模板

```c++
QT       += core gui concurrent

        实际开发时,会遇到两种类型:

1. cpu密集型:做大量计算的,它的线程池个数=电脑核数

2. IO密集型:输入输出(数据库操作比较多的) ,线程池个数 = 核数*2

 新建文件

代码编写 

#include "mainwindow4.h"
#include "ui_mainwindow4.h"
#include <QDebug>
#include <QThread>
#include <QtConcurrent>QString fun1()
{qDebug() << "无参函数,线程执行任务" <<QThread::currentThreadId();return "fun1..............";
}QString fun2(QString name,int age)
{qDebug() << "无参函数,线程执行任务name=" << name <<"  age=" <<age <<QThread::currentThreadId();return "fun2.................";
}MainWindow4::MainWindow4(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow4)
{ui->setupUi(this);qDebug() << "主线程开始执行:" << QThread::currentThreadId();// 线程使用方式四: QtConcurrent// 将fun1和fun2任务加入到线程池中QFuture<QString> f1 = QtConcurrent::run(fun1);QFuture<QString> f2 = QtConcurrent::run(fun2,QString("zhangsan"),30);// 执行任务,result()的返回值,就是fun1()和fun2()这两个函数所返回来的内容f1.result();f2.result();qDebug() << "主线程执行结束:" << QThread::currentThreadId();}MainWindow4::~MainWindow4()
{delete ui;
}

程序运行 

 

通过观察发现它好像是同步的?

但其实是因为result执行线程任务,是需要等待并拿到返回结果的,所以看上去好像是同步执行


说明一下:

  • 通过测试,上面的代码还是会在子线程任务之后执行,说明QtConcurrent还是异步操作

  • 只是说如果在返回值之后做操作的话,必须要等返回值拿到结果以后才能执行

  • 如果以后我的主线程耗费5s,子线程任务都分别要消耗5s

3.加锁

3.1 QMutex入门

 

  •  这里创建了2个线程,一个线程对num循环5k次,由于没加锁导致结果不是1w

  •  这里把锁加上就不会出现各个线程相互竞争的问题了

3.2 QMutexLocker自动解锁

 

  • 因为上面的锁很容易忘记释放,忘记unlock,在逻辑复杂的情况下
  • Qt中也有一个对互斥锁进行封装的类->QMutexLocker,类似与std::mutex智能指针 
  • C++11 也引入了std::lock_guard

3.3 其他补充说明

条件变量:QWaitCondition

信号量:QSemaphore

读写锁:QReadLocker、QWriteLocker、QReadWriteLock

  • 这里就暂不介绍了,这里类和API用法和Linux中的类似,就是对系统函数/接口的封装,

  • 要用到的时候,再查查文档

 

http://www.lqws.cn/news/514009.html

相关文章:

  • 每日AI资讯速递 | 2025-06-25
  • TDengine 的 CASE WHEN 语法技术详细
  • 磐维数据库PanWeiDB V2.0-S3.1.1_B01集中式一主二备安装
  • linux安装docker
  • Android14音频子系统-ASoC-ALSA之DAPM电源管理子系统
  • ISO/IEC 27001:2022 資訊安全管理系統 Information Security Management System , ISMS
  • elementui修改radio字体的颜色和圆圈的样式
  • centos7网络不可达connect: network is unreachable
  • 【JVS更新日志】物联网、智能排产APS、企业计划、规则引擎6.25更新说明!
  • 华为云Flexus+DeepSeek征文|基于Dify构建智能情感分析Agent全流程
  • MiniMax-M1混合MoE大语言模型(本地运行和私有化搭建)
  • 【零基础学AI】第3讲:Git版本控制基础
  • Java项目RestfulAPI设计最佳实践
  • 深入剖析:Spring Boot系统开发的高效之道
  • T-BOX 革新:ASR1606 LTE Cat.1 联合 SD NAND MKDV1GIL-AST 的优势剖析
  • 签名组件:uniapp 签名组件开发,兼容小程序、H5、App等 电子签名
  • Python DuckDB 详解:轻量级分析型数据库的革新实践
  • 学习昇腾开发的第8天
  • 通用 Excel 导出功能设计与实现:动态列选择与灵活配置
  • 鸿蒙ArkUI---基础组件Tabs(Tabbar)
  • ASR1606 LTE Cat.1 与 MK SD NAND–––T-BOX 智能基座的通信存储双擎
  • x86-64安装编译Apollo 9.0 aarch64版本
  • ZArchiver×亚矩云手机:云端文件管理的“超维解压”革命
  • B树和B+树的区别
  • SpringBoot项目快速开发框架JeecgBoot——数据访问!
  • 从零开始的云计算生活——第二十三天,稍作休息,Tomcat
  • pycharm基础操作备忘记录
  • 国芯思辰|同步降压转换器CN2020A替换LMR33620应用于分布式电源系统
  • Jenkins X + AI:重塑云原生时代的持续交付范式
  • Docker部署Flask应用