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 提供了多种实现模态窗口的方式,核心分为应用程序级模态和窗口级(父级)模态,实现方式不同
解决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 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;
|
实际编程中,确保在对象不再需要时正确地删除它们非常重要,否则会导致内存泄漏。
如果你使用智能指针(如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 2 3
| connect(sender, &SenderClass::signalName, [=](){ // Lambda表达式中的代码,相当于槽的函数体 });
|
注意事项
- 信号和槽的名称:在使用旧式字符串语法时(
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 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() 函数(线程入口点)实现线程逻辑。
实现步骤:
- 自定义类继承
QThread;
- 重写
run() 函数,线程启动后会执行 run() 中的代码;
- 调用
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); } qDebug() << "线程结束"; } };
#include <QApplication>
int main(int argc, char *argv[]) { QApplication app(argc, argv); MyThread thread; thread.start(); QCoreApplication::processEvents(); qDebug() << "主线程ID:" << QThread::currentThreadId(); return app.exec(); }
|
- 适用场景:简单的后台任务,不需要与主线程频繁通信。
注意:
方法二:
QObject + moveToThread(推荐方式)
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(); 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();
QtConcurrent::run(myFunction); QCoreApplication::processEvents(); return app.exec(); }
|
优点:
- 代码简洁,无需手动管理线程。
- 适合简单的后台任务。
缺点:
- 灵活性不如
QObject + moveToThread 方式。
方法四:
使用 QThreadPool 和 QRunnable(线程池方式)
- 原理:
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>
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;
Worker* worker = reinterpret_cast<Worker*>(thread->property("worker").value<void*>()); if (worker) worker->requestStop();
if (thread->isRunning()) { thread->quit(); if (!thread->wait(2000)) { thread->terminate(); thread->wait(100); } }
delete thread; }
g_threads.clear(); qDebug() << "子线程清理完成"; }
int main(int argc, char *argv[]) { atexit(cleanupAllThreads);
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(); });
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 的信号槽机制可以安全地在不同线程间传递数据(自动队列化)。
- 互斥锁:使用
QMutex、QReadWriteLock 等保护共享数据。
- 条件变量:使用
QWaitCondition 实现线程间的等待和唤醒。
注意事项
- 不要在子线程中操作 GUI:Qt 的 GUI 类(如
QWidget、QMainWindow 等)不是线程安全的,所有 GUI 操作必须在主线程中执行。
- 线程的结束:要确保线程能够正确结束,避免资源泄漏。可以通过
quit() 和 wait() 方法来结束线程。
- 异常处理:在子线程中抛出的异常如果没有被捕获,可能会导致程序崩溃,要注意异常处理。
推荐
- 如果任务比较复杂,或者需要多个任务在同一个线程中执行,建议使用
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 { QTableView::mousePressEvent(event);
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
- 打开 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 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);
QVariant var1; var1.setValue(p);
QVariant var2 = QVariant::fromValue(p);
|
读取数据
1 2 3 4 5 6 7 8 9 10
| Person p1 = var2.value<Person>();
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; }
|
- 信号与槽:传递不确定类型的数据
- 动态属性系统:
QObject::setProperty()
- 模型/视图:
QAbstractItemModel 的数据角色
- 配置文件:
QSettings 存储各种类型配置
- 数据库:
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
|