QT-初识
初始Qt
一、Qt简介
Qt 是一个跨平台的应用程序开发框架,旨在简化GUI(图形用户界面)应用程序的开发工作,广泛应用于桌面应用程序、办公软件、车载系统、工控人机界面、医疗设备界面、游戏开发、建模软件等领域的开发。
Qt的主要特点:
- 跨平台支持:Qt允许开发者编写一次代码,然后可以在多个操作系统上运行,包括Windows、macOS、Linux等。原理是一次编码,处处编译。上层API接口是一样的,针对不同平台提供了对应的程序包,使开发者可以在不知道底层具体实现细节的情况下编写跨平台的应用程序。
- 丰富的GUI组件:Qt提供了大量的图形用户界面(GUI)组件和工具,使开发者能够轻松构建各种复杂的用户界面。无论是桌面应用程序、移动应用还是嵌入式系统,Qt都能满足各种需求。
- 模块化的设计:Qt的类库中的类根据功能分为各种模块,这些模块为开发者提供了广泛的功能,如网络编程、数据库访问、Web浏览器功能、音频和视频处理等。
- 易用性和强大的社区支持:Qt的API设计被认为是易用和直观的,而且其官方文档和社区资源也非常丰富,为开发者提供了大量的帮助。
三、开发工具介绍
1、创建项目
点击QT Creator的菜单项 File -> New Project
设置好项目名称和存储路径
这里选择qmake,点击下一步
设置窗口类的名称,选择窗口基类,有3种窗口基类可以选择。
- QMainWindow是主窗口类,主窗口类具有菜单栏,工具栏,和状态栏。
- QWidget是所有界面组件类的基类,QWidget可以作为独立的窗口,就是一个空白的窗口。
- QDialog是对话框类,窗口具有对话框的显示特效,例如没有最大化按钮。
我们选择QWidget作为窗口基类,并且勾选Generate form
复选框。如果勾选了该复选框,QT Creator会创建窗体(form)文件,我们就可以使用Qt Designer可视化设计窗口界面。
选择翻译文件的界面,本示例不涉及多语言界面,所以使用默认即可。
选择开发套件的界面,开发套件包含了所用的编译器。
2、工程结构
项目创建完成后,工程结构如图所示:
1、MyProject.pro
工程文件
#指定了项目需要用到的基础Qt模块,这里包含了核心模块(core)和图形用户界面模块(gui) 比如:serialport:串口模块。
QT += core gui
#当前Qt版本是否大于4。如果是,则添加widgets模块,这是因为Qt5开始,GUI类从 QtGui模块移到了 QtWidgets(即widgets)模块。
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
#设置项目采用C++11标准进行编译。
CONFIG += c++11
#这部分是关于禁用过时API的注释说明。如果取消注释这行,将在编译时禁止使用Qt 6.0.0之前弃用的所有API,这样可以避免项目依赖已过时的功能。
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
DEFINES += QT_DEPRECATED_WARNINGS
#声明项目的源代码文件,这里是主程序入口main.cpp和自定义Widget实现的widget.cpp。
SOURCES += \main.cpp \widget.cpp
#声明项目所需的头文件,这里包含的是与widget.cpp配套的widget.h头文件。
HEADERS += \widget.h
#声明项目使用的Qt Designer设计的UI文件,这里有一个名为widget.ui的界面文件。
FORMS += \widget.ui
#这部分定义了部署规则。对于QNX系统,目标文件(编译后的可执行文件或库)将被安装到/tmp/<target>/bin目录下;对于非QNX且非Android的Unix系统,则安装到/opt/<target>/bin目录下。当target.path不为空时,会将目标文件加入到INSTALLS列表中,这样在执行make install时,会自动按指定路径进行安装。
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
- 模块引入
QT += core gui
- 告诉 Qt 使用
core
和gui
模块。core
:提供 Qt 的核心功能,如事件循环、容器、线程等。gui
:提供图形界面相关的功能,如窗口、绘图、字体等。
- 版本判断
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
- 如果 Qt 主版本号大于 4(即 Qt 5 或更高),则额外加入
widgets
模块。widgets
模块是 Qt 5 中新增,用于支持传统的桌面 UI 控件(按钮、文本框等)。- Qt 4 中这部分功能在
gui
模块里,所以不需要额外加。
- 启用 C++11
CONFIG += c++11
- 告诉编译器启用 C++11 标准支持(允许使用
auto
、lambda
、std::unique_ptr
等新特性)。
- 编译警告设置
DEFINES += QT_DEPRECATED_WARNINGS
- 如果你使用了 Qt 中标记为“已废弃”的 API,编译器会发出警告。
- 有助于提醒你升级到新 API。
- 禁止旧 API(可选)
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
- 这行被注释掉了。
- 如果取消注释,Qt 会在编译时禁止使用所有在 Qt 6.0.0 之前就被废弃的 API。
- 用于强制代码保持最新。
- 源文件列表
SOURCES += \main.cpp \widget.cpp
- 指定需要编译的
.cpp
源文件。
- 头文件列表
HEADERS += \widget.h
- 指定需要包含的头文件(
.h
)。
- UI 文件列表
FORMS += \widget.ui
- 指定 Qt Designer 创建的界面文件(
.ui
),这些文件会被uic
工具转换为 C++ 代码。
- 部署设置(跨平台)
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
- 设置程序安装路径,根据平台不同而不同:
- 如果是 QNX 系统,安装到
/tmp/项目名/bin
- 如果是 Unix/Linux(非 Android),安装到
/opt/项目名/bin
- 如果是 QNX 系统,安装到
!isEmpty(target.path): INSTALLS += target
:如果设置了路径,就将其加入安装规则。
总结的来说:
这个文件告诉 Qt:“我要用 Qt 的 GUI 模块,支持 C++11,有两个源文件和一个界面文件,最终程序要装在系统目录里。”
2、widget.h是窗口类定义的头文件
Widget类定义的第一行语句插入了一个宏Q_OBJECT,这是使用QT元对象系统的类时必须插入的一个宏。插入了这个宏之后,Widget类就可以使用信号与槽、属性等功能。
/*
这是一个预处理器的条件编译指令,用于防止头文件被重复包含。当第一次包含此头文件时,WIDGET_H未定义,所以#ifndef后面的表达式为真,#define WIDGET_H定义了WIDGET_H这个宏。后续再次包含此头文件时,由于WIDGET_H已经被定义,预处理器会跳过整个条件编译块内的内容,避免了类的多重声明和链接错误
*/
#ifndef WIDGET_H
#define WIDGET_H
//包含了QWidget类的头文件,因为这里的Widget类是从QWidget派生出来的,需要使用QWidget的成员和方法
#include <QWidget>
//Qt框架中用于管理命名空间的预处理宏。宏展开后实质上开始了一个名为QT_NAMESPACE的命名空间
QT_BEGIN_NAMESPACE
//声明了一个内部嵌套的Ui命名空间
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
//定义了一个名为Widget的类,继承Qt的QWidget类,QWidget是一个可以显示在屏幕上的图形用户界面组件
class Widget : public QWidget
{
//这是一个Qt MOC(Meta-Object Compiler)所需的宏,用于启用信号与槽机制等功能。任何需要使用信号与槽或Qt的元对象特性的类都需要包含此宏。Q_OBJECT
public:
//构造函数,接受一个指向父Widget的指针,默认为空。当创建一个Widget时,如果传入一个父Widget,那么在关闭父Widget时,当前子Widget也会随之销毁。Widget(QWidget *parent = nullptr);//析构函数,当Widget对象销毁时会调用。~Widget();
private:
//这个类是在使用Qt Creator或Qt Designer这类集成开发环境(IDE)设计用户界面(UI)时自动生成的。当你在Qt Designer中拖放控件,设计窗口布局时,IDE会根据你的设计自动生成对应的C++代码。Ui::Widget *ui;
};
#endif // WIDGET_H
- 头文件保护宏
#ifndef WIDGET_H
#define WIDGET_H
...
#endif // WIDGET_H
防止同一个头文件被多次包含,造成重复定义。
2. 引入 Qt 的窗口基类
#include <QWidget>
QWidget
是所有可视组件的祖先,你的窗口从它派生。
3. Qt 命名空间的兼容性宏
QT_BEGIN_NAMESPACE
...
QT_END_NAMESPACE
-
在 Qt4/Qt5/Qt6 的不同版本里,
Ui::Widget
可能被放在不同的命名空间。 -
用这两个宏包裹,保证跨版本兼容。
-
展开后大概等于:
namespace Ui { class Widget;
4. 前置声明 ui 类
namespace Ui { class Widget; }
- 不是你自己写的类!
- 这是 Qt 的
uic
工具读取widget.ui
后自动生成的类(真正的文件叫ui_widget.h
)。 - 只在这里前置声明,减少编译依赖,加快编译速度。
5. 你自己的窗口类
class Widget : public QWidget
{Q_OBJECT // 启用 Qt 的信号槽、翻译、反射等元对象系统
public:Widget(QWidget *parent = nullptr); // 构造函数,创建 Widget 对象时可传一个父窗口指针;不传(默认 nullptr)它就是顶层窗口,传了则由父窗口托管生命周期。~Widget(); // 析构函数,负责释放 uiprivate:Ui::Widget *ui; // 指向自动生成的界面类的指针// 通过 ui->pushButton、ui->lineEdit… 访问拖进设计器的控件
};
一张图看懂关系
widget.ui ──[uic]──> ui_widget.h│(生成的类叫 Ui::Widget)││被包含进widget.h/.cpp(你写的业务类)
- 你写代码时用
ui->xxx
就能操控拖进去的按钮、输入框。 - 编译时
uic
把widget.ui
翻译成ui_widget.h
,你的Widget
类通过ui
指针“持有”这份界面。
总结
ui_widget.h
就是Ui::Widget
的完整定义文件;在 Qt Designer 里拖按钮、输入框、布局,保存成widget.ui
→ 编译时uic
把它翻译成ui_widget.h
里的Ui::Widget
类。
你在自己的Widget
类里#include "ui_widget.h"
,就能用ui->pushButton
、ui->lineEdit
直接访问这些拖进去的控件,不用再手写任何界面代码。
3、widget.cpp源程序文件
Widget类目前只有构造函数和析构函数。
#include "widget.h" // 引入自己写的 Widget 类声明
#include "ui_widget.h" //只有包含了 ui_widget.h,才知道 Ui::Widget 里有哪些控件可以用// Widget 构造函数实现
// 参数 parent 指定父窗口;默认 nullptr 表示顶层窗口
Widget::Widget(QWidget *parent): QWidget(parent) // 把 parent 传给基类 QWidget,建立父子关系, ui(new Ui::Widget) // 为界面类 Ui::Widget 分配内存并赋给 ui 指针
{ui->setupUi(this); // 将 Designer 里拖好的界面元素安装到当前窗口 this 上ui->pushButton->setText("取消"); // 把界面中名为 pushButton 的按钮文字改为“取消”
}// 析构函数:释放 new 出来的 ui 对象
Widget::~Widget()
{delete ui; // 释放界面类占用的内存;Qt 的父子机制会自动释放子控件
}
4、main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{//创建一个应用程序对象QApplication a(argc, argv);//创建一个Widget类的实例Widget w;// 没传 parent,等价于 Widget w(nullptr);//调用Widget对象的show()方法,该方法将窗口显示在屏幕上。w.show();// 这样 w 就是一个顶层窗口//启动事件循环,Qt框架会不断地监听各种事件,如鼠标点击、键盘输入、窗口大小改变等。//直到应用程序退出为止return a.exec();
}
5、widget.ui
widget.ui
文件是Qt应用程序中使用Qt Designer工具设计图形用户界面(GUI)时产生的一个XML格式的文件。这个文件存储了关于界面布局、使用的控件及其属性的所有信息。
用Qt Designer打开UI文件进行窗口界面可视化设计时,会生成UI文件。双击widget.ui
文件,可以进行可视化设计。
窗口界面可视化设计
3、log输出
3.1 在调试窗口中输入日志
在Qt中进行log输出, 一般不使用c中的printf, 也不使用C++中的cout, Qt框架提供了专门用于日志输出的类, 头文件名为
使用方法如下:
qDebug() << "Date:" << QDate::currentDate();
// 输出类似: Date: QDate("2025-08-19")qDebug() << "Types:" << QString("String") << QChar('x') << QRect(0, 10, 50, 40);int number = 666;
float i = 11.11;
qWarning() << "Number:" << number << "Other value:" << i;
qInfo() << "Number:" << number << "Other value:" << i;
qCritical()<< "Number:" << number << "Other value:" << i;
输出结果大概是:
Date: QDate("2025-08-19")
Types: "String" "x" QRect(0,10 50x40)
Number: 666 Other value: 11.11
Number: 666 Other value: 11.11
Number: 666 Other value: 11.11
- QDate
- Qt 提供的日期类,用来表示年月日。
- 常用方法:
QDate::currentDate()
→ 返回当前系统日期。QDate(2025, 8, 19)
→ 构造一个指定日期对象。toString()
→ 转换为字符串显示。
例子:
QDate today = QDate::currentDate();
qDebug() << today.toString("yyyy-MM-dd"); // 输出 2025-08-19
- QChar
-
Qt 的字符类,本质上是对
ushort
的封装,可以表示 Unicode 字符。 -
例子:
QChar c('x'); qDebug() << c; // 输出 "x"
-
和
char
不同,QChar
可以表示宽字符(Unicode)。
- QRect
- 表示一个二维矩形区域。
- 构造函数:
QRect(int x, int y, int width, int height)
(x, y)
→ 左上角坐标width
→ 宽度height
→ 高度
例子:
QRect rect(0, 10, 50, 40);
qDebug() << rect; // 输出 QRect(0,10 50x40)
3.2日志输出函数
Qt 提供了一系列 全局日志函数,都返回一个 QDebug
流对象,可以用 <<
输出各种 Qt 类型和 C++ 基本类型。
qDebug()
用于一般调试信息输出(开发时常用)。qWarning()
用于警告信息输出,表示程序可能有问题但不影响继续运行。qInfo()
(Qt 5.5 新增)
用于普通的运行日志输出,不算错误,通常用来打印运行状态。qCritical()
用于严重错误信息输出,表示出现了需要重视的错误。- (还有
qFatal()
,会直接导致程序退出)
总结:
QDate
:日期类QChar
:字符类(Unicode)QRect
:矩形类qWarning() / qInfo() / qCritical()
:不同级别的日志输出函数,类似qDebug()
,区别在于语义不同(调试/警告/信息/严重错误)。
4、Qt案例
MyWidget::MyWidget(QWidget *parent) : // 构造函数,初始化父类 QWidgetQWidget(parent),ui(new Ui::MyWidget) // 创建 UI 指针(Qt Designer 生成的界面类)
{ui->setupUi(this); // 初始化 UI 界面(如果有 .ui 文件的话)this->setWindowTitle("运行"); // 设置窗口标题为“运行”this->resize(300,120); // 设置窗口大小为 300x120 像素QVBoxLayout* vlayout = new QVBoxLayout(this); // 创建一个垂直布局管理器(从上到下排列控件),作用于当前窗口QLabel* info = new QLabel("请输入程序名称"); // 创建一个标签,提示用户输入vlayout->addWidget(info); // 把标签控件加入到垂直布局中QHBoxLayout* hlayout = new QHBoxLayout; // 创建一个水平布局管理器(从左到右排列控件)QLabel* open = new QLabel("打开"); // 创建一个标签,显示文字“打开”hlayout->addWidget(open); // 把标签加入水平布局QLineEdit* edit = new QLineEdit; // 创建一个单行文本输入框hlayout->addWidget(edit); // 把输入框加入水平布局vlayout->addLayout(hlayout); // 把水平布局 hlayout 放到垂直布局 vlayout 中QHBoxLayout* btnlayout = new QHBoxLayout; // 创建一个新的水平布局,用来放置按钮QPushButton* confirmBtn = new QPushButton("确定"); // 创建“确定”按钮QPushButton* cancel = new QPushButton("取消"); // 创建“取消”按钮btnlayout->addWidget(confirmBtn); // 把“确定”按钮加入按钮布局btnlayout->addWidget(cancel); // 把“取消”按钮加入按钮布局vlayout->addLayout(btnlayout); // 把按钮的水平布局加入到垂直布局中} // 构造函数结束
四、信号和槽
- 信号:鼠标点击“取消按钮” 按钮会自动发出信号
- 槽:信号一旦发出了,可以用槽函数和信号绑定,在槽函数中写逻辑
在上述示例中,当我们在图形界面上去点击按钮的过程中,并没有任何反应,如果想要实现相关的功能,需要通过信号
与槽函数
的绑定来实现。
信号(Signals):
- 信号是由Qt对象在特定情况或事件发生时自动发出的通知。例如,当用户点击一个按钮时,按钮对象会发出一个
clicked()
信号。 - 信号本身并不执行任何操作,它是事件的宣告
槽(Slots):
- 槽是普通的C++成员函数,可以与信号关联。当信号被发射时,与之绑定关联的槽函数会被自动调用执行。
- 要想在信号发出时执行槽函数,则需要关联信号与槽函数。
连接(Connect):
- 通过
QObject::connect()
函数,可以将一个信号与一个或多个槽关联起来。当信号被触发时,与之关联的所有槽函数将会被顺序或并发地执行(取决于连接类型)
4.1、信号与槽的绑定方式
QObject::connect() 函数作用:信号和槽机制的连接,允许对象之间通过事件驱动的方式进行通信。
QObject
是 Qt 框架中所有对象的基类(除了一些 GUI 类派生自QWidget
等)。它提供了 Qt 的 元对象系统(Meta-Object System, MOS),支持:
- 信号与槽机制(Signals & Slots)
- 对象树管理(父子关系自动析构)
- 事件处理系统
- 属性系统
所有需要使用信号和槽的类,必须继承自
QObject
,并且要在类中添加Q_OBJECT
宏。例子:
class MyWidget : public QWidget // QWidget 本身继承了 QObject {Q_OBJECT // 启用 Qt 的元对象系统(必须) public:MyWidget(QWidget *parent = nullptr); };
connect()
常见的参数形式:QMetaObject::Connection QObject::connect(const QObject *sender, // 信号发出者对象PointerToMemberFunction signal, // 信号指针const QObject *receiver, // 槽函数所在的对象PointerToMemberFunction slot, // 槽函数指针Qt::ConnectionType type = Qt::AutoConnection // 连接类型(默认自动) );
参数说明:
- sender:发出信号的对象(比如按钮)
- signal:信号的函数指针
- receiver:接收信号的对象(比如窗口)
- slot:槽函数的函数指针
- type:连接方式(可选,常见的有):
Qt::AutoConnection
(默认):如果在同一线程则是直接调用,否则是队列调用Qt::DirectConnection
:立即调用槽函数(同步)Qt::QueuedConnection
:将信号放入事件队列(异步)Qt::BlockingQueuedConnection
:阻塞方式,直到槽函数执行完成Qt::UniqueConnection
:避免重复连接同一个信号和槽
返回值
- 返回值类型是
QMetaObject::Connection
,它是一个轻量级的句柄,代表一次连接。- 可以用它来断开连接:
QMetaObject::Connection conn = connect(button, &QPushButton::clicked, this, &MyWidget::onButtonClicked); disconnect(conn); // 断开
- 如果连接失败,返回一个无效的
QMetaObject::Connection
。
👉总结一句:
QObject
是 Qt 所有对象的基类,提供信号与槽机制。connect()
用来把信号和槽绑定起来,新语法类型安全,返回QMetaObject::Connection
句柄,可以随时断开。
1、基于字符串的连接
这种方式在Qt4版本之前使用较多,它不支持编译时检查。
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
connect(cancelBut, SIGNAL(clicked()), this, SLOT(on_cancelBut_clicked()));
2、使用函数的方式
这是最直观的绑定方式,直接在代码中使用函数。
QObject::connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slot);
connect(commitBut,&QPushButton::clicked, this, &Widget::on_commitBut_clicked);
在 C++ 里,函数名本身并不会自动衰减为指针(不像数组名那样),所以必须显式写 &类名::函数名
来表示函数指针。
3、Lambda表达
Qt 5引入了对lambda表达式的支持,可以用来定义槽函数的行为,这为处理简单逻辑提供了一种简洁的方式。
QObject::connect(sender, &SenderClass::signal, [=]() {// 处理逻辑
});
connect(cancelBut, &QPushButton::clicked, this, [=](){this->close();});
4、使用Qt Creator的“Go To Slot”功能
虽然这不是一种编程上的绑定方式,但在Qt Creator IDE中,你可以通过右击信号并选择“转到槽”(Go To Slot),IDE会自动生成相应的槽函数框架代码并自动完成连接,简化了开发过程。
5、规范槽函数定义简化绑定
- 定义一个按钮,例如按钮的名称:pushButton_1
- 在窗口对象中定义槽函数规则是
on_按钮名称_信号名称
即可绑定
4.2、“Go To Slot” 图解
选择信号,这里我们选择clicked()
单击信号。
🔘 QAbstractButton(QPushButton 的父类)
这些信号和按钮交互有关:
clicked()
/clicked(bool checked)
👉 当按钮被点击时触发(鼠标按下+释放,并且释放时鼠标还在按钮上)。- 普通按钮触发
clicked()
- 可勾选按钮(checkable button,比如复选框/切换按钮)会额外传递当前是否被选中的状态
bool checked
。pressed()
👉 当鼠标左键按下按钮时立即触发。released()
👉 当鼠标左键从按钮上释放时触发。toggled(bool checked)
👉 仅在按钮是 checkable 时有效(比如setCheckable(true)
),每次状态切换都会触发,并传递新的勾选状态。
🖼️ QWidget
这些信号和窗口属性变化有关:
customContextMenuRequested(QPoint)
👉 当用户在窗口(或控件)上点击右键并且设置了setContextMenuPolicy(Qt::CustomContextMenu)
时触发,传递点击位置。windowIconChanged(QIcon)
👉 当窗口图标(setWindowIcon()
)被修改时触发。windowIconTextChanged(QString)
👉 当窗口图标的文字描述(通常用于辅助功能)被修改时触发。windowTitleChanged(QString)
👉 当窗口标题(setWindowTitle()
)被修改时触发。
⚙️ QObject
这些信号和对象本身生命周期或属性变化有关:
destroyed()
/destroyed(QObject\*)
👉 当对象被销毁(delete)时发出,带QObject*
版本会告诉你是哪个对象被销毁了。objectNameChanged(QString)
👉 当对象的名字(setObjectName()
)被修改时触发。
📌 总结一句话:
- 按钮的信号(clicked / pressed / released / toggled) → 和用户交互相关。
- 窗口的信号(title/icon/contextMenu) → 和界面显示属性变化相关。
- QObject的信号(destroyed / nameChanged) → 和对象生命周期及属性变化相关。
添加完成后会自动生成如下代码:
所有的槽函数必须要加slots
关键字
public slots: void on_confirmBut_clicked();
槽函数的实现:
public slots: void on_confirmBut_clicked(){};
接下来我们在槽函数中实现代码,完成开启一个新的进程。在帮助手册中搜索QProcess。
如下代码示例,用来启动一个进程。
代码实现如下:
#include "widget.h"
#include "ui_widget.h"
#include <QProcess>//启动外部程序
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{//初始化窗口ui->setupUi(this);
}
Widget::~Widget()
{delete ui;
}
void Widget::on_confirmBut_clicked()
{// 获取界面中LineEdit单行文件框控件中输入的文本QString program = ui->lineEdit->text();//用于启动外部程序并与它们进行通信QProcess *myProcess = new QProcess(this);//启动进程myProcess->start(program);
}
void MyWidget::cancelFunc()
{this->close();qDebug() << "触发了槽函数";
}
运行项目,在文本框中输入如下内容,点击确定按钮,打开一个记事本程序。
给取消按钮添加信号与槽:
五、QT项目构建的原理
前面项目我们自动实现了信号与槽的连接过程,而UI文件又是如何被转换为C++程序文件的,我们来总结一下项目的构建过程。
一个使用qmake构建系统的Qt项目,除了项目配置文件,还有用C++编写的头文件和源程序文件,以及窗口UI文件和资源文件。在构建项目中,这3类文件会分别编译为标准C++语言的程序文件,然后被标准C++编译器编译成可执行文件或库。如图所示:
1、元对象系统和MOC
Moc 是 Meta-Object Compiler 元对象编译器的缩写,是Qt框架特有的一个预处理器。它的主要任务是处理含有Q_OBJECT宏的C++源文件(通常是头文件)
元对象系统(Meta-Object System),为C++类添加了一层动态特性,使得Qt应用能够实现一些高级功能。比如:信号与槽(Signals and Slots)、事件处理和事件过滤器等。
当一个类声明中包含Q_OBJECT宏时,这个类就具备了Qt的信号与槽机制、运行时类型信息、动态属性系统等特性。这些特性在标准C++中是不直接支持的,因此需要Moc这样的工具来扩展语言的功能。
Moc会读取这样的头文件,并生成一个额外的C++源文件,这个源文件中包含了实现上述特性的代码。生成的文件通常以moc_
开头,然后是原文件名。这个新生成的文件随后会被编译并链接到最终的可执行文件中,使得Qt的元对象系统能够识别和操作这些类。
2、UI文件和UIC
Uic 是 User Interface Compiler用户界面编译器 的简称,用于处理Qt Designer创建的用户界面设计文件(
.ui
文件)
这些.ui
文件是以XML格式存储的,描述了窗口、按钮、文本框等界面元素的布局和属性。Uic读取.ui
文件,并将其转换为C++源代码,这样开发者就可以在他们的应用程序中直接实例化和使用这些界面类。生成的文件通常命名为ui_
加上原UI文件的名字,例如,如果UI文件名为widget.ui
,文件会被转换为ui_widget.h
文件,是一个中间文件,不会出现在项目管理目录树中。
3、资源文件和RCC
Rcc 是 Resource Compiler 的缩写,用于处理资源文件(
.qrc
文件)
Qt项目中的资源文件(.qrc文件)会被资源编译器RCC转换为C++程序文件。
资源文件允许开发者将图像、样式表、翻译文件等二进制数据作为应用程序的一部分打包起来。Rcc读取.qrc
文件,将其中指定的各种资源编译成一个二进制资源文件,该文件在运行时可以通过Qt的资源系统访问。编译后的资源文件通常以.rcc
为扩展名,但更常见的是,它被直接编译成一个C++源文件(.cpp
),这样就可以直接链接到应用程序中,无需单独的.rcc
文件。生成的C++源文件名通常以qrc_
开头,后面跟着资源文件名。
4、标准C++编译器
使用MOC、UIC和RCC编译各原始文件的过程称为预编译过程,预编译之后生成的是标准C++语言的程序文件,它们被标准C++编译器编译和连接,最终生产可执行文件。
六、自定义信号
自定义信号,可以实现在自定义的类中,根据需要定义新的信号类型,以便当特定事件发生时,能够通知并调用其他对象的槽函数。
自定义信号的要求和注意事项:
- 信号是类的成员函数
- 返回值基本是void 类型
- 信号的名字可以根据实际情况进行指定
- 参数可以随意指定, 信号也支持重载
- 信号需要使用 signals 关键字进行声明, 使用方法类似于 public 等关键字
- 信号函数只需要声明,不需要定义(没有函数体实现), 它的作用是 “触发事件”,而具体的处理逻辑由 “槽函数(Slot)” 实现。
- 在程序中发射自定义信号:发送信号的本质就是调用信号函数
- 习惯性在信号函数前加关键字: emit。可以省略不写,但是推荐大家写上,语义更加明确。
- emit只是显示的声明一下信号要被发射了,没有特殊含义
// Qt中的类想要使用信号槽机制必须要从QObject类派生(直接或间接派生都可以)
class Test : public QObject {Q_OBJECT // 支持信号和槽的宏
signals:void testsignal();// 参数的作用是数据传递, 谁调用信号函数谁就指定实参// 实参最终会被传递给槽函数void testsignal(int a);
};
自定义槽函数的要求和注意事项:
返回值必须是 void 类型
槽也是函数,因此支持重载
槽函数需要指定多少个参数,需要看连接的信号的参数个数
槽函数的参数是用来接收信号传递的数据的,信号传递的数据就是信号的参数
举例:
- 信号函数: void testsig(int a, double b);
- 槽函数: void testslot(int a, double b);
总结:
- 槽函数的参数应该和对应的信号的参数个数, 从左到右类型依次对应
- 信号的参数可以大于等于槽函数的参数个数
- 信号函数: void testsig(int a, double b);
- 槽函数: void testslot(int a);
Qt中槽函数的类型是多样的
- Qt中的槽函数可以是类的成员函数、全局函数、静态函数、Lambda表达式(匿名函数)
槽函数可以使用关键字进行声明: slots (Qt5中slots可以省略不写)
- public slots:
- private slots: 这样的槽函数不能在类外部被调用
- protected slots: 这样的槽函数不能在类外部被调用
// 槽函数书写格式举例
// 类中的这三个函数都可以作为槽函数来使用
class Test : public QObject {
public:void testSlot();static void testFunc();
public slots:void testSlot(int id);
};
我们通过自定义信号实现数字自增功能:
1、自定义信号
(1)声明信号
使用signals:
声明信号,信号可以有参数
signals: void change(int value);
(2)发射信号
发射信号使用emit
关键字。
这里我们通过点击一个按钮,发射自定义的信号。
static int num = 0;
ui->lineEdit->setText(QString::number(num)); //给编辑框初始值
connect(ui->pushButton,&QPushButton::clicked,this ,[&](){//发射信号emit change(num++); //用户点击按钮,发射一个信号
});
(3)连接信号和槽
声明槽函数:
private slots: void on_change(int val);
定义槽函数:
void Widget::on_change(int val)
{ui->lineEdit->setText(QString::number(val));
}
连接槽函数:
connect(this, &Widget::change, &Widget::on_change);
2、跨窗口发射信号
在多窗口应用程序中,通常涉及一个窗口或对象中的信号需要被另一个窗口或对象的槽函数接收处理,从而实现跨窗口数据处理。
例如要实现如下功能,在父窗口中点击open
按钮会打开一个子窗口,在子窗口中点击Add
按钮主窗口中的计数自增。需要进行跨窗口的信号处理。
(1)开发子窗口
自定义信号:
signals: void change(int val);
发射信号:
connect(ui->pushButton, &QPushButton::clicked, this, [&](){static int num = 0;emit change(num++);});
(2)父窗口打开子窗口
在父窗口的槽参数汇中打开子窗口,并处理子窗口发出的信号:
void MyWidget_01::on_pushButton_2_clicked(){Dialog dialog; //自定义窗口类,使用#include <Dialog> 引入connect(&dialog, &Dialog::change, [&](int val){ui->lineEdit->setText(QString::number(val));});dialog.setWindowTitle("子窗口");dialog.exec();
}
七、对象树机制
从C++语言规范标准来说,使用new运算符动态申请的空间一定要在不使用的时候进行释放,以免造成泄露。
在Qt框架中提供了对象树机制,极大的简化了对动态申请对象内存空间的维护工作。
对象树机制的原理是,如果一个子对象将另外一个QObject类对象或QObject派生类的对象作为自己的父对象时,那么该子对象就会被添加到父对象的children列表中。当父对象销毁时,对象树机制会从父对象的children列表中取出这些子对象,并依次销毁。
在项目中自定义一个按钮类型MyButton
class MyButton : public QPushButton
{
public:MyButton(QWidget *parent =nullptr);~MyButton();
};
#include "mybutton.h"
MyButton::MyButton(QWidget *parent):QPushButton(parent) {}
MyButton::~MyButton()
{qDebug() << "执行析构函数";
}
在窗口中添加按钮,当窗口关闭时,程序运行结束,窗口对象析构,由于对象树的机制,按钮对象也跟着析构了。
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);MyButton *btn = new MyButton (this);btn->setText("按钮");
}
MainWindow::~MainWindow()
{delete ui;
}