Qt程序上手构建

一个QT程序可以分为前端和后端,前端就是程序的界面。界面一般由Qt Design程序以可视化的形式生成,通过拖拽布置组件、并为组件设置属性和设置信号和函数槽。

界面头文件的生成

Qt Design生成的界面文件后缀为ui,是一种xml文件,我们可以使用uic程序转换为c++编程语言所需要的.h头文件。

1
uic hello.ui -o ui_hello.h

界面头文件的使用

查看生成的UI文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/********************************************************************************
** Form generated from reading UI file 'hello.ui'
**
** Created by: Qt User Interface Compiler version 5.9.0
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_HELLO_H
#define UI_HELLO_H
#include <QtCore/QVariant>
#include <QtWidgets/QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_Form
{
public:
QLabel *label;
void setupUi(QWidget *Form)
{
if (Form->objectName().isEmpty())
Form->setObjectName(QStringLiteral("Form"));
Form->resize(427, 244);
label = new QLabel(Form);
label->setObjectName(QStringLiteral("label"));
label->setGeometry(QRect(10, 10, 200, 40));
retranslateUi(Form);
QMetaObject::connectSlotsByName(Form);
} // setupUi
void retranslateUi(QWidget *Form)
{
Form->setWindowTitle(QApplication::translate("Form", "Form", Q_NULLPTR));
label->setText(QApplication::translate("Form", "C\350\257\255\350\250\200\344\270\255\346\226\207\347\275\221", Q_NULLPTR));
} // retranslateUi
};
namespace Ui {
class Form: public Ui_Form {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_HELLO_H

代码开头的注释提醒开发者不要手动修改该头文件,因为uic工具下次自动生成.h 文件时,会把旧的代码全清掉,然后生成新的代码内容。

QT_BEGIN_NAMESPACEQT_END_NAMESPACE这两个宏标示中间的代码是包含在名字空间里的,就是一个提示作用,没有实际意义。

第一个类是全局范围定义的Ui_Form类,里面首先定义了一个label指针,注意这个指针名称就是之前设计师里显示的objectName。

接着定义了一个 setupUi 函数,这个是最关键的生成图形界面的函数,它接收一个 QWidget 对象的指针,然后为这个 QWidget 对象设置窗口界面和控件。

还有一个 retranslateUi 函数,是专门用于支持多国语言翻译的,主窗口和标签控件的字符串都在这重新翻译一下,如果有多国语言支持的翻译文件,界面的多国语言显示就通过该函数实现。

接下来定义了一个叫 Ui 的名字空间,空间里定义了一个类 Form ,简单地从 Ui_Form 类继承一下,并没有添加额外的代码。使用 Ui 名字空间的好处就是避免名称冲突,所以正常都不会直接使用 Ui_Form 类,而是用名字空间里的 Ui::Form 类。

准确地说,它们通过 setupUi 函数,辅助该函数参数里的窗口对象(QWidget *Form)构建图形界面,它们帮助别的窗口类对象构建图形界面,仅此而已。当然,在 setupUi 函数里新建的控件指针,如 label,是 Ui_Form 或Ui::Form 类里的成员变量,代码里需要通过这个类的成员变量来操控相应的控件。

如果要在项目里面使用 ui 文件(其实是 ui_*.h),通常有三种方式:直接使用方式、多重继承使用方式和成员变量使用方式。本节讲述前两种使用方式,而以后 Qt Creator 自动生成的代码就是成员变量使用方式,本节就不重复了。

直接使用 .ui 文件

直接使用 .ui 文件的原理非常简单,创建一个 QWidget 类对象和 Ui::Form 类对象,调用 Ui::Form 对象的 setupUi 函数设置一下主窗体,然后显示就行了。我们在 D:\QtDemo\ 文件夹里新建一个 main.cpp,然后编辑代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include "ui_hello.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget *w = new QWidget(); //主窗口
Ui::Form createUi; //createUi并不是一个真正的窗口
createUi.setupUi(w); //createUi是创建GUI的工具
w->show(); //w是真正的窗口
return a.exec();
}

main.cpp 包含了三个头文件 QApplication、QWidget 和 前面用 uic 生成的 ui_hello.h,由于 ui_hello.h 不包含 Q_OBJECT 宏,是不需要用元对象编译器 moc 处理的。

main 函数里第一行是图形界面程序入口对象,第二行创建了一个 QWidget 类对象 w(w 其实是一个指向对象的指针)作为程序的主窗口,w 自己并没有创建控件或设置窗口属性。

第三行语句:

Ui::Form createUi;

创建了 Ui::Form 类的对象 createUi ,这个对象自己不是一个窗口,它可以为别的窗口对象设置图形界面。

第四行语句:

createUi.setupUi(w);

调用了 createUi 对象的 setupUi 函数,该函数接收一个窗体对象指针,这里是 w。setupUi 函数里面的代码会为 w 创建内部的控件,设置窗体大小等等。

剩下的两行代码是显示主界面,并进入事件处理循环,直到退出。

多重继承法使用 .ui 文件

上面的代码非常简单,除了 uic 生成的 ui_hello.h 和手动编写的 main.cpp,就没其他的代码文件了。主界面是 QWidget 类的对象,然后该对象比较简单,没有自己的代码。

如果要丰富一下主界面的窗口类,那就需要使用从 QWidget 类继承的方式并加上 Ui::Form 类的代码。C++ 如果要同时使用两个类的代码,有两种方式:

  • 一种是多重继承的方式,同时用 QWidget 和 Ui::Form 类作为基类;
  • 还有一种是使用成员变量,将 Ui::Form 类的对象作为 QWidget 派生类的成员变量,这种也叫单一继承方式,它的基类只有 QWidget。

本小节介绍多重继承方式,而以后的代码都用 QtCreator 自动生成的单一继承方式(Ui::Form 的对象作为成员变量)。

将前面做好的 hello.ui 和 ui_hello.h 复制到 D:\QtDemo 文件夹,然后再新建三个代码文件,分别是 hellouiwidget.h、hellouiwidget.cpp 和 main.cpp ,每个文件的内容如下。

  1. hellouiwidget.h 代码内容
1
2
3
4
5
6
7
8
9
10
11
12
#include <QtWidgets/QWidget>
#include <QtWidgets/QLabel>
#include "ui_hello.h"
class HelloUIWidget : public QWidget, public Ui::Form
{
Q_OBJECT
public:
explicit HelloUIWidget(QWidget *parent = 0);
~HelloUIWidget();
protected:
void AdjustLabel();
};

hellouiwidget.h 包含了三个头文件 QWidget、QLabel 和 使用 uic 生成的 ui_hello.h ,里面定义了一个类 HelloUIWidget 。HelloUIWidget 从 QWidget、Ui::Form 两个基类继承而来,都是 public 继承方式。由于基类有一个是 Qt 窗口类 QWidget,所以在类定义开始处必须加入 Q_OBJECT 宏,用于声明元对象系统。

该类定义了两个公开类型(public)的函数,即构造函数和析构函数。

最后一个是我们自己编写的保护类型(protected)的函数 AdjustLabel,用于调整 label 标签对象的显示效果。使用多重继承或成员变量的方式就容易丰富窗口类的功能,我们在 HelloUIWidget  里添加了 AdjustLabel 函数,当然还可以添加更多的函数。

  1. hellouiwidget.cpp 文件内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "hellouiwidget.h"
HelloUIWidget::HelloUIWidget(QWidget *parent) : QWidget(parent)
{
setupUi(this); //必须先调用setupUi 函数
//TODO:
AdjustLabel();
}
HelloUIWidget::~HelloUIWidget()
{
//无需手动删除 Label 组件和 widget 组件,它们会被 Qt 自动删除
}
void HelloUIWidget::AdjustLabel()
{
label->setText("新宝库");
}

在 HelloUIWidget 构造函数定义处,它使用输入参数 parent 初始化了基类 QWidget,另一个基类 Ui::Form 因为它构造函数不需要参数,就没必要手动编写初始化代码,C++ 编译器自己会先构造好基类。

HelloUIWidget 从基类 Ui::Form 继承了 setupUi 函数,所以可直接调用该函数为自己窗口(this)构建图形界面。在构建好图形界面的控件之后,我们再调用自己编写的 AdjustLabel 函数修改标签控件显示效果。

第二个函数是 HelloUIWidget 类的析构函数,里面没有实际代码。仔细观察 ui_hello.h 代码可以发现 label 指针保存的对象是用 new 创建的,而这里我们没有手动 delete 它,因为在 Qt 主窗口关闭时,这些控件会随着主窗口全会被自动销毁,对于控件对象可以不用手动编写 delete 代码。

第三个函数是 AdjustLabel ,这个函数里对 label 指针保存的对象进行处理,label 指针成员变量也是从基类 Ui::Form 继承而来的。AdjustLabel 里面第一句代码:

1
label->setText("新宝库");

是设置标签控件显示的文本。

  1. main.cpp 文件内容:
1
2
3
4
5
6
7
8
9
#include <QtWidgets/QApplication>
#include "hellouiwidget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
HelloUIWidget *w = new HelloUIWidget();
w->show();
return a.exec();
}

代码内容比较简单,main 函数里第一句创建图形程序的入口对象,第二句创建主界面窗口对象,第三句显示主界面窗口,最后一句进入事件循环直到退出为止。窗口对象会在主窗口关闭时自动销毁,所以没有 手动加 delete 代码。

代码分析完了,接下来就是编译生成目标程序了。

参考文献

博客:Qt.ui文件的使用

作者

echo

发布于

2023-01-13

更新于

2024-08-10

许可协议

评论