Qt笔记

AbstractButton(抽象按钮)

clicked 点击了
pressed 按下
released 释放
toggled 切换的
triggered 触发的

QT | 设置应用程序名称和主窗口标题

w.setWindowTitle("qt程序");

QT主窗口关闭并且关闭其他窗口

在Qt中,如果你想要在关闭主窗口时同时关闭所有其他窗口,你可以通过连接主窗口的closeEvent()信号到其他窗口的关闭槽函数来实现。

mainwin.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <QMainWindow>
#include <QCloseEvent>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow {
Q_OBJECT

public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();

protected:
void closeEvent(QCloseEvent *event) override { //override 虚函数
// 关闭所有其他窗口
// 你需要有一个指针或者引用列表来存储这些窗口
#if 0 //下面方法二选其一
foreach (QWidget *widget, otherWindows) {
widget->close();
}
#else
foreach(auto widget,otherWin)
{
widget->close();
}
#endif
event->accept(); // 接受关闭事件
}

private:
MainWindow_Debug * mainWindow;
QList<QWidget*> otherWindows; // 存储其他窗口的列表

private:
Ui::Widget *ui;
};

mainwin.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MainWindow::MainWindow(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);

this->mainWindow = new MainWindow_Debug(parent);

this->mainWindow->show();

otherWin.push_back(this->mainWindow);

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

阻塞父窗口

实现父窗口被阻塞、只能操作当前窗口的这类窗口被称为模态窗口(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提供了一些规则来帮助判断是否需要手动删除对象:

  1. 如果你使用new来创建对象,那么你需要使用delete来释放它。
  2. 如果对象是通过Qt的动态信号和槽连接机制创建的(比如通过connect函数使用new创建的lambda表达式或者是QObject的子类),那么你需要负责删除这些对象。
  3. 如果对象是通过Qt的对象树创建的(比如通过QWidget的子类创建的部件,并且设置了非null的父对象),那么当父对象被删除时,它的子对象也会被自动删除。
  4. 如果你使用了Qt的内存管理助手宏 Q_DECLARE_METATYPE 并且将你的自定义类型注册到了Qt的类型系统中,那么你需要确保当不再需要这个对象时,使用delete来释放它,因为Qt的信号和槽机制会自动对其进行复制。
1
2
3
4
5
6
7
8
9
10
11
// 需要手动删除
MyObject *obj = new MyObject();
// ... 使用obj
delete obj;

// 不需要手动删除,当父对象被删除时,子对象会自动被删除
QWidget *window = new QWidget();
QPushButton *button = new QPushButton(window);
// ... 使用button
// 当不再需要window时,可以通过delete window;来自动删除button
delete window;

实际编程中,确保在对象不再需要时正确地删除它们非常重要,否则会导致内存泄漏。

如果你使用智能指针(如QSharedPointerstd::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
2
3
connect(sender, &SenderClass::signalName, [=](){
// Lambda表达式中的代码,相当于槽的函数体
});

注意事项

  • 信号和槽的名称:在使用旧式字符串语法时(SIGNALSLOT宏),信号和槽的名称必须是有效的。在C++11及以后,你可以直接使用成员函数的指针。
  • 线程安全:确保信号和槽在相同的线程中执行,或者使用Qt::QueuedConnectionQt::DirectConnectionQt::AutoConnection等连接类型来控制信号和槽的执行方式。例如,跨线程通信时使用Qt::QueuedConnection
1
connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName, Qt::QueuedConnection);
  • 断开连接:使用disconnect函数可以断开之前建立的连接,以避免内存泄漏或不必要的回调。例如:
1
disconnect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName);

添加不在项目路径中的文件

