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-&gt;objectName().isEmpty())
        CalculatorForm-&gt;setObjectName(QString::fromUtf8("CalculatorForm"));
    CalculatorForm-&gt;resize(400, 300);
    QSizePolicy sizePolicy(static_cast&lt;QSizePolicy::Policy&gt;(5),
    static_cast&lt;QSizePolicy::Policy&gt;(5));
    sizePolicy.setHorizontalStretch(0);
    sizePolicy.setVerticalStretch(0);
    sizePolicy.setHeightForWidth(CalculatorForm-&gt;sizePolicy().hasHeightForWidth());
    CalculatorForm-&gt;setSizePolicy(sizePolicy);
    gridLayout = new QGridLayout(CalculatorForm);
    #ifndef Q_OS_MAC
    gridLayout-&gt;setSpacing(6);
    #endif
    #ifndef Q_OS_MAC
    gridLayout-&gt;setMargin(9);
    #endif
    gridLayout-&gt;setObjectName(QString::fromUtf8("gridLayout"));
    gridLayout-&gt;setObjectName(QString::fromUtf8(""));
    spacerItem = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
    gridLayout-&gt;addItem(spacerItem, 0, 6, 1, 1);
    label_3_2 = new QLabel(CalculatorForm);
    label_3_2-&gt;setObjectName(QString::fromUtf8("label_3_2"));
    label_3_2-&gt;setGeometry(QRect(169, 9, 20, 52));
    label_3_2-&gt;setAlignment(Qt::AlignCenter);
    gridLayout-&gt;addWidget(label_3_2, 0, 4, 1, 1);
    vboxLayout = new QVBoxLayout();
    #ifndef Q_OS_MAC
    vboxLayout-&gt;setSpacing(6);
    #endif
    vboxLayout-&gt;setMargin(1);
    vboxLayout-&gt;setObjectName(QString::fromUtf8("vboxLayout"));
    vboxLayout-&gt;setObjectName(QString::fromUtf8(""));
    label_2_2_2 = new QLabel(CalculatorForm);
    label_2_2_2-&gt;setObjectName(QString::fromUtf8("label_2_2_2"));
    label_2_2_2-&gt;setGeometry(QRect(1, 1, 36, 17));
    vboxLayout-&gt;addWidget(label_2_2_2);
    outputWidget = new QLabel(CalculatorForm);
    outputWidget-&gt;setObjectName(QString::fromUtf8("outputWidget"));
    outputWidget-&gt;setGeometry(QRect(1, 24, 36, 27));
    outputWidget-&gt;setFrameShape(QFrame::Box);
    outputWidget-&gt;setFrameShadow(QFrame::Sunken);
    outputWidget-&gt;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-&gt;addWidget(outputWidget);
    gridLayout-&gt;addLayout(vboxLayout, 0, 5, 1, 1);
    spacerItem1 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
    gridLayout-&gt;addItem(spacerItem1, 1, 2, 1, 1);
    vboxLayout1 = new QVBoxLayout();
    #ifndef Q_OS_MAC
    vboxLayout1-&gt;setSpacing(6);
    #endif
    vboxLayout1-&gt;setMargin(1);
    vboxLayout1-&gt;setObjectName(QString::fromUtf8("vboxLayout1"));
    vboxLayout1-&gt;setObjectName(QString::fromUtf8(""));
    label_2 = new QLabel(CalculatorForm);
    label_2-&gt;setObjectName(QString::fromUtf8("label_2"));
    label_2-&gt;setGeometry(QRect(1, 1, 46, 19));
    vboxLayout1-&gt;addWidget(label_2);
    inputSpinBox2 = new QSpinBox(CalculatorForm);
    inputSpinBox2-&gt;setObjectName(QString::fromUtf8("inputSpinBox2"));
    inputSpinBox2-&gt;setGeometry(QRect(1, 26, 46, 25));
    vboxLayout1-&gt;addWidget(inputSpinBox2);
    gridLayout-&gt;addLayout(vboxLayout1, 0, 3, 1, 1);
    label_3 = new QLabel(CalculatorForm);
    label_3-&gt;setObjectName(QString::fromUtf8("label_3"));
    label_3-&gt;setGeometry(QRect(63, 9, 20, 52));
    label_3-&gt;setAlignment(Qt::AlignCenter);
    gridLayout-&gt;addWidget(label_3, 0, 1, 1, 1);
    vboxLayout2 = new QVBoxLayout();
    #ifndef Q_OS_MAC
    vboxLayout2-&gt;setSpacing(6);
    #endif
    vboxLayout2-&gt;setMargin(1);
    vboxLayout2-&gt;setObjectName(QString::fromUtf8("vboxLayout2"));
    vboxLayout2-&gt;setObjectName(QString::fromUtf8(""));
    label = new QLabel(CalculatorForm);
    label-&gt;setObjectName(QString::fromUtf8("label"));
    label-&gt;setGeometry(QRect(1, 1, 46, 19));
    vboxLayout2-&gt;addWidget(label);
    inputSpinBox1 = new QSpinBox(CalculatorForm);
    inputSpinBox1-&gt;setObjectName(QString::fromUtf8("inputSpinBox1"));
    inputSpinBox1-&gt;setGeometry(QRect(1, 26, 46, 25));
    vboxLayout2-&gt;addWidget(inputSpinBox1);
    gridLayout-&gt;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_&lt;object name&gt;_&lt;signal name&gt;(&lt;signal parameters&gt;);

从编程人员的角度来看,只要将槽函数遵循上面的书写形式 ,就能够实现信号和槽的自动关联,而无需手写代码显式的完成。

再举个例子,假设我们的界面实体类中含有一个子对象,它的类型是 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-&gt;setWindowTitle(QApplication::translate("CalculatorForm", 
        "Calculator Form", 0, QApplication::UnicodeUTF8));
    label_3_2-&gt;setText(QApplication::translate("CalculatorForm", "=", 0,
        QApplication::UnicodeUTF8));
    label_2_2_2-&gt;setText(QApplication::translate("CalculatorForm", "Output", 0,
        QApplication::UnicodeUTF8));
    outputWidget-&gt;setText(QApplication::translate("CalculatorForm", "0", 0,
        QApplication::UnicodeUTF8));
    label_2-&gt;setText(QApplication::translate("CalculatorForm", "Input 2", 0,
        QApplication::UnicodeUTF8));
    label_3-&gt;setText(QApplication::translate("CalculatorForm", "+", 0,
        QApplication::UnicodeUTF8));
    label-&gt;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 发生联系,这样做就使得应用程序的界面设计与代码尽可能的分离开来,是现代编程思想的体现。