https://www.bilibili.com/video/BV1cW4y1y7Lw
在上一节课中,我们新建了第一个 Qt
工程,其中包括 5
个文件:
其中,前两个文件,已经为大家做了详细讲解,这节课先来看后 3 个文件,然后再为大家详细讲解整个项目的构建流程
在 main.cpp
中,除了 QApplication
进入事件的循环处理以外,还会创建一个 MyWindow
的对象,并显示出来,如下:
int main(int argc, char *argv[])
{
...
// 创建 MyWindow 窗口对象, 并调用其 show 方法,将窗口显示出来
MyWindow w;
w.show();
...
}
接下来,我们按住 Ctrl
键的同时,点击 MyWindow
类,跳转到该类的定义处(mywindow.h
),如下:
#include
QT_BEGIN_NAMESPACE
// 在此声明一个MyWindow类,这个类定义在 Ui 命名空间中
// 因为下面会定义一个 Ui::MyWindow 类型的指针 *ui
namespace Ui { class MyWindow; }
QT_END_NAMESPACE
// 我们自定义的 MyWindow 类,要继承自Qt框架提供的QMainWindow/QDialog/QWidget这三个类其中之一,才可以正常显示出来
class MyWindow : public QMainWindow
{
Q_OBJECT
public:
MyWindow(QWidget *parent = nullptr);
~MyWindow();
private:
// 定义一个 Ui::MyWindow 类型的指针 *ui
// Ui::MyWindow 这个类定义在 ui_mywindow.h 中(可以 Ctrl+单击 跳转过去)
// 这个 Ui::MyWindow 类,本身是空实现,但是它继承自 Ui_MyWindow 类
// Ui_MyWindow 类,是和UI设计界面一一对应的,也就和mywindow.ui这个xml文件一一对应的
// 至于为什么一个C++代码中类的对象,会和mywindow.ui这个xml文件一一对应,本节课下面就会讲到,暂且知道即可
Ui::MyWindow *ui;
};
接下来,来到 mywindow.cpp
文件:
MyWindow::MyWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MyWindow) // 在初始化列表中,对ui指针进行初始化赋值
{
// 并将当前窗口,也就是this指针,设置到ui对象中
// 只有这样,我们在ui界面中拖拽的控件才会在随MyWindow显示出来。
ui->setupUi(this);
// 之后就可以对拖拽的那些控件进行各种操作了,比如下面设置按钮显示的文字。
ui->btn_start->setText("启动");
ui->btn_stop->setText("停止");
}
MyWindow::~MyWindow()
{
// 既然在构造函数中 new 了ui对象,在析构中要delete销毁
delete ui;
}
到此,我们基本了解了窗口是如何显示的,其实还没有百分百了解,比如你还不知道
ui_mywindow.h
这个文件是怎么来的?Ui::MyWindow
这个c++ 中的类,是和 xml 格式的 mywindow.ui
文件一一对应的?ui->setupUi(this)
; 才可以将界面上拖拽的按钮显示出来别着急,下面马上就为你揭秘
在揭秘之前,有必要了解下 Qt
项目的构建流程
上一节中,我们使用向导创建了第一个 Qt 工程,在 Qt Creator
集成开发环境中,一个按钮或者一个快捷键,就可以完成项目的构建和运行。
但是,这些看起来简单的过程,背后到底发生了什么呢?
作为一名专业的程序员,我们有必要探讨一番,这样即使以后出现了问题,编译报错,我们也能很快地定位到问题所在。
在了解项目构建流程之前,有必要先看几个概念:
makefile
make
qmake
作为一名 Qt 开发者,需要有 C/C++ 基础,想必对这几个概念并不陌生,这里做一个总结
对于一个源文件 main.cpp,如何编译成可执行文件呢?
g++ -o main main.cpp
每当 main.cpp
修改之后,就需要重新执行以上编译命令
更简单地,可以新建一个 Makefile
文件,其中内容如下:
main:main.cpp
g++ -o main main.c
此时,在命令行直接执行 make
就可以直接执行 Makefile
,进而执行 g++ -o main main.c
来编译出可执行文件 main
可见,引入了Makefile
文件之后,只需执行make
命令即可
以上只有一个源文件 main.cpp
,然而一个实际的工程中,其源文件一般有有很多,并且通常按照功能模块,放在若干个目录中
此时如果每次都手动一条条执行编译命令,很显然是不现实的,因此就有了Makefile
Makefile
文件用于描述整个工程的编译、链接的规则。比如,工程中的哪些源文件需要编译(根据时间戳,只编译修改过的文件)以及如何编
译、编译顺序,最终链接成目标的可执行文件或目标的库文件
Makefile
有自己的书写格式、关键字、函数,像 C 语言有自己的格式、关键字和函数一样。
Makefile
文件编写完毕之后,编译整个工程你所要做的唯一的一件事就是在 shell
窗口下输入 make
命令,就可以将整个工程进行 “自动化编译”
,极大提高了效率。
make
是一个命令工具,在 shell
窗口下执行 make
命令,它会自动读取并解释 makefile
中指令,来完成整个工程的自动编译,极大的提高了软件开发的效率。
手写 Makefile
是比较困难而且容易出错,尤其在进行跨平台开发时,必须针对不同平台分别编写 Makefile
,会增加跨平台开发复杂性与困难度。
qmake
会根据项目文件(.pro
)里面的信息,自动生成 Makefile
。
因此在 Qt 开发时,我们只需编写 .pro 文件即可,因为 qmake
会自动根据.pro
文件生成 Makefile
因此,Qt 项目构建的基本流程是:
qmake
执行 qmake 生成 Makefile
make
执行 make 命令,编译出可执行程序
运行
运行可执行程序
了解了 Makefile
、make
、qmake
的基本概念之后,我们知道 Qt 项目的构建流程:
qmake
-> make
-> run
接下来看下,上一节的 HelloQt
项目是如何编译出可执行文件,并运行呈现出窗口界面的
如果想为生成的目标程序,添加命令行参数,那么需要用到项目模式的运行设置
此时可以在main.cpp
中,添加如下代码来输出命令行参数,如下:
qDebug() << "参数个数:" << argc;
qDebug() << "参1:" << argv[0];
qDebug() << "参2:" << argv[1];
qDebug() << "参3:" << argv[2];
执行结果:
参数个数: 3
参1: E:\qt_project\HelloQt\debug\HelloQt.exe
参2: 123
参3: 456
构建设置和运行设置之后,来看项目构建时,【编译输出】窗口的输出,如下:
# 1、qmake 生成 Makefile
23:22:27: 正在启动 "C:\Qt\5.15.2\mingw81_32\bin\qmake.exe" E:\qt_project\HelloQt\HelloQt.pro -spec win32-g++ "CONFIG+=debug" "CONFIG+=qml_debug"
23:22:28: 进程"C:\Qt\5.15.2\mingw81_32\bin\qmake.exe"正常退出。
23:22:28: 正在启动 "C:\Qt\Tools\mingw810_32\bin\mingw32-make.exe" -f E:/qt_project/HelloQt/Makefile qmake_all
mingw32-make: Nothing to be done for 'qmake_all'.
23:22:28: 进程"C:\Qt\Tools\mingw810_32\bin\mingw32-make.exe"正常退出。
23:22:28: 正在启动 "C:\Qt\Tools\mingw810_32\bin\mingw32-make.exe" -j4
C:/Qt/Tools/mingw810_32/bin/mingw32-make -f Makefile.Debug
mingw32-make[1]: Entering directory 'E:/qt_project/HelloQt'
# 2、执行 uic.exe,将ui文件(xml格式),转换为代码文件(.h)
C:\Qt\5.15.2\mingw81_32\bin\uic.exe mywindow.ui -o ui_mywindow.h
# 3、g++ 开始编译,以生成目标文件
g++ -c -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -fexceptions -mthreads -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN -I. -IC:\Qt\5.15.2\mingw81_32\include -IC:\Qt\5.15.2\mingw81_32\include\QtWidgets -IC:\Qt\5.15.2\mingw81_32\include\QtGui -IC:\Qt\5.15.2\mingw81_32\include\QtANGLE -IC:\Qt\5.15.2\mingw81_32\include\QtCore -Idebug -I. -IC:\Qt\5.15.2\mingw81_32\mkspecs\win32-g++ -o debug\main.o main.cpp
g++ -c -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -fexceptions -mthreads -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN -I. -IC:\Qt\5.15.2\mingw81_32\include -IC:\Qt\5.15.2\mingw81_32\include\QtWidgets -IC:\Qt\5.15.2\mingw81_32\include\QtGui -IC:\Qt\5.15.2\mingw81_32\include\QtANGLE -IC:\Qt\5.15.2\mingw81_32\include\QtCore -Idebug -I. -IC:\Qt\5.15.2\mingw81_32\mkspecs\win32-g++ -o debug\mywindow.o mywindow.cpp
g++ -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -dM -E -o debug\moc_predefs.h C:\Qt\5.15.2\mingw81_32\mkspecs\features\data\dummy.cpp
C:\Qt\5.15.2\mingw81_32\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN --include E:/qt_project/HelloQt/debug/moc_predefs.h -IC:/Qt/5.15.2/mingw81_32/mkspecs/win32-g++ -IE:/qt_project/HelloQt -IC:/Qt/5.15.2/mingw81_32/include -IC:/Qt/5.15.2/mingw81_32/include/QtWidgets -IC:/Qt/5.15.2/mingw81_32/include/QtGui -IC:/Qt/5.15.2/mingw81_32/include/QtANGLE -IC:/Qt/5.15.2/mingw81_32/include/QtCore -IC:/Qt/Tools/mingw810_32/lib/gcc/i686-w64-mingw32/8.1.0/include/c++ -IC:/Qt/Tools/mingw810_32/lib/gcc/i686-w64-mingw32/8.1.0/include/c++/i686-w64-mingw32 -IC:/Qt/Tools/mingw810_32/lib/gcc/i686-w64-mingw32/8.1.0/include/c++/backward -IC:/Qt/Tools/mingw810_32/lib/gcc/i686-w64-mingw32/8.1.0/include -IC:/Qt/Tools/mingw810_32/lib/gcc/i686-w64-mingw32/8.1.0/include-fixed -IC:/Qt/Tools/mingw810_32/i686-w64-mingw32/include mywindow.h -o debug\moc_mywindow.cpp
g++ -c -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -fexceptions -mthreads -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN -I. -IC:\Qt\5.15.2\mingw81_32\include -IC:\Qt\5.15.2\mingw81_32\include\QtWidgets -IC:\Qt\5.15.2\mingw81_32\include\QtGui -IC:\Qt\5.15.2\mingw81_32\include\QtANGLE -IC:\Qt\5.15.2\mingw81_32\include\QtCore -Idebug -I. -IC:\Qt\5.15.2\mingw81_32\mkspecs\win32-g++ -o debug\moc_mywindow.o debug\moc_mywindow.cpp
g++ -Wl,-subsystem,windows -mthreads -o debug\HelloQt.exe debug/main.o debug/mywindow.o debug/moc_mywindow.o C:\Qt\5.15.2\mingw81_32\lib\libQt5Widgets.a C:\Qt\5.15.2\mingw81_32\lib\libQt5Gui.a C:\Qt\5.15.2\mingw81_32\lib\libQt5Core.a -lmingw32 C:\Qt\5.15.2\mingw81_32\lib\libqtmain.a -LC:\openssl\lib -LC:\Utils\my_sql\mysql-5.7.25-win32\lib -LC:\Utils\postgresql\pgsql\lib -lshell32
mingw32-make[1]: Leaving directory 'E:/qt_project/HelloQt'
23:22:35: 进程"C:\Qt\Tools\mingw810_32\bin\mingw32-make.exe"正常退出。
23:22:35: Elapsed time: 00:11.
uic
用于将 ui
文件转换为 .h
文件
我们知道在编译项目时,编译器编译的是 cpp/.h
文件,而 ui
文件本身是 xml
格式的,因此需要将 ui
文件转换为编译器可编译的 cpp/.h
文件
根据以上的编译输出可知,uic
工具会将 mywindow.ui
转换为 ui_mywindow.h
,如下:
class Ui_MyWindow
{
public:
QWidget *centralwidget;
QPushButton *btn_start;
QPushButton *btn_stop;
QMenuBar *menubar;
QStatusBar *statusbar;
void setupUi(QMainWindow *MyWindow)
{
if (MyWindow->objectName().isEmpty())
MyWindow->setObjectName(QString::fromUtf8("MyWindow"));
MyWindow->resize(800, 600);
// 只有将 centralwidget 的父窗口设置为我们自己定义的MyWindow,才能在MyWindow show出来时,才可以将centralwidget也显示出来
centralwidget = new QWidget(MyWindow);
centralwidget->setObjectName(QString::fromUtf8("centralwidget"));
// 同理,只有将 btn 的父窗口设置为centralwidget,才能在centralwidget显示出来时,才可以将btn也显示出来
btn_start = new QPushButton(centralwidget);
btn_start->setObjectName(QString::fromUtf8("btn_start"));
btn_start->setGeometry(QRect(270, 260, 75, 24));
btn_stop = new QPushButton(centralwidget);
btn_stop->setObjectName(QString::fromUtf8("btn_stop"));
btn_stop->setGeometry(QRect(400, 260, 75, 24));
MyWindow->setCentralWidget(centralwidget);
// 同理,只有将 menubar 的父窗口设置为我们自己定义的MyWindow,才能在MyWindow show出来时,才可以将menubar也显示出来
menubar = new QMenuBar(MyWindow);
menubar->setObjectName(QString::fromUtf8("menubar"));
menubar->setGeometry(QRect(0, 0, 800, 22));
MyWindow->setMenuBar(menubar);
// 同理,只有将 statusbar 的父窗口设置为我们自己定义的MyWindow,才能在MyWindow show出来时,才可以将 statusbar 也显示出来
statusbar = new QStatusBar(MyWindow);
statusbar->setObjectName(QString::fromUtf8("statusbar"));
MyWindow->setStatusBar(statusbar);
retranslateUi(MyWindow);
QMetaObject::connectSlotsByName(MyWindow);
} // setupUi
void retranslateUi(QMainWindow *MyWindow)
{
MyWindow->setWindowTitle(QCoreApplication::translate("MyWindow", "MyWindow", nullptr));
btn_start->setText(QCoreApplication::translate("MyWindow", "start", nullptr));
btn_stop->setText(QCoreApplication::translate("MyWindow", "stop", nullptr));
} // retranslateUi
};
namespace Ui {
// 这个Ui命名空间中的 MyWindow 类,本身是空实现,但是它继承自 Ui_MyWindow 类
// Ui_MyWindow 类就是和 mywindow.ui 这个xml文件一一对应的。
// 比如在在ui设计界面拖放两个按钮,可以在其xml文件中看到两个按钮的属性
// uic 工具就会根据xml文件中的属性,在 ui_mywindow.h 文件中,生成对应的代码
// 比如在ui设计界面设置两个按钮的显示文字为“start”和“stop”,那么在生成的这个.h文件中,就会生成对应的代码,如上↑
class MyWindow: public Ui_MyWindow {};
} // namespace Ui
至此,我们重新回到上面的三个问题,应该就可以回答了
ui_mywindow.h
这个文件是怎么来的?
答:通过 Qt 提供的 uic
工具,自动将 .ui
文件,转换为编译器可以编译的 .h
文件
为什么说 Ui::MyWindow
这个 c++ 中的类,是和 xml
格式的 mywindow.ui
文件一一对应的?
答:uic
工具会去解析 .ui
的 xml
格式文件,依次读取其中的字段,比如 width
、height
、property
等,来生成对应的 c++
代码
为什么上面需要调用 ui->setupUi(this);
才可以将界面上拖拽的按钮显示出来
答:我们自己定义的 MyWindow
要显示,直接调用其 show
方法,如果子控件要随我们的窗口一起显示,那么子控件创建时的父窗口就要指定为我们的 MyWindow
到此,项目构建流程就讲解完了,可能有些小伙伴有点蒙,建议大家,自己跳转下代码,多看几遍视频,相信你肯定就会恍然大悟的!
https://www.bilibili.com/video/BV1cW4y1y7Lw