1
2
3
4
5
6
7
8
9
10
# .pro 文件配置
INCLUDEPATH += $$PWD/3rdparty/libusb/include # 头文件路径
LIBS += -L$$PWD/3rdparty/libusb/lib # 库文件路径
# 根据平台链接具体库
win32 {
LIBS += -llibusb-1.0 # Windows 下链接 libusb-1.0.lib
}
unix {
LIBS += -lusb-1.0 # Linux/macOS 下链接 libusb-1.0.so/dylib
}

QT创建线程

方式 优点 缺点 适用场景
继承 QThread 简单直观 不够灵活,难以管理多个任务 简单的后台任务
QObject + moveToThread 灵活,可管理多个任务,信号槽通信方便 代码稍复杂 复杂任务,需要线程复用
QtConcurrent::run 无需管理线程,使用简单 不够灵活,难以控制线程生命周期 简单的并行计算
QThreadPool + QRunnable 高效管理大量短任务 不适合长时间运行的任务 大量短任务,线程池复用

方案一:

一、继承 QThread 并重写 run()(传统方法)

这是最经典的线程创建方式,通过继承 QThread 类,重写其 run() 函数(线程入口点)实现线程逻辑。

实现步骤:

  1. 自定义类继承 QThread
  2. 重写 run() 函数,线程启动后会执行 run() 中的代码;
  3. 调用 start() 启动线程(而非直接调用 run())。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <QThread>
#include <QDebug>

class MyThread : public QThread {
Q_OBJECT
protected:
void run() override {
// 线程执行的逻辑(耗时操作等)
for (int i = 0; i < 5; ++i) {
qDebug() << "线程运行中:" << i << " 线程ID:" << currentThreadId();
msleep(1000); // 休眠1秒(QThread的成员函数)
}
qDebug() << "线程结束";
}
};

// 使用方式
#include <QApplication>

int main(int argc, char *argv[]) {
QApplication app(argc, argv);//这里很关键,一定要在这个函数下面才能使用信号与槽

MyThread thread;
thread.start(); // 启动线程(会自动调用run())
//QThread::msleep(1);
QCoreApplication::processEvents();
// 主线程继续执行其他逻辑
qDebug() << "主线程ID:" << QThread::currentThreadId();

return app.exec();
}
  • 适用场景:简单的后台任务,不需要与主线程频繁通信。

注意

  • QThread 对象本身是在创建它的线程中(通常是主线程)执行的,只有 run() 方法内的代码才会在新线程中运行。

  • 不要直接调用 run(),而是调用 start() 来启动线程。

方法二:

QObject + moveToThread(推荐方式)

  • 原理:将一个继承自 QObject 的任务对象移动到一个 QThread 中,通过信号槽触发任务。

  • 特点

    • 更灵活,一个线程可以执行多个任务对象。
    • 线程生命周期与任务对象分离,易于管理。
    • 信号槽可以安全地跨线程通信。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <QThread>
#include <QObject>
#include <QDebug>
#include <QCoreApplication>

class Worker : public QObject {
Q_OBJECT
public slots:
void doWork() {
qDebug() << "Worker 线程ID:" << QThread::currentThreadId();
for (int i = 0; i < 5; ++i) {
qDebug() << "执行任务:" << i;
QThread::msleep(500); // 模拟耗时操作
}
emit workFinished(); // 通知任务完成
}

signals:
void workFinished(); // 任务完成信号
};

int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv); //这里很关键,一定要在这个函数下面才能使用信号与槽

qDebug() << "主线程ID:" << QThread::currentThreadId();

// 创建线程和任务对象
QThread *thread = new QThread;
Worker *worker = new Worker;

// 将任务对象移动到线程
worker->moveToThread(thread);

// 启动线程后执行任务
QObject::connect(thread, &QThread::started, worker, &Worker::doWork);

// 任务完成后退出线程
QObject::connect(worker, &Worker::workFinished, thread, &QThread::quit);

// 线程结束后销毁任务对象和线程对象
QObject::connect(thread, &QThread::finished, worker, &Worker::deleteLater);
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);

