Skip to content

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里的类Widget定义的,所以ui是窗口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可视 化设计时为组件设置的对象名称。例如,为界面上的QLabelQPushButton组件自动生成的定义是:

    QLabel *labDemo; QPushButton *btnClose;
    
  • Ui_Widget 类中定义了一个函数setupUi(),详细信息见代码注释。

  • 定义名字空间Ui,并定义一个从Ui_Widget继承的类Widget Ui::Widget与文件widget.h 里的类Widget 同名,但是用命名空间区分开

5. 窗口的创建与初始化

再回过头来看文件widget.hWidget类的定义,它定义了Ui::Widget类型的指针变量ui,通过这个指针就可以访问界面上的所有组件,因为在Ui::Widget类中,界面组件对象都是公有成员。

在文件widget.cpp 中,Widget 类的构造函数创建了Ui::Widget类对象ui,运行 ui->setupUi(this) 进行窗口界面初始化,包括创建所有界面组件、设置属性、设置信号与槽的关联。thisWidget实例对象,它是一个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("关闭");
}