10.2 Ui_YourFormName.h 文件的组成
我们了解了 uic 工具的使用方法和公用,现在来看看它为我们生成的 C++头文件的组成 是什么样的,里面的“构件”又有哪些功用,这对我们理解后面几节的内容有着重要的作用 。
我们将使用 Qt Designer 创建一个界面文件,名为 calculatorform.ui,这个程序主要 是完成简单的加法计算功能。该界面的元素组合如图 10-1 所示。
图 10-1 使用 Qt Designer 制作的界面
怎样做到在不使用 qmake 的情况下,就可以产生对应的头文件呢,我们遵循上一节介绍 的方法,在 Qt 的命令行下面输入如下命令:
uic –o ui_calculatorform.h calculatorform.ui
小贴士:再次提醒,以 Windows XP 操作系统为例,进入 Qt 命令行的方法是:依次点击【开始】->【所有程序】->【Qt SDK by Nokia v2009.03 (open source)】->【Qt Command Prompt】。然后切换到你的.ui 文件所在的目录,执行上述命令即可。
以下是据此生成的 ui_calculatorform.h 文件的完整内容,我将分段向大家讲解。
/********************************************************************************
** Form generated from reading ui file 'calculatorform.ui'
**
** Created: Sun Sep 6 22:34:26 2009
** by: Qt User Interface Compiler version 4.5.2
**
** WARNING! All changes made in this file will be lost when recompiling ui file!
********************************************************************************/
这一段代码是 uic 工具自动生成的注释内容,它记录了该 C++头文件对应的原始.ui 文件的名字以及产生的时间和使用的 uic 的版本。最后一行提示内容很重要,它明确的告诉我们,不要手动修改该文件内容,因为在下次运行 qmake 或者 uic 时,它们会被覆盖掉。所以,我们如果需要对文件内容做修改,那么到 Qt Designer 中修改界面元素就行了,uic 会把所 有的改动反映出来。
#ifndef UI_CALCULATORFORM_H
#define UI_CALCULATORFORM_H
#include <QtCore/QVariant>
#include <QtGui/QAction>
#include <QtGui/QApplication>
#include <QtGui/QButtonGroup>
#include <QtGui/QGridLayout>
#include <QtGui/QHeaderView>
#include <QtGui/QLabel>
#include <QtGui/QSpacerItem>
#include <QtGui/QSpinBox>
#include <QtGui/QVBoxLayout>
#include <QtGui/QWidget>
上面这段的开头两句加上该文件结尾的 #endif // UI_CALCULATORFORM_H 这句构成了完整的防止头文件重复定义的卫哨,这是很好的编程规范,我们在自己书写代码时,也要遵循 这种严谨的做法。
后面的几句加入了程序中用到的窗口部件以及变量的头文件 。请大家注意它们的用法也 是非常规范的,正是许多 C++大师所提倡的“用到什么就包含什么,不用的就不包含 ”的做 法的应用典范。笔者发现有很多的朋友不论什么情况,都喜欢加入一句 #include <QtGui>或 者#include <QtCore>,这固然省事,因为它们包含了这两个模块下的所有子模块的定义 ,但 是这样一来,就降低了程序的编译速度,使得性能下降。
QT_BEGIN_NAMESPACE
class Ui_CalculatorForm
{
public:
QGridLayout *gridLayout;
QSpacerItem *spacerItem;
QLabel *label_3_2;
QVBoxLayout *vboxLayout;
QLabel *label_2_2_2;
QLabel *outputWidget;
QSpacerItem *spacerItem1;
QVBoxLayout *vboxLayout1;
QLabel *label_2;
QSpinBox *inputSpinBox2;
QLabel *label_3;
QVBoxLayout *vboxLayout2;
QLabel *label;
QSpinBox *inputSpinBox1;
上面这段代码中,首先用 QT_BEGIN_NAMESPACE 宏表示开始进入名字空间。然后定义了一个名为 Ui_CalculatorForm 的类,它实际上是界面的实体类,界面上的所有元素都被定义 为相应的窗口部件的实例,并且它们一定是被定义为 public,即公有的成员,这将使得后面的 Ui 名字空间内的派生类能够继承它们。
void setupUi(QWidget *CalculatorForm)
{
if (CalculatorForm->objectName().isEmpty())
CalculatorForm->setObjectName(QString::fromUtf8("CalculatorForm"));
CalculatorForm->resize(400, 300);
QSizePolicy sizePolicy(static_cast<QSizePolicy::Policy>(5),
static_cast<QSizePolicy::Policy>(5));
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0);
sizePolicy.setHeightForWidth(CalculatorForm->sizePolicy().hasHeightForWidth());
CalculatorForm->setSizePolicy(sizePolicy);
gridLayout = new QGridLayout(CalculatorForm);
#ifndef Q_OS_MAC
gridLayout->setSpacing(6);
#endif
#ifndef Q_OS_MAC
gridLayout->setMargin(9);
#endif
gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
gridLayout->setObjectName(QString::fromUtf8(""));
spacerItem = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
gridLayout->addItem(spacerItem, 0, 6, 1, 1);
label_3_2 = new QLabel(CalculatorForm);
label_3_2->setObjectName(QString::fromUtf8("label_3_2"));
label_3_2->setGeometry(QRect(169, 9, 20, 52));
label_3_2->setAlignment(Qt::AlignCenter);
gridLayout->addWidget(label_3_2, 0, 4, 1, 1);
vboxLayout = new QVBoxLayout();
#ifndef Q_OS_MAC
vboxLayout->setSpacing(6);
#endif
vboxLayout->setMargin(1);
vboxLayout->setObjectName(QString::fromUtf8("vboxLayout"));
vboxLayout->setObjectName(QString::fromUtf8(""));
label_2_2_2 = new QLabel(CalculatorForm);
label_2_2_2->setObjectName(QString::fromUtf8("label_2_2_2"));
label_2_2_2->setGeometry(QRect(1, 1, 36, 17));
vboxLayout->addWidget(label_2_2_2);
outputWidget = new QLabel(CalculatorForm);
outputWidget->setObjectName(QString::fromUtf8("outputWidget"));
outputWidget->setGeometry(QRect(1, 24, 36, 27));
outputWidget->setFrameShape(QFrame::Box);
outputWidget->setFrameShadow(QFrame::Sunken);
outputWidget->setAlignment(Qt::AlignAbsolute|Qt::AlignBottom|Qt::AlignCenter|Qt::AlignHCenter
|Qt::AlignHorizontal_Mask|Qt::AlignJustify|Qt::AlignLeading|Qt::AlignLeft|Qt::AlignRight|Qt::
AlignTop|Qt::AlignTrailing|Qt::AlignVCenter|Qt::AlignVertical_Mask);
vboxLayout->addWidget(outputWidget);
gridLayout->addLayout(vboxLayout, 0, 5, 1, 1);
spacerItem1 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
gridLayout->addItem(spacerItem1, 1, 2, 1, 1);
vboxLayout1 = new QVBoxLayout();
#ifndef Q_OS_MAC
vboxLayout1->setSpacing(6);
#endif
vboxLayout1->setMargin(1);
vboxLayout1->setObjectName(QString::fromUtf8("vboxLayout1"));
vboxLayout1->setObjectName(QString::fromUtf8(""));
label_2 = new QLabel(CalculatorForm);
label_2->setObjectName(QString::fromUtf8("label_2"));
label_2->setGeometry(QRect(1, 1, 46, 19));
vboxLayout1->addWidget(label_2);
inputSpinBox2 = new QSpinBox(CalculatorForm);
inputSpinBox2->setObjectName(QString::fromUtf8("inputSpinBox2"));
inputSpinBox2->setGeometry(QRect(1, 26, 46, 25));
vboxLayout1->addWidget(inputSpinBox2);
gridLayout->addLayout(vboxLayout1, 0, 3, 1, 1);
label_3 = new QLabel(CalculatorForm);
label_3->setObjectName(QString::fromUtf8("label_3"));
label_3->setGeometry(QRect(63, 9, 20, 52));
label_3->setAlignment(Qt::AlignCenter);
gridLayout->addWidget(label_3, 0, 1, 1, 1);
vboxLayout2 = new QVBoxLayout();
#ifndef Q_OS_MAC
vboxLayout2->setSpacing(6);
#endif
vboxLayout2->setMargin(1);
vboxLayout2->setObjectName(QString::fromUtf8("vboxLayout2"));
vboxLayout2->setObjectName(QString::fromUtf8(""));
label = new QLabel(CalculatorForm);
label->setObjectName(QString::fromUtf8("label"));
label->setGeometry(QRect(1, 1, 46, 19));
vboxLayout2->addWidget(label);
inputSpinBox1 = new QSpinBox(CalculatorForm);
inputSpinBox1->setObjectName(QString::fromUtf8("inputSpinBox1"));
inputSpinBox1->setGeometry(QRect(1, 26, 46, 25));
vboxLayout2->addWidget(inputSpinBox1);
gridLayout->addLayout(vboxLayout2, 0, 0, 1, 1);
retranslateUi(CalculatorForm);
QMetaObject::connectSlotsByName(CalculatorForm);
} // setupUi
上面这段代码是界面实体类的公有方法 setupUi()的内容。这是最为重要的一个成员函数,它完成了整个界面的构造和布局,代码的内容比较简单,遵循了我们前面反复讲到的使 用 Qt 编程的基本步骤,即先声明要使用到的窗口部件,然后实例化它们,再为它们设置属 性和方法。
QMetaObject::connectSlotsByName(CalculatorForm);这一句是需要重点说明的,因为 它实现了 CalculatorForm 界面实体类中的信号与槽机制。这个方法的原型是:
void QMetaObject::connectSlotsByName ( QObject * object ) [static]
它是 QMetaObject 类的静态方法,作用是递归搜索 object 对象及子对象,然后将其中相匹配的信号与槽连接起来。所谓相匹配的信号与槽,就是要求槽函数的书写形式如下所示:
void on_<object name>_<signal name>(<signal parameters>);
从编程人员的角度来看,只要将槽函数遵循上面的书写形式 ,就能够实现信号和槽的自动关联,而无需手写代码显式的完成。
再举个例子,假设我们的界面实体类中含有一个子对象,它的类型是 QPushButton,它 的 objectName 属性被设置为 btn1。那么为了使槽函数与信号 clicked()能够自动关联,我们 必须将槽函数写成如下形式:
void on_button1_clicked();
小贴士:QMetaObject 类是 Qt 元对象系统(Meta-Object System)的组成部分,是实现信号/槽机制、运行时类型信息以及 Qt 属性系统的基础。在一个应用程序中所有的 QObject 子类 都会创建一个属于自己的单独的 QMetaObject 的实例,并且这个实例存储了该子类的所有元 对象信息。这些通常包括类名(class name)、超类名(superclass name)、属性信息(properties)、信号与槽(signals and slots)等。
关于 Qt 的元对象系统,我们会在后面的第 13 章作详细介绍。
在后面我们会介绍编译时处理.ui 文件的 3 种方法:直接使用法、单继承法和多继承法。 无论使用它们中的哪一种,都要显式的调用 setupUi()这个方法来完成界面元素的构造和布 局,而它的参数就是你自定义的窗体类,这一点请大家在阅读时注意。
void retranslateUi(QWidget *CalculatorForm)
{
CalculatorForm->setWindowTitle(QApplication::translate("CalculatorForm",
"Calculator Form", 0, QApplication::UnicodeUTF8));
label_3_2->setText(QApplication::translate("CalculatorForm", "=", 0,
QApplication::UnicodeUTF8));
label_2_2_2->setText(QApplication::translate("CalculatorForm", "Output", 0,
QApplication::UnicodeUTF8));
outputWidget->setText(QApplication::translate("CalculatorForm", "0", 0,
QApplication::UnicodeUTF8));
label_2->setText(QApplication::translate("CalculatorForm", "Input 2", 0,
QApplication::UnicodeUTF8));
label_3->setText(QApplication::translate("CalculatorForm", "+", 0,
QApplication::UnicodeUTF8));
label->setText(QApplication::translate("CalculatorForm", "Input 1", 0,
QApplication::UnicodeUTF8));
Q_UNUSED(CalculatorForm);
} // retranslateUi
以上这段是 retranslateUi 方法的定义,它使得应用程序具备了支持国际化的能力 。其 中调用了 translate()方法,它是在 Qt 4.5 以后新引入的。
namespace Ui {
class CalculatorForm: public Ui_CalculatorForm {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_CALCULATORFORM_H
上面这段代码定义了名字空间 Ui,在其内部声明了一个名为 CalculatorForm 的类,它单公有继承自界面实体类 Ui_CalculatorForm,这就使得它继承了界面实体类的所有公有成 员。声明名字空间的用意是防止混淆命名。
注意,这个类的名字是与你的.ui 文件中 Form 的 objectName 的属性值相一致的。在后面的章节里,我们都要与这个类打交道,而不与界面实体类 Ui_CalculatorForm 发生联系,这样做就使得应用程序的界面设计与代码尽可能的分离开来,是现代编程思想的体现。