// 启动线程
thread->start();
//QThread::msleep(1);
QCoreApplication::processEvents();
return app.exec();
}
  • 适用场景:复杂任务、需要线程池管理、多个任务共享一个线程。

方法三:

使用 QtConcurrent(简单并行)

如果只是想简单地在后台运行一个函数,可以使用 QtConcurrent::run(),它会自动管理线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <QCoreApplication>
#include <QtConcurrent/QtConcurrent>
#include <QDebug>

void myFunction() {
qDebug() << "QtConcurrent 线程ID:" << QThread::currentThreadId();
for (int i = 0; i < 5; ++i) {
qDebug() << "QtConcurrent 执行中:" << i;
QThread::msleep(500);
}
}

int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);//这里很关键,一定要在这个函数下面才能使用信号与槽

qDebug() << "主线程ID:" << QThread::currentThreadId();

// 在后台线程中运行 myFunction
QtConcurrent::run(myFunction);
//QThread::msleep(1);
QCoreApplication::processEvents();
return app.exec();
}

优点

  • 代码简洁,无需手动管理线程。
  • 适合简单的后台任务。

缺点

  • 灵活性不如 QObject + moveToThread 方式。

方法四:

使用 QThreadPoolQRunnable(线程池方式)

  • 原理QThreadPool 是 Qt 的线程池管理类,QRunnable 表示一个可运行的任务。
  • 特点
    • 适合大量短任务,线程池自动管理线程的创建和销毁。
    • 可以限制最大线程数,避免资源耗尽。
  • 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>

class MyTask : public QRunnable {
public:
void run() override {
qDebug() << "线程池线程ID:" << QThread::currentThreadId();
}
};

// 使用
QThreadPool::globalInstance()->start(new MyTask);
QThread::msleep(1);
  • 适用场景:大量的短任务,例如图像处理、网络请求等。

方案五:

信号量(无 GUI)- 主线程退出清理子线程

核心机制

  • 用全局容器管理子线程指针;
  • 主线程退出前调用清理函数,强制终止子线程并释放资源;
  • 无需事件循环,通过 atexit 注册退出回调(C++ 原生机制)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <QThread>
#include <QSemaphore>
#include <QDebug>
#include <QList>
#include <QMutex>
#include <cstdlib> // atexit头文件

// 全局容器:管理所有子线程
static QList<QThread*> g_threads;
static QMutex g_threadMutex;

// 工作类(无信号槽)
class Worker
{
public:
QSemaphore *sem = nullptr;
bool isStopRequested = false; // 停止标记

void doWork() {
qDebug() << "子线程启动,ID=" << QThread::currentThreadId();
for (int i = 1; i <= 10; ++i) {
if (isStopRequested) break; // 检查停止标记
QThread::msleep(500);
qDebug() << "任务进度=" << i * 10 << "%";
}
if (sem) sem->release();
}

void requestStop() {
isStopRequested = true;
}
};

// 清理子线程的核心函数
void cleanupAllThreads() {
QMutexLocker locker(&g_threadMutex);
qDebug() << "主线程退出,清理子线程,当前线程数=" << g_threads.count();

for (QThread* thread : g_threads) {
if (!thread) continue;

// 1. 找到Worker对象,请求停止
Worker* worker = reinterpret_cast<Worker*>(thread->property("worker").value<void*>());
if (worker) worker->requestStop();

// 2. 强制停止线程
if (thread->isRunning()) {
thread->quit();
if (!thread->wait(2000)) { // 超时2秒强制终止
thread->terminate();
thread->wait(100);
}
}

// 3. 释放资源
delete thread; // 直接delete,无事件循环依赖
}

g_threads.clear();
qDebug() << "子线程清理完成";
}

