Qt笔记
AbstractButton(抽象按钮)
clicked 点击了pressed 按下released 释放toggled 切换的triggered 触发的
QT | 设置应用程序名称和主窗口标题
w.setWindowTitle("qt程序");
QT主窗口关闭并且关闭其他窗口
在Qt中,如果你想要在关闭主窗口时同时关闭所有其他窗口,你可以通过连接主窗口的closeEvent()信号到其他窗口的关闭槽函数来实现。
mainwin.h
1 | #include <QMainWindow> |
mainwin.c
1 | MainWindow::MainWindow(QWidget *parent): QWidget(parent), ui(new Ui::Widget) |
阻塞父窗口
实现父窗口被阻塞、只能操作当前窗口的这类窗口被称为模态窗口(Modal Window),Qt 提供了多种实现模态窗口的方式,核心分为应用程序级模态和窗口级(父级)模态,实现方式不同
1 | --- |
解决QT中文显示乱码问题
方法一:简单粗暴
1 | ui.label->setText(u8"显示汉字");//使用方式 |
其他方法请参考:https://developer.aliyun.com/article/1256040
QT创建的对象是否需要delete
Qt中,如果你使用了new关键字来创建对象,并且该对象的生命周期不是由Qt的对象树(object tree)管理的,那么你需要负责在适当的时候使用delete来释放这个对象。
Qt提供了一些规则来帮助判断是否需要手动删除对象:
- 如果你使用
new来创建对象,那么你需要使用delete来释放它。 - 如果对象是通过Qt的动态信号和槽连接机制创建的(比如通过
connect函数使用new创建的lambda表达式或者是QObject的子类),那么你需要负责删除这些对象。 - 如果对象是通过Qt的对象树创建的(比如通过
QWidget的子类创建的部件,并且设置了非null的父对象),那么当父对象被删除时,它的子对象也会被自动删除。 - 如果你使用了Qt的内存管理助手宏
Q_DECLARE_METATYPE并且将你的自定义类型注册到了Qt的类型系统中,那么你需要确保当不再需要这个对象时,使用delete来释放它,因为Qt的信号和槽机制会自动对其进行复制。
1 | // 需要手动删除 |
实际编程中,确保在对象不再需要时正确地删除它们非常重要,否则会导致内存泄漏。
如果你使用智能指针(如QSharedPointer或std::unique_ptr),它们会自动管理对象的生命周期,从而减少内存泄漏的风险。
项目中获取当前路径
代码里路径直接用$$PWD取当前路径,再接库目录的路径
QT信号与槽多种表达方式
connect
使用QObject指针和成员函数指针
1 | QObject::connect(sender, SIGNAL(signalName()), receiver, SLOT(slotName())); |
使用函数指针(C++11及以后)
从Qt 5开始,推荐使用新的连接语法,特别是在C++11及以后的版本中,你可以直接使用函数指针:
1 | connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName); |
使用Lambda表达式(C++11及以后)
Lambda表达式提供了一种灵活的方式来定义槽,而不需要定义独立的槽函数:
1 | connect(sender, &SenderClass::signalName, [=](){ |
注意事项
- 信号和槽的名称:在使用旧式字符串语法时(
SIGNAL和SLOT宏),信号和槽的名称必须是有效的。在C++11及以后,你可以直接使用成员函数的指针。 - 线程安全:确保信号和槽在相同的线程中执行,或者使用
Qt::QueuedConnection、Qt::DirectConnection、Qt::AutoConnection等连接类型来控制信号和槽的执行方式。例如,跨线程通信时使用Qt::QueuedConnection:
1 | connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName, Qt::QueuedConnection); |
- 断开连接:使用disconnect函数可以断开之前建立的连接,以避免内存泄漏或不必要的回调。例如:
1 | disconnect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName); |
添加不在项目路径中的文件
1 | # .pro 文件配置 |
QT创建线程
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 继承 QThread | 简单直观 | 不够灵活,难以管理多个任务 | 简单的后台任务 |
| QObject + moveToThread | 灵活,可管理多个任务,信号槽通信方便 | 代码稍复杂 | 复杂任务,需要线程复用 |
| QtConcurrent::run | 无需管理线程,使用简单 | 不够灵活,难以控制线程生命周期 | 简单的并行计算 |
| QThreadPool + QRunnable | 高效管理大量短任务 | 不适合长时间运行的任务 | 大量短任务,线程池复用 |
方案一:
一、继承 QThread 并重写 run()(传统方法)
这是最经典的线程创建方式,通过继承 QThread 类,重写其 run() 函数(线程入口点)实现线程逻辑。
实现步骤:
- 自定义类继承
QThread; - 重写
run()函数,线程启动后会执行run()中的代码; - 调用
start()启动线程(而非直接调用run())。
示例代码:
1 |
|
- 适用场景:简单的后台任务,不需要与主线程频繁通信。
注意:
QThread对象本身是在创建它的线程中(通常是主线程)执行的,只有run()方法内的代码才会在新线程中运行。不要直接调用
run(),而是调用start()来启动线程。
方法二:
QObject + moveToThread(推荐方式)
原理:将一个继承自
QObject的任务对象移动到一个QThread中,通过信号槽触发任务。特点:
- 更灵活,一个线程可以执行多个任务对象。
- 线程生命周期与任务对象分离,易于管理。
- 信号槽可以安全地跨线程通信。
1 |
|
- 适用场景:复杂任务、需要线程池管理、多个任务共享一个线程。
方法三:
使用 QtConcurrent(简单并行)
如果只是想简单地在后台运行一个函数,可以使用 QtConcurrent::run(),它会自动管理线程。
1 |
|
优点:
- 代码简洁,无需手动管理线程。
- 适合简单的后台任务。
缺点:
- 灵活性不如
QObject + moveToThread方式。
方法四:
使用 QThreadPool 和 QRunnable(线程池方式)
- 原理:
QThreadPool是 Qt 的线程池管理类,QRunnable表示一个可运行的任务。 - 特点:
- 适合大量短任务,线程池自动管理线程的创建和销毁。
- 可以限制最大线程数,避免资源耗尽。
- 示例:
1 |
|
- 适用场景:大量的短任务,例如图像处理、网络请求等。
方案五:
信号量(无 GUI)- 主线程退出清理子线程
核心机制
- 用全局容器管理子线程指针;
- 主线程退出前调用清理函数,强制终止子线程并释放资源;
- 无需事件循环,通过
atexit注册退出回调(C++ 原生机制)。
1 |
|
线程同步与通信
在多线程编程中,线程间的同步和通信非常重要:
- 信号槽:Qt 的信号槽机制可以安全地在不同线程间传递数据(自动队列化)。
- 互斥锁:使用
QMutex、QReadWriteLock等保护共享数据。 - 条件变量:使用
QWaitCondition实现线程间的等待和唤醒。
注意事项
- 不要在子线程中操作 GUI:Qt 的 GUI 类(如
QWidget、QMainWindow等)不是线程安全的,所有 GUI 操作必须在主线程中执行。 - 线程的结束:要确保线程能够正确结束,避免资源泄漏。可以通过
quit()和wait()方法来结束线程。 - 异常处理:在子线程中抛出的异常如果没有被捕获,可能会导致程序崩溃,要注意异常处理。
推荐
- 如果任务比较复杂,或者需要多个任务在同一个线程中执行,建议使用
QObject + moveToThread方式。 - 如果是大量短任务 →
QThreadPool + QRunnable - 如果只是简单的后台任务,可以使用
QtConcurrent::run()。
自定义Qt UI组件功能
给组件添加本来不存在的功能
示例:获取鼠标点击空白或者点击单元格
基础方式:重写鼠标事件函数
QTableView 继承自 QAbstractItemView,可通过重写 mousePressEvent 捕获所有鼠标点击事件(包括空白处),核心是区分 “点击单元格” 和 “点击空白处”。
自定义 TableView 类
1 |
|
重写 QTableView 后 UI 界面调用(Qt Designer 集成)
调用自定义时候,一定要现在控件中调用自定义的头文件,才能使用如下步骤
重写的 CustomTableView 可以直接在 Qt Designer 中调用,无需手动编写所有代码,核心是 “提升控件(Promote to)”。
步骤 1:Qt Designer 中放置普通 QTableView
- 打开 Qt Designer,拖一个
QTableView到界面,命名为customTableView; - 右键该
QTableView→ 选择「Promote to…」(提升为)。
步骤 2:配置提升类
在弹出的 “Promoted Widgets” 窗口中:
- Promoted Class Name:填写自定义类名
CustomTableView; - Header File:填写头文件路径(如
customtableview.h); - 点击「Add」→ 「Promote」。
步骤 3:编译运行
- 将
customtableview.h/.cpp添加到项目工程; - 编译后,Designer 中的
QTableView会被替换为CustomTableView,其重写的mousePressEvent自动生效; - 信号槽连接(可选)
1 | // 主窗口构造函数中 |
注意
优先重写 Q*View + 自定义模型(如 QStandardItemModel);Q*Widget 是封装好的控件(耦合了模型),灵活性低;
QVariant
类似 C++ 的 std::variant 或联合 union
可以存储和管理多种 Qt 和 C++ 数据类型。
| 特性 | 说明 |
|---|---|
| 类型灵活 | 可存储 int、double、QString、QDateTime 等多种类型 C++ 基本类型:int、float、double、bool 等 Qt 所有已定义的类型:QString、QDate、QTime、QSize、QPoint 等 容器类型:QVariantList、QVariantMap、QVariantHash |
| 类型安全 | 内部维护类型标志,运行时检查类型 |
| 隐式共享 | 采用 Qt 的隐式共享机制,拷贝高效 |
| 通用容器 | 常用于信号槽、动态属性、QSettings 等场景 |
存储自定义数据的操作步骤
要存储自定义类型(如 struct 或 class),需要完成以下 3 个步骤:
定义自定义类型
1 | struct Person { |
注册元类型(关键步骤)
在头文件或源文件中使用 Q_DECLARE_METATYPE 宏声明:
1 |
|
注意:这个宏通常放在全局命名空间,且在 main() 函数之前或头文件中
注册到 Qt 元类型系统(用于信号槽)
如果要在信号和槽之间传递自定义类型,还需要在运行时注册:
1 | qRegisterMetaType<Person>("Person"); |
通常在 main() 函数开头调用
QVariant 存储和读取自定义数据
存储数据
1 | Person p("张三", 25); |
读取数据
1 | // 方法1:使用 value<T>() |
注意事项
| 注意事项 | 说明 |
|---|---|
| 头文件 | 需要 #include <QVariant> 和 #include <QMetaType> |
| 宏位置 | Q_DECLARE_METATYPE 应在全局命名空间 |
| 信号槽 | 跨线程信号槽必须调用 qRegisterMetaType |
| 数据流 | 如需序列化,需实现 QDataStream 的 << 和 >> 操作符 |
| 性能 | 频繁类型转换可能有性能开销,避免滥用 |
实现数据流操作符(用于序列化):
1 | QDataStream &operator<<(QDataStream &out, const Person &p) { |
- 信号与槽:传递不确定类型的数据
- 动态属性系统:
QObject::setProperty() - 模型/视图:
QAbstractItemModel的数据角色 - 配置文件:
QSettings存储各种类型配置 - 数据库:
QSqlQuery处理不同列类型
QVariant 是 Qt 中处理多类型数据的核心工具,存储自定义类型的关键是 Q_DECLARE_METATYPE 宏声明 和 qRegisterMetaType (信号和槽需要使用这个)运行时注册。
字符转换
VS2017生成的静态库提供QT调用
Qt官方预编译库默认使用 /MD
大多数第三方库(包括libusb)也是用 /MD 编译的
VS2017工程配置

静态库的头文件
静态库头文件,只需要有如下配置,其他的都不需要了,不要别的关键字
1 | //需要包含的文件 |