一、项目概述
本文以 Qt Creator 创建的一个标准 Qt Widgets 工程为例,分析以下几个部分:
- Qt 工程文件
.pro的作用与语法 main.cpp、widget.h、widget.cpp之间的关系.ui界面文件的 XML 结构Q_OBJECT、setupUi()、MOC、UIC 等 Qt 核心机制- 当前项目可能存在的问题与优化方向
项目目录结构如下:
untitled/ ├── untitled.pro ├── untitled.pro.user ├── main.cpp ├── widget.h ├── widget.cpp └── widget.ui编译之后,在独立的构建目录中还会生成:
build-untitled-Desktop_Qt_5_9_0_MinGW_32bit-Debug/ ├── Makefile ├── ui_widget.h ├── debug/ │ ├── moc_widget.cpp │ ├── main.o │ ├── widget.o │ └── untitled.exe需要特别注意:
ui_widget.h是由 UIC 根据widget.ui自动生成的moc_widget.cpp是由 MOC 根据Q_OBJECT自动生成的- 这两个文件不能手动修改,否则重新编译后内容会被覆盖
二、untitled.pro:Qt 工程配置文件
untitled.pro是 qmake 工程文件,相当于整个项目的“构建说明书”。
主要内容如下:
QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = untitled TEMPLATE = app DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ main.cpp \ widget.cpp HEADERS += \ widget.h FORMS += \ widget.ui1.QT += core gui
表示项目需要链接 Qt Core 和 Qt GUI 模块。
QtCore:提供字符串、容器、文件、时间和对象机制QtGui:提供字体、颜色、绘图等基础 GUI 能力QtWidgets:提供按钮、标签、输入框等桌面控件
2.greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
这是 qmake 的条件语法:
条件: 满足条件时执行的语句它的含义是:
如果 Qt 主版本号大于 4,就额外加入
widgets模块。
因为在 Qt 5 中,QWidget、QPushButton、QLabel等控件已经从QtGui分离到了独立的QtWidgets模块。
如果是 Qt 4,只需要:
QT += core gui如果是 Qt 5 或 Qt 6,则需要:
QT += core gui widgets3.TARGET = untitled
指定最终生成的可执行程序名称:
untitled.exe4.TEMPLATE = app
表示当前工程类型是应用程序。
常见类型包括:
app:桌面应用程序lib:动态库或静态库subdirs:包含多个子工程
5.DEFINES += QT_DEPRECATED_WARNINGS
定义一个编译宏,让编译器在使用 Qt 已废弃 API 时给出警告。
这有利于后续从 Qt 5 升级到 Qt 6。
6.SOURCES、HEADERS、FORMS
分别声明:
SOURCES += main.cpp widget.cpp HEADERS += widget.h FORMS += widget.uiSOURCES:C++ 源文件HEADERS:头文件FORMS:Qt Designer 界面文件
其中反斜杠\表示下一行仍然属于当前语句。
三、untitled.pro.user:Qt Creator 的本地配置文件
untitled.pro.user不是项目源代码,而是 Qt Creator 自动生成的用户配置文件。
其中保存了:
- 使用的 Qt 版本
- 编译套件
- Debug、Release、Profile 构建目录
- 运行配置
- 调试器配置
- 环境变量配置
当前工程使用的构建套件是:
Desktop Qt 5.9.0 MinGW 32bit对应的 Debug 构建目录是:
C:/Users/dell/Documents/build-untitled-Desktop_Qt_5_9_0_MinGW_32bit-Debug建议不要手动编辑.pro.user。
如果使用 Git 管理项目,通常应该将其加入.gitignore:
*.pro.user *.pro.user.*因为它只适用于当前电脑和当前 Qt Creator 配置,不适合提交到代码仓库。
四、main.cpp:程序入口
完整代码如下:
#include "widget.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; w.show(); return a.exec(); }1. 引入头文件
#include "widget.h" #include <QApplication>- 引号形式
"widget.h":通常用于当前项目中的自定义头文件 - 尖括号形式
<QApplication>:通常用于标准库或 Qt 官方库头文件
2. 创建 Qt 应用程序对象
QApplication a(argc, argv);QApplication是 Qt Widgets 程序的应用程序对象,负责:
- 管理 GUI 程序生命周期
- 处理窗口系统事件
- 分发鼠标、键盘和窗口事件
- 管理应用程序级设置
对于有界面的 Qt Widgets 程序,main()中必须创建一个QApplication对象。
3. 创建主窗口控件
Widget w;这里的Widget是自定义类,继承自QWidget。
对象采用栈内存创建,因此程序结束时会自动析构,不需要手动delete。
4. 显示窗口
w.show();调用show()后,窗口才会真正显示。
5. 进入事件循环
return a.exec();exec()会进入 Qt 事件循环,程序会持续等待并处理用户操作,例如:
- 点击按钮
- 输入文字
- 关闭窗口
- 定时器触发
如果没有调用exec(),窗口可能会一闪而过,程序也不会持续响应用户操作。
五、widget.h:Widget 类声明
完整代码如下:
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); private: Ui::Widget *ui; }; #endif // WIDGET_H1. 头文件保护
#ifndef WIDGET_H #define WIDGET_H // 头文件内容 #endif这是传统的 Include Guard,用于防止同一个头文件被重复包含,避免重复定义错误。
2. 前置声明
namespace Ui { class Widget; }这里只是告诉编译器:
Ui命名空间中存在一个名为Widget的类。
具体定义不在这里展开,从而减少头文件之间的依赖。
真正的Ui::Widget定义由 UIC 自动生成在ui_widget.h中。
3. 继承 QWidget
class Widget : public QWidget表示Widget是一个 Qt 窗口部件,具有窗口、布局、事件和控件管理能力。
类似的常见基类包括:
QWidget:通用控件或自定义窗口QMainWindow:带菜单栏、工具栏、状态栏的主窗口QDialog:对话框
4.Q_OBJECT宏
Q_OBJECT这是 Qt 元对象系统的核心宏。
它让类可以使用:
- 信号与槽
tr()国际化- 运行时类型信息
- 动态属性
只要类中写了Q_OBJECT,qmake 就会调用 MOC,自动生成类似下面的文件:
moc_widget.cpp如果删除这个宏,代码可能仍然能编译,但是信号、槽等 Qt 特性会失效。
5. 构造函数
explicit Widget(QWidget *parent = 0);explicit防止构造函数被隐式类型转换调用parent表示父控件- 传入父控件后,子控件会自动加入父子对象树,由父对象管理生命周期
当前代码使用0表示空指针。在现代 C++ 中也可以写成:
explicit Widget(QWidget *parent = nullptr);6. UI 指针
private: Ui::Widget *ui;ui是指向自动生成界面类的指针。
通过它,可以访问.ui文件中定义的控件,例如:
ui->cmdLineEdit ui->cancelButton ui->browseButton六、widget.cpp:界面初始化
完整代码如下:
#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); } Widget::~Widget() { delete ui; }1. 引入自动生成的 UI 头文件
#include "ui_widget.h"ui_widget.h是由 UIC 根据widget.ui生成的。
它里面会定义:
class Ui_Widget { public: QLabel *label; QLabel *label_2; QLineEdit *cmdLineEdit; QPushButton *pushButtocommitButtonn; QPushButton *cancelButton; QPushButton *browseButton; void setupUi(QWidget *Widget); void retranslateUi(QWidget *Widget); };然后通过命名空间包装:
namespace Ui { class Widget : public Ui_Widget {}; }因此项目代码中可以使用Ui::Widget。
2. 初始化列表
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)冒号后面的部分称为初始化列表。
它完成了两件事:
- 调用父类
QWidget的构造函数 - 使用
new创建 UI 对象
这比在构造函数函数体内赋值更加规范,也符合 C++ 对象初始化顺序。
3. 构建界面
ui->setupUi(this);setupUi()是 UIC 自动生成的核心函数,它的主要作用是:
- 创建
.ui中定义的控件 - 设置控件位置和大小
- 设置字体、标题和文字
- 设置布局
- 把控件加入父对象
例如,widget.ui中的:
<widget class="QLineEdit" name="cmdLineEdit">最终会在setupUi()中生成类似代码:
cmdLineEdit = new QLineEdit(Widget); cmdLineEdit->setObjectName(QStringLiteral("cmdLineEdit")); cmdLineEdit->setGeometry(QRect(130, 100, 113, 21));4. 析构函数
Widget::~Widget() { delete ui; }程序销毁Widget对象时,需要释放动态创建的ui对象。
不过,界面中的控件一般会由 Qt 父子对象机制自动释放,不需要逐个delete。
七、widget.ui:XML 界面文件
widget.ui是 Qt Designer 使用的 XML 文件。
它的核心结构可以简化为:
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>Widget</class> <widget class="QWidget" name="Widget"> <!-- 属性 --> <!-- 子控件 --> </widget> </ui>1. XML 声明
<?xml version="1.0" encoding="UTF-8"?>表示当前文件使用 XML 1.0 格式,编码是 UTF-8。
因此中文可以正常保存,例如:
<string>确定</string>2. 根节点
<ui version="4.0">表示这是 Qt Designer UI 文件,格式版本为 4.0。
3. 界面类名
<class>Widget</class>对应 C++ 中的 XML 类名。
4. 根控件
<widget class="QWidget" name="Widget">表示根控件类型为QWidget,对象名为Widget。
5. 窗口尺寸与标题
<property name="geometry"> <rect> <x>0</x> <y>0</y> <width>400</width> <height>300</height> </rect> </property> <property name="windowTitle"> <string>Widget</string> </property>表示窗口初始大小为 400 × 300,标题为Widget。
6. 当前项目中的控件
| 控件类型 | 对象名 | 作用 |
|---|---|---|
QLabel | label | 显示“请输入命令” |
QLabel | label_2 | 显示“打开:” |
QLineEdit | cmdLineEdit | 输入命令或路径 |
QWidget | 未命名 | 按钮区域容器 |
QPushButton | pushButtocommitButtonn | 确定按钮 |
QPushButton | cancelButton | 取消按钮 |
QPushButton | browseButton | 浏览按钮 |
QHBoxLayout | horizontalLayout | 水平排列三个按钮 |
7. QLabel 中的 HTML 文本
例如:
<string><html><body>请输入命令</body></html></string>XML 中的:
< 表示 < > 表示 > " 表示 "Qt 的QLabel支持富文本,因此可以直接使用简单 HTML 设置居中、字号和颜色。
8. 按钮布局
<layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QPushButton" name="pushButtocommitButtonn"> <property name="text"> <string>确定</string> </property> </widget> </item> </layout>QHBoxLayout表示水平布局,三个按钮会从左到右排列。
八、UIC 与 MOC 的工作过程
这个项目涉及两个非常重要的 Qt 自动生成工具。
1. UIC:处理.ui文件
widget.ui ↓ UIC ui_widget.hUIC 是 Qt User Interface Compiler。
它把 XML 界面文件转换成 C++ 代码。
项目中的:
#include "ui_widget.h"使用的就是生成后的文件。
2. MOC:处理Q_OBJECT
widget.h ↓ MOC moc_widget.cppMOC 是 Meta-Object Compiler。
由于Widget中包含:
Q_OBJECTMOC 会生成与信号、槽和元对象相关的 C++ 代码。
3. 完整构建流程
untitled.pro ↓ qmake Makefile ↓ UIC + MOC + C++ Compiler ↓ 目标文件 ↓ 链接 Qt 库 ↓ untitled.exe九、当前项目存在的问题
1. 按钮没有绑定任何功能
目前widget.ui中:
<connections/>是空的,C++ 代码中也没有调用connect()。
因此点击“确定”“取消”“浏览”暂时不会产生任何效果。
可以通过信号与槽添加功能,例如:
connect(ui->browseButton, &QPushButton::clicked, this, [this]() { QString path = QFileDialog::getOpenFileName(this, "选择文件"); if (!path.isEmpty()) { ui->cmdLineEdit->setText(path); } });需要额外引入:
#include <QFileDialog>2. 按钮对象名存在疑似拼写错误
当前对象名是:
pushButtocommitButtonn其中Butto和末尾的Buttonn都可能属于拼写问题。
更规范的命名建议是:
confirmButton如果修改对象名,需要重新生成ui_widget.h,同时修改所有引用该控件的 C++ 代码。
3. 大量使用绝对坐标
当前界面主要通过geometry设置位置和大小:
<property name="geometry"> <rect> <x>110</x> <y>40</y> <width>161</width> <height>41</height> </rect> </property>这种方式适合简单练习,但窗口缩放后布局不容易自适应。
更推荐使用:
QVBoxLayoutQHBoxLayoutQGridLayoutQFormLayout
例如,可以把“请输入命令”、输入框和按钮区域组合成垂直布局。
4..pro.user不应提交到 Git
建议在.gitignore中加入:
*.pro.user *.pro.user.*源码目录保留.pro、.cpp、.h和.ui即可。
十、Qt 核心语法总结
| 语法 | 含义 |
|---|---|
QT += core gui widgets | 引入 Qt 模块 |
TARGET = untitled | 指定生成程序名称 |
TEMPLATE = app | 指定工程类型 |
SOURCES += | 添加源文件 |
HEADERS += | 添加头文件 |
FORMS += | 添加.ui界面文件 |
class Widget : public QWidget | 继承 Qt 控件类 |
Q_OBJECT | 启用 Qt 元对象系统 |
Ui::Widget *ui | 指向自动生成的界面类 |
ui->setupUi(this) | 根据.ui构建界面 |
new Ui::Widget | 创建界面对象 |
delete ui | 释放界面对象 |
QApplication | 管理整个 GUI 程序 |
a.exec() | 启动事件循环 |
show() | 显示窗口 |
connect() | 连接信号与槽 |
十一、总结
这个untitled工程虽然代码量不大,但它已经包含了 Qt Widgets 项目最基础的完整结构:
.pro 负责构建配置 main.cpp 负责程序启动 widget.h 负责类声明 widget.cpp 负责界面初始化 widget.ui 负责可视化界面描述 MOC 负责元对象代码 UIC 负责界面代码理解它们之间的关系后,就可以继续扩展:
- 为按钮添加信号与槽
- 使用
QFileDialog实现文件选择 - 使用布局替代绝对坐标
- 增加业务逻辑类
- 使用
QProcess执行外部命令 - 使用
.qrc管理图片和样式资源 - 使用
QSettings保存配置 - 将功能拆分为多个模块和子工程
这个项目可以作为一个 Qt Widgets 入门模板,用来继续学习 Qt 的对象树、事件循环、信号与槽、布局系统和 qmake 构建机制。