int main(int argc, char *argv[])
{
// ========== 1. 注册退出回调(C++原生,主线程退出前触发) ==========
atexit(cleanupAllThreads);

// ========== 2. 创建子线程(new出来的堆对象) ==========
QSemaphore sem(0);
for (int i = 0; i < 2; ++i) {
QThread* thread = new QThread();
Worker* worker = new Worker();
worker->sem = &sem;

// 绑定任务到线程
QObject::connect(thread, &QThread::started, [worker, thread]() {
worker->doWork();
thread->quit();
});

// 将Worker对象关联到线程(方便清理时查找)
thread->setProperty("worker", QVariant::fromValue(static_cast<void*>(worker)));

// 加入全局容器
QMutexLocker locker(&g_threadMutex);
g_threads.append(thread);

thread->start();
}

qDebug() << "主线程启动,按任意键退出(模拟提前关闭)";
std::cin.get(); // 按回车模拟主线程提前关闭

// 主线程退出前主动清理(双重保障)
cleanupAllThreads();
return 0;
}

线程同步与通信

在多线程编程中,线程间的同步和通信非常重要:

  • 信号槽:Qt 的信号槽机制可以安全地在不同线程间传递数据(自动队列化)。
  • 互斥锁:使用 QMutexQReadWriteLock 等保护共享数据。
  • 条件变量:使用 QWaitCondition 实现线程间的等待和唤醒。

注意事项

  1. 不要在子线程中操作 GUI:Qt 的 GUI 类(如 QWidgetQMainWindow 等)不是线程安全的,所有 GUI 操作必须在主线程中执行。
  2. 线程的结束:要确保线程能够正确结束,避免资源泄漏。可以通过 quit()wait() 方法来结束线程。
  3. 异常处理:在子线程中抛出的异常如果没有被捕获,可能会导致程序崩溃,要注意异常处理。

推荐

  • 如果任务比较复杂,或者需要多个任务在同一个线程中执行,建议使用 QObject + moveToThread 方式。
  • 如果是大量短任务 → QThreadPool + QRunnable
  • 如果只是简单的后台任务,可以使用 **QtConcurrent::run()**。

自定义Qt UI组件功能

给组件添加本来不存在的功能

示例:获取鼠标点击空白或者点击单元格

基础方式:重写鼠标事件函数

QTableView 继承自 QAbstractItemView,可通过重写 mousePressEvent 捕获所有鼠标点击事件(包括空白处),核心是区分 “点击单元格” 和 “点击空白处”。

自定义 TableView 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <QTableView>
#include <QMouseEvent>
#include <QModelIndex>

class CustomTableView : public QTableView
{
Q_OBJECT // 必须加,支持信号槽

public:
explicit CustomTableView(QWidget *parent = nullptr) : QTableView(parent) {}

protected:
// 重写鼠标按下事件
void mousePressEvent(QMouseEvent *event) override {
// 1. 先调用父类事件(保证原有功能不丢失,如选中单元格)
QTableView::mousePressEvent(event);

// 2. 判断点击位置是否为空白处(无模型索引)
QModelIndex clickedIndex = indexAt(event->pos());
if (clickedIndex.isValid()) {
// 点击到单元格:获取行、列
qDebug() << "点击单元格:行=" << clickedIndex.row() << "列=" << clickedIndex.column();
emit cellClicked(clickedIndex.row(), clickedIndex.column()); // 自定义信号
} else {
// 点击空白处
qDebug() << "点击表格空白处,位置:" << event->pos();
emit blankAreaClicked(event->pos()); // 自定义信号
}
}

signals:
// 自定义信号:供外部槽函数响应
void cellClicked(int row, int col);
void blankAreaClicked(const QPoint &pos);
};

重写 QTableView 后 UI 界面调用(Qt Designer 集成)

调用自定义时候,一定要现在控件中调用自定义的头文件,才能使用如下步骤

重写的 CustomTableView 可以直接在 Qt Designer 中调用,无需手动编写所有代码,核心是 “提升控件(Promote to)”。

