上一章我们实现了简易加法器,通过Qt Designer设计界面,然后通过信号与槽完成了“计算”功能。这一章我们来研究下使用Qt Designer设计的界面是如何被Qt Creator编译,又如何被主程序所调用的。
我们看下上一章使用Qt Designer设计的界面是什么样的:
从这个布局图可以看出,这个界面里由7个标签、2个数字输入框、1个按钮、1个水平占位符、4个垂直布局器、2个水平布局器和中心控件组成,下面列出界面中所有的控件及其名称:
7个标签为:“简易加法器”label_6、“加数1”label_1、“加数2”label_2、“结果”label_3、“+”label_4、“=”label_5、存放结果的标签(带边框)resultLabel;
2个数字输入框:加数1数字输入框add1SpinBox、加数2数字输入框add2SpinBox;
1个按钮:“计算”按钮calculatePushButton;
1个水平占位符:图中在计算按钮左侧的蓝色像弹簧的长条horizontalSpacer;
4个垂直布局器:加数1垂直布局器verticalLayout_1、加数2垂直布局器verticalLayout_2、结果垂直布局器verticalLayout_3,还有个别忘了,用于整个界面垂直布局的布局器verticalLayout_4;
2个水平布局器:用于输入数字和结果的那一行的水平布局器horizontalLayout_1、用于水平占位符和计算按钮那一行的水平布局器horizontalLayout_2;
中心控件:界面中用于盛放所有控件的中心控件centralwidget。
我们想研究Qt Creator是如何将这些控件编译成代码的,在这之前,我们先看看Qt C++的应用是如何启动的,窗口是如何运行起来的。我们先看下Qt C++应用的入口函数main()函数所在的main.cpp中的代码:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
main.cpp中的代码很简单,只有短短几行,先是包含了2个头文件,一个是主窗口MainWindow类的头文件"mainwindow.h",一个是Qt的应用头文件<QApplication>。
接下来就是Qt C++程序的入口函数main()函数,在main()函数中,只有4行代码,第一行定义了一个QApplication类的对象a,并在构造函数中传入了main()函数的实参;第二行定义了一个主窗口类MainWindow的对象w;第三行执行了w的show()函数,作用是将主窗口显示在屏幕上;第四行main()函数返回了a.exec(),这句代码的作用是执行QApplication应用程序,将程序的控制权交给了Qt。
QApplication是Qt核的类,我们不必深究,我们只需要看下MainWindow这个类,以下是mainwindow.cpp中的代码:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_calculatePushButton_clicked()
{
// 获取加数1的值
int add1 = ui->add1SpinBox->value();
// 获取加数2的值
int add2 = ui->add2SpinBox->value();
// 计算和
int result = add1 + add2;
// 将计算和显示出来
ui->resultLabel->setText(QString::number(result));
}
在mainwindow.cpp代码中,包含了两个头文件,一个是mainwindow.h,是MainWindow类的头文件;一个是ui_mainwindow.h,这个就是界面编译的文件,Qt Creator将界面翻译成代码存放在ui_mainwindow.h头文件中。下面我们看下ui_mainwindow.h中的内容:
/********************************************************************************
** Form generated from reading UI file 'mainwindow.ui'
**
** Created by: Qt User Interface Compiler version 5.14.2
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_MAINWINDOW_H
#define UI_MAINWINDOW_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QSpacerItem>
#include <QtWidgets/QSpinBox>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_MainWindow
{
public:
QWidget *centralwidget;
QVBoxLayout *verticalLayout_4;
QLabel *label_6;
QHBoxLayout *horizontalLayout_1;
QVBoxLayout *verticalLayout_1;
QLabel *label_1;
QSpinBox *add1SpinBox;
QLabel *label_4;
QVBoxLayout *verticalLayout_2;
QLabel *label_2;
QSpinBox *add2SpinBox;
QLabel *label_5;
QVBoxLayout *verticalLayout_3;
QLabel *label_3;
QLabel *resultLabel;
QHBoxLayout *horizontalLayout_2;
QSpacerItem *horizontalSpacer;
QPushButton *calculatePushButton;
void setupUi(QMainWindow *MainWindow)
{
if (MainWindow->objectName().isEmpty())
MainWindow->setObjectName(QString::fromUtf8("MainWindow"));
MainWindow->resize(291, 187);
centralwidget = new QWidget(MainWindow);
centralwidget->setObjectName(QString::fromUtf8("centralwidget"));
verticalLayout_4 = new QVBoxLayout(centralwidget);
verticalLayout_4->setObjectName(QString::fromUtf8("verticalLayout_4"));
label_6 = new QLabel(centralwidget);
label_6->setObjectName(QString::fromUtf8("label_6"));
QFont font;
font.setPointSize(12);
font.setBold(true);
font.setWeight(75);
label_6->setFont(font);
label_6->setAlignment(Qt::AlignCenter);
verticalLayout_4->addWidget(label_6);
horizontalLayout_1 = new QHBoxLayout();
horizontalLayout_1->setObjectName(QString::fromUtf8("horizontalLayout_1"));
verticalLayout_1 = new QVBoxLayout();
verticalLayout_1->setObjectName(QString::fromUtf8("verticalLayout_1"));
label_1 = new QLabel(centralwidget);
label_1->setObjectName(QString::fromUtf8("label_1"));
label_1->setAlignment(Qt::AlignCenter);
verticalLayout_1->addWidget(label_1);
add1SpinBox = new QSpinBox(centralwidget);
add1SpinBox->setObjectName(QString::fromUtf8("add1SpinBox"));
verticalLayout_1->addWidget(add1SpinBox);
horizontalLayout_1->addLayout(verticalLayout_1);
label_4 = new QLabel(centralwidget);
label_4->setObjectName(QString::fromUtf8("label_4"));
QFont font1;
font1.setPointSize(16);
label_4->setFont(font1);
label_4->setAlignment(Qt::AlignCenter);
horizontalLayout_1->addWidget(label_4);
verticalLayout_2 = new QVBoxLayout();
verticalLayout_2->setObjectName(QString::fromUtf8("verticalLayout_2"));
label_2 = new QLabel(centralwidget);
label_2->setObjectName(QString::fromUtf8("label_2"));
label_2->setAlignment(Qt::AlignCenter);
verticalLayout_2->addWidget(label_2);
add2SpinBox = new QSpinBox(centralwidget);
add2SpinBox->setObjectName(QString::fromUtf8("add2SpinBox"));
verticalLayout_2->addWidget(add2SpinBox);
horizontalLayout_1->addLayout(verticalLayout_2);
label_5 = new QLabel(centralwidget);
label_5->setObjectName(QString::fromUtf8("label_5"));
label_5->setFont(font1);
label_5->setAlignment(Qt::AlignCenter);
horizontalLayout_1->addWidget(label_5);
verticalLayout_3 = new QVBoxLayout();
verticalLayout_3->setObjectName(QString::fromUtf8("verticalLayout_3"));
label_3 = new QLabel(centralwidget);
label_3->setObjectName(QString::fromUtf8("label_3"));
label_3->setAlignment(Qt::AlignCenter);
verticalLayout_3->addWidget(label_3);
resultLabel = new QLabel(centralwidget);
resultLabel->setObjectName(QString::fromUtf8("resultLabel"));
resultLabel->setFrameShape(QFrame::Box);
resultLabel->setFrameShadow(QFrame::Sunken);
resultLabel->setLineWidth(1);
resultLabel->setMidLineWidth(0);
resultLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
resultLabel->setMargin(0);
resultLabel->setOpenExternalLinks(false);
verticalLayout_3->addWidget(resultLabel);
horizontalLayout_1->addLayout(verticalLayout_3);
horizontalLayout_1->setStretch(0, 10);
horizontalLayout_1->setStretch(2, 10);
horizontalLayout_1->setStretch(4, 10);
verticalLayout_4->addLayout(horizontalLayout_1);
horizontalLayout_2 = new QHBoxLayout();
horizontalLayout_2->setObjectName(QString::fromUtf8("horizontalLayout_2"));
horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout_2->addItem(horizontalSpacer);
calculatePushButton = new QPushButton(centralwidget);
calculatePushButton->setObjectName(QString::fromUtf8("calculatePushButton"));
horizontalLayout_2->addWidget(calculatePushButton);
verticalLayout_4->addLayout(horizontalLayout_2);
MainWindow->setCentralWidget(centralwidget);
retranslateUi(MainWindow);
QMetaObject::connectSlotsByName(MainWindow);
} // setupUi
void retranslateUi(QMainWindow *MainWindow)
{
MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "MainWindow", nullptr));
label_6->setText(QCoreApplication::translate("MainWindow", "\347\256\200\346\230\223\345\212\240\346\263\225\345\231\250", nullptr));
label_1->setText(QCoreApplication::translate("MainWindow", "\345\212\240\346\225\2601", nullptr));
label_4->setText(QCoreApplication::translate("MainWindow", "+", nullptr));
label_2->setText(QCoreApplication::translate("MainWindow", "\345\212\240\346\225\2602", nullptr));
label_5->setText(QCoreApplication::translate("MainWindow", "=", nullptr));
label_3->setText(QCoreApplication::translate("MainWindow", "\347\273\223\346\236\234", nullptr));
resultLabel->setText(QCoreApplication::translate("MainWindow", "0", nullptr));
calculatePushButton->setText(QCoreApplication::translate("MainWindow", "\350\256\241\347\256\227", nullptr));
} // retranslateUi
};
namespace Ui {
class MainWindow: public Ui_MainWindow {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_MAINWINDOW_H
我们看到这个文件的顶部注释,意思是:这个文件由界面文件'mainwindow.ui'生成,被Qt用户接口编译版本5.14.2创建。警告!对这个文件的所有改动都将在编译界面文件时丢失。
也就是说,这个文件是Qt根据界面自动生成的,用户不需要更改。我们看下这个文件有哪些代码:首先,文件头部包含了所用到的头文件,后定义了一个类Ui_MainWindow,在这个类里,定义了一些公有指针变量,这些变量就是我们上面列举的界面上的控件,名称就是设置的objectName,之后定义了一个公有函数void setupUi(QMainWindow *MainWindow),在这个函数中实例了各个控件对象,设置了各个控件的属性以及界面的布局,是不是很像我们手动撸的代码,但因为是机器自动生成的,显得有些机械化而已,但作用是一样的。其中有条语句关注一下,是QMetaObject::connectSlotsByName(MainWindow);这条语句的作用是搜索MainWindow界面上的所有控件,将信号与槽函数匹配的信号和槽关联起来,用作名为 void 类名::on_控件名_信号();这种槽函数的声明,使得控件的信号可以直接关联到这样的槽函数中。
除了setupUi函数外,这个类里还定义了一个void retranslateUi(QMainWindow *MainWindow)函数,这个函数用来设置界面各组件的文字内容属性,如窗口的标题、标签的文字、按钮的文字,将界面上的文字设置的内容独立出来作为一个函数,在设计多语言界面时会被用到。
在函数的最后,有以下语句:
namespace Ui {
class MainWindow: public Ui_MainWindow {};
}
这几条语句定义了命名空间Ui,并定义了一个继承Ui_MainWindow的类MainWindow。这个操作的意义我们等会会讲到。
接下来我们看看mainwindow.h中的代码:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
private slots:
void on_calculatePushButton_clicked();
};
#endif // MAINWINDOW_H
mainwindow.h文件中,先是包含了<QMainWindow>头文件,后声明了命名空间namespace Ui { class MainWindow; },注意,这里的class MainWindow是命名空间Ui中的类,Ui命名空间和其下的MainWindow在ui_mainwindow.h文件中定义,这里只是拿过来显性地声明下,在mainwindow.h文件中定义的MainWindow类中用到。
我们看下mainwindow.h文件中定义的MainWindow类包含哪些东西,首先,这个类继承自QMainWindow,类的内部有个Q_OBJECT宏定义,在声明信号与槽之前,必须使用宏定义Q_OBJECT。类中声明了构造函数和析构函数,是公有函数。然后定义了一个私有指针变量Ui::MainWindow *ui;我们仔细分析下这个指针变量,ui这个指针变量的类型是Ui::MainWindow,就是ui_mainwindow.h文件中定义的MainWindow,所以这个ui指针变量可以访问到ui_mainwindow.h文件中定义的MainWindow内部的所有的公有变量和函数,即可以通过ui这个指针变量访问到界面上的所有控件。
ui_mainwindow.h文件中实现界面功能的类是Ui_MainWindow,再定义一个类MainWindow继承Ui_MainWindow,并定义在namespace Ui里,这样Ui::MainWindow与mainwindow.h文件中的类MainWindow同名,但是用namespace区分开来。所以,界面的Ui::MainWindow类与文件mainwindow.h中定义的MainWindow类实际上是两个类,但是Qt的处理让用户感觉不到Ui::MainWindow类的存在,只需要知道在MainWindow类里用ui指针可以访问界面的控件就行了。是不是感觉Qt这样的设计非常的巧妙,行云流水!
mainwindow.h中MainWindow类的最后部分声明的公有槽函数void on_calculatePushButton_clicked();这个是“计算”按钮calculatePushButton的点击信号clicked链接的槽函数,之所以这个槽函数能够被“计算”按钮的点击信号触发,完全是因为在ui_mainwindow.h文件中Ui_MainWindow类里的一条语句QMetaObject::connectSlotsByName(MainWindow)的作用。
讲完了mainwindow.h,我们再回过头来看看mainwindow.cpp中的内容。在MainWindow的构造函数中,使用new Ui::MainWindow实例化了ui指针变量,开辟了空间,ui也不再是空指针了。在构造函数中,使用ui->setupUi(this);初始化了界面。在MainWindow的析构函数中,将ui指针释放掉。在mainwindow.cpp中定义了槽函数void on_calculatePushButton_clicked(),实现简易加法器计算功能。
到此,简易加法器的代码框架我们就讲完了,小伙伴们清晰了吗,如果还不清晰,对着代码看几遍就懂了。