GUI程序结构与运行机制
项目配置文件¶
创建项目时,如果选择qmake构建系统,就会生成后缀为“.pro”的项目配置文件,文件名就是项目的名称。
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
widget.cpp
HEADERS += \
widget.h
FORMS += \
widget.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
qmake是构建项目的工具软件,qmake的作用是根据项目配置文件中的设置生成Makefile文件,然后C++编译器就可以根据Makefile文件进行编译和连接。对于Qt项目,qmake还会自动为元对象编译器(meta-object compiler,MOC)和用户界面编译器(user interface compiler,UIC)生成构建规则。
Qt项目的配置文件是自动生成的,一般不需要手动修改,但需要读懂。其中,“#”用于标识注释语句,一些全大写的单词是配置文件中的变量。
变量 | 含义 |
---|---|
QT | 项目中使用的 Qt 模块列表,在用到某些模块时需要手动添加 |
CONFIG | 项目的通用配置选项 |
DEFINES | 项目中的预处理定义列表,例如可以定义一些用于预处理的宏 |
TEMPLATE | 项目使用的模板,项目模板可以是应用程序(app)或库(lib)。如果不设置就默认为应用程序 |
HEADERS | 项目中的头文件(.h 文件)列表 |
SOURCES | 项目中的源程序文件(.cpp 文件)列表 |
FORMS | 项目中的 UI 文件(.ui 文件)列表 |
RESOURCES | 项目中的资源文件(.qrc 文件)列表 |
TARGET | 项目构建后生成的应用程序的可执行文件名称,默认与项目名称相同 |
DESTDIR | 目标可执行文件的存放路径 |
INCLIDEPATH | 项目用到的其他头文件的搜索路径列表 |
DEPENDPATH | 项目其他依赖文件(如源程序文件)的搜索路径列表 |
当我们向项目中添加文件时,用于文件和路径管理的变量会自动更新。
如果项目需要用到Qt框架中的一些附加模块,需要再配置文件中将模块加入QT
变量
qmake提供替换函数(replace function)用于在配置过程中处理变量或内置函数的值,$$
是替换函数的前缀,后面可以是变量名或qmake的一些内置函数。$${TARGET}
等于 $$TARGET
。
UI 文件¶
后缀为“.ui”的文件是用于窗口界面可视化设计的文件,双击该文件,Qt Creator会打开内置的Qt Designer对窗口界面进行可视化设计, 而不需要写任何代码 。
- Action Editor:可视化设计Action
- Signals and Slots Editor:可视化进行信号与槽的关联
主程序文件¶
main()
函数是C++程序的入口,主要功能是定义并创建应用程序,定义并创建窗口对象和显示窗口,运行应用程序,开始应用程序的消息循环和事件处理。
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
QApplication
是Qt的标准应用程序类,main()
函数中就定义了该类型的变量a
,它就是应用程序对象。Widget
是该项目中设计的窗口的类名称,定义变量w
是创建窗口对象,w.show()
显示此窗口a.exec()
启动应用程序,开始应用程序的消息循环和事件处理。GUI应用程序是事件驱动的,窗口上的各种组件接收鼠标或键盘的操作,并进行相应的处理。
窗口相关文件¶
若是取消勾选Shadow build
复选框,然后以Release模式构建项目,项目的根目录下会生成一个ui_widget.h文件。这样,存在四个与窗口相关的文件:
文件 | 说明 |
---|---|
widget.h | 定义了窗口类 Widget |
widget.cpp | 实现 Widget 类的功能的源程序文件 |
widget.ui | 窗口 UI 文件,用于在 Qt Designer 中进行 UI 可视化设计。widget.ui 是一个 XML 文件,存储界面上各个组件的属性和布局内容 |
ui_widget.h | UI 文件经过 UIC 编译后生成的文件,这个文件里定义了一个类,类的名称是 Ui_Widget,用 C++ 语言描述 UI 文件中界面组件的属性设置、布局以及信号与槽的关联等内容 |
1. widget.h¶
在创建项目时,我们选择窗口基类是 QWidget,文件widget.h中定义了一个继承自 QWidget的类Widget。
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget;}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr); //构建函数
~Widget(); //析构函数
private:
Ui::Widget *ui; //使用Ui::Widget类定义的一个对象指针
};
#endif // WIDGET_H
-
窗口UI类Ui::Widget的声明
代码声明了一个名称为Ui的名字空间(namespace),它包含一个类Widget。注意,这个类Widget 并不是本文件的代码里定义的类Widget,而是文件 ui_widget.h 里定义的一个类,是用于描述可视化设计的窗口界面的,所以我们称Ui::Widget类是窗口UI类。这种声明相当于外部类型声明,需要了解对文件ui_widget.h的解释之后才能搞明白。
QT_BEGIN_NAMESPACE namespace Ui { class Widget;} QT_END_NAMESPACE
-
窗口类Widget的定义
文件widget.h的主体部分是一个继承自QWidget的类Widget的定义——
class Widget : public QWidget
。Widget类中包含创建窗口界面,实现窗口上界面组件的交互操作,以及其他业务逻辑。Widget 类定义的第一行语句插入了一个宏Q_OBJECT,这是使用Qt 元对象系统的类时必须插入的一个宏。插入了这个宏之后,Widget类中就可以使用信号与槽、属性等功能。
Widget 类的private 部分定义了一个指针,代码如下:
这个指针是用前面声明的名字空间Ui里的类Widget定义的,所以ui是窗口UI类的对象针,它指向可视化设计的窗口界面。要访问界面上的组件,就需要通过这个指针来实现。Ui::Widget *ui;
命名空间
在C++中,命名空间(Namespace)是一种组织代码的方式,它允许你将相关的代码片段(如函数、类、变量等)分组在一起,并且避免命名冲突。想象一下,如果你在一个大城市里,每个人都叫“张三”,那会引起多少混乱!命名空间的作用就是防止这种混乱。
为什么需要命名空间?
在大型项目中,可能会有多个库或模块,它们可能包含同名的函数或类。如果没有命名空间,这些同名的实体就会相互冲突,导致编译器无法区分它们。命名空间通过给这些实体添加一个“前缀”来解决这个问题。
如何使用命名空间?
在C++中,你可以使用namespace
关键字来定义一个命名空间。下面是一个简单的例子:
namespace MyProject {
void function() {
std::cout << "This is a function in MyProject namespace." << std::endl;
}
}
在这个例子中,我们定义了一个名为MyProject
的命名空间,里面包含了一个名为function
的函数。
如何访问命名空间中的实体?
要访问命名空间中的实体,你需要在实体名前加上命名空间的名称,像这样:
MyProject::function();
使用using
声明
如果你经常需要访问某个命名空间中的多个实体,每次都写完整的命名空间名称可能会很麻烦。C++允许你使用using
声明来简化这个过程:
using namespace MyProject;
//引入整个 `MyProject` 命名空间,使得在该命名空间下的所有名称都可以直接使用
using MyProject::function;
//只引入 `MyProject` 命名空间下的 `function` 函数
function(); // 直接调用,不需要前缀
命名空间的嵌套
你可以在一个命名空间内部定义另一个命名空间,就像这样:
namespace Outer {
namespace Inner {
void function() {
std::cout << "This is a function in Outer::Inner namespace." << std::endl;
}
}
}
要访问这个函数,你需要指定完整的路径:
Outer::Inner::function();
总结
命名空间是C++中管理代码和避免命名冲突的重要工具。通过合理使用命名空间,你可以使你的代码更加清晰、有组织,并且减少命名冲突的可能性。想象一下,如果每个城市都有自己的区域代码,比如“010”代表北京,“021”代表上海,那么打电话时就不会混淆了。命名空间在C++中的作用也是类似的。
2. 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;
}
-
#include "ui_widget.h"
该文件是UI文件 widget.ui 被UIC编译后生成的文件,这个文件里定义了窗口UI类。 -
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
Widget
继承自QWidget
。当你创建一个Widget
对象时,不仅需要初始化Widget
类特有的部分(如ui
指针),还需要初始化从QWidget
继承来的部分因此,这代码运行父类QWidget的构造函数,并传递了
parent
参数。且初始化了Widget
类中的ui
指针,分配了一个新的Ui::Widget
对象,并将其地址赋给ui
指针。 -ui->setupUi(this);
this
就是Widget
类对象的实例,也就是一个窗口。setupUi()
函数里会创建窗口上所有的界面组件, 并且以Widget窗口作为所有组件的父容器,这需要看了后面的代码才能明白。3. widget.ui¶
文件widget.ui 是窗口界面定义文件,是一个XML文件。它存储了界面上所有组件的属性设 置、布局、信号与槽函数的关联等内容。
用Qt Designer打开UI文件进行窗口界面可视化设计, 保存修改后会重新生成UI文件,所以,我们不用关注文件widget.ui 是怎么生成的,也不用关注此文件内容的意义。
4. ui_widget.h¶
在构建项目时,UI文件widget.ui会被Qt的UIC编译为对应的C++语言头文件ui_widget.h。 这个文件并不会出现在Qt Creator 的项目管理目录树里,它是构建项目时的一个中间文件。文件 ui_widget.h 的完整内容如下(加注释版)。
/********************************************************************************
** Form generated from reading UI file 'widget.ui'
** Created by: Qt User Interface Compiler version 6.2.3
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_WIDGET_H
#define UI_WIDGET_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_Widget
{
public:
QLabel *labDemo;
QPushButton *btnClose;
void setupUi(QWidget *Widget)
{
//设置Widget的一些属性
if (Widget->objectName().isEmpty())
Widget->setObjectName(QString::fromUtf8("Widget"));
// 对象名称设置,也就是Qt Designer中设计的窗体的对象名称
Widget->resize(271, 162);
// 创建界面组件并设置其属性
QFont font;
font.setPointSize(10);
Widget->setFont(font);
labDemo = new QLabel(Widget); // 将Widget作为labDemo的父容器,没有父容器的界面组件就是独立的窗口
labDemo->setObjectName(QString::fromUtf8("labDemo"));
labDemo->setGeometry(QRect(65, 40, 156, 35));
QFont font1;
font1.setPointSize(20);
font1.setBold(true);
labDemo->setFont(font1);
btnClose = new QPushButton(Widget);
btnClose->setObjectName(QString::fromUtf8("btnClose"));
btnClose->setGeometry(QRect(160, 105, 81, 31));
// 设置界面上各组件的文字属性
retranslateUi(Widget);
// 设置信号与槽的关联
// connect()函数:中信号与槽关联的实现代码
// 此处将按钮 btnClose 的 clicked()信号与窗口 Widget 的 close()槽函数关联起来
QObject::connect(btnClose, &QPushButton::clicked, Widget, &QWidget::close);
// 设置槽函数的关联方式式,用于将窗口上各组件的信号与槽函数自动连接,在后面介 绍信号与槽时会详细解释其功能。
QMetaObject::connectSlotsByName(Widget);
} // setupUi
void retranslateUi(QWidget *Widget)
{
Widget->setWindowTitle(QCoreApplication::translate("Widget", "First Demo", nullptr));
labDemo->setText(QCoreApplication::translate("Widget", "Hello Qt6", nullptr));
btnDemo->setText(QCoreApplication::translate("Widget", "Close", nullptr));
} // retranslateUi
};
namespace Ui {
class Widget : public Ui_Widget {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_WIDGET_H
这个文件主要做了以下一些工作:
- 定义了一个类
Ui_Widget
,用于封装可视化设计的界面。注意,Ui_Widget
类没有父类, 不是从QWidget
继承而来的,所以Ui_Widget
不是一个窗口类。 -
Ui_Widget
类的public
部分为界面上每个组件定义了一个指针变量,变量的名称就是UI可视 化设计时为组件设置的对象名称。例如,为界面上的QLabel
和QPushButton
组件自动生成的定义是:QLabel *labDemo; QPushButton *btnClose;
-
Ui_Widget
类中定义了一个函数setupUi()
,详细信息见代码注释。 - 定义名字空间
Ui
,并定义一个从Ui_Widget
继承的类Widget
Ui::Widget
与文件widget.h
里的类Widget
同名,但是用命名空间区分开
5. 窗口的创建与初始化¶
再回过头来看文件widget.h
里Widget
类的定义,它定义了Ui::Widget
类型的指针变量ui
,通过这个指针就可以访问界面上的所有组件,因为在Ui::Widget
类中,界面组件对象都是公有成员。
在文件widget.cpp
中,Widget
类的构造函数创建了Ui::Widget
类对象ui
,运行 ui->setupUi(this)
进行窗口界面初始化,包括创建所有界面组件、设置属性、设置信号与槽的关联。this
是Widget
实例对象,它是一个QWidget
窗口,是setupUi(this)
中创建的所有界面组件的父容器。
在 Widget 类中编写程序时,可以通过 ui 指针访问窗口界面上的所有组件。我们稍微修改 Widget 类的构造函数,例如修改labDemo和btnClose的显示文字,代码如下:
Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) {
ui->setupUi(this);
ui->labDemo->setText("欢迎使用 Qt6");
ui->btnClose->setText("关闭");
}