步骤 1:Qt Designer 中放置普通 QTableView

  1. 打开 Qt Designer,拖一个 QTableView 到界面,命名为 customTableView
  2. 右键该 QTableView → 选择「Promote to…」(提升为)。

步骤 2:配置提升类

在弹出的 “Promoted Widgets” 窗口中:

  • Promoted Class Name:填写自定义类名 CustomTableView
  • Header File:填写头文件路径(如 customtableview.h);
  • 点击「Add」→ 「Promote」。

步骤 3:编译运行

  1. customtableview.h/.cpp 添加到项目工程;
  2. 编译后,Designer 中的 QTableView 会被替换为 CustomTableView,其重写的 mousePressEvent 自动生效;
  3. 信号槽连接(可选)
1
2
3
4
// 主窗口构造函数中
connect(ui->customTableView, &CustomTableView::blankAreaClicked, this, [=](const QPoint &pos) {
qDebug() << "UI 中调用自定义信号:" << pos;
});

注意

优先重写 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
2
3
4
5
6
7
8
struct Person {
QString name;
int age;

// 可选:默认构造函数
Person() : age(0) {}
Person(const QString &n, int a) : name(n), age(a) {}
};

注册元类型(关键步骤)

在头文件或源文件中使用 Q_DECLARE_METATYPE 宏声明:

1
2
3
#include <QMetaType>

Q_DECLARE_METATYPE(Person)

注意:这个宏通常放在全局命名空间,且在 main() 函数之前或头文件中

注册到 Qt 元类型系统(用于信号槽)

如果要在信号和槽之间传递自定义类型,还需要在运行时注册:

1
qRegisterMetaType<Person>("Person");

通常在 main() 函数开头调用

QVariant 存储和读取自定义数据

存储数据

1
2
3
4
5
6
7
8
Person p("张三", 25);

// 方法1:使用 setValue()
QVariant var1;
var1.setValue(p);

// 方法2:使用 fromValue()(推荐)
QVariant var2 = QVariant::fromValue(p);

读取数据

1
2
3
4
5
6
7
8
9
10
// 方法1:使用 value<T>()
Person p1 = var2.value<Person>();

// 方法2:简写形式
Person p2 = var2.value();

// 检查是否可以转换
if (var2.canConvert<Person>()) {
Person p3 = var2.value<Person>();
}

注意事项

注意事项 说明
头文件 需要 #include <QVariant>#include <QMetaType>
宏位置 Q_DECLARE_METATYPE 应在全局命名空间
信号槽 跨线程信号槽必须调用 qRegisterMetaType
数据流 如需序列化,需实现 QDataStream<<>> 操作符
性能 频繁类型转换可能有性能开销,避免滥用

实现数据流操作符(用于序列化):

1
2
3
4
5
6
7
8
9
QDataStream &operator<<(QDataStream &out, const Person &p) {
out << p.name << p.age;
return out;
}

QDataStream &operator>>(QDataStream &in, Person &p) {
in >> p.name >> p.age;
return in;
}
  1. 信号与槽:传递不确定类型的数据
  2. 动态属性系统QObject::setProperty()
  3. 模型/视图QAbstractItemModel 的数据角色
  4. 配置文件QSettings 存储各种类型配置
  5. 数据库QSqlQuery 处理不同列类型

QVariant 是 Qt 中处理多类型数据的核心工具,存储自定义类型的关键是 Q_DECLARE_METATYPE 宏声明 和 qRegisterMetaType (信号和槽需要使用这个)运行时注册。

VS2017生成的静态库提供QT调用

Qt官方预编译库默认使用 /MD

大多数第三方库(包括libusb)也是用 /MD 编译的

VS2017工程配置

静态库的头文件

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

1
2
3
4
5
6
7
8
9
10
11
12
//需要包含的文件

#ifdef __cplusplus
extern "C" {
#endif

//函数声明


#ifdef __cplusplus
}
#endif