7.2 子类化 QDialog

创建基于对话框的应用程序主要是使用子类化 QDialog 的方法。在本节,我们采用这个方法创建一个稍微复杂的实例-可扩展对话框。

可扩展对话框通常只显示简单的外观,但是它还有一个切换按钮( toggle button), 可以让用户在对话框的简单外观和扩展外观之间来回切换。 可扩展对话框通常用于试图同时 满足普通用户和高级用户需要的应用程序中,这种应用程序通常会隐藏那些高级选项,除非 用户明确要求看到它们。

这个对话框是一个用于电子制表软件应用程序的排序对话框( Sort 对话框),在这个 对话框中,用户可以选择一列或多列进行排序。在这个简单外观中,允许用户输入一个单一 的排序键,而在扩展外观下,还额外提供了两个排序键。 More 按钮允许用户在简单外观和 扩展外观之间切换。该实例的运行效果如图 7-2 所示。

图 7-2 实例运行效果

该实例名为 extensionDlg。共有以下原生源文件:工程文件 extensionDlg.pro,主程 序文件 main.cpp,对话框类 ExtensionDlg 的头文件 extensionDlg.h,实现文件 extensionDlg.cpp。

在第 6 章中我们向大家介绍了创建 Qt 应用程序的基本方法,这里我们采用 Qt Creator作为 IDE,并使用完全手写的方式完成程序的界面布局和构建。

首先当然是在 Qt Creator 中创建这个名为 extensionDlg 的项目,类型是 Empty Qt4 Project。然后在其中依次加入对话框类 ExtensionDlg 的头文件 extensionDlg.h,实现文件 extensionDlg.cpp,主程序文件 main.cpp 以及工程文件 extensionDlg.pro。这些文件的内容, 可以在 Qt Creator 的代码编辑器中完成编辑。

我们先看一下对话框类 ExtensionDlg 的头文件的内容。

#ifndef EXTENSIONDLG_H
#define EXTENSIONDLG_H
#include <QtGui>
class ExtensionDlg : public QDialog
{
    Q_OBJECT
public:
    ExtensionDlg();
    void initBasicInfo();
    void initDetailInfo();
    public slots:
    void slot2Extension();
private:
    QWidget *baseWidget;
    QWidget *detailWidget;
};

第 1 行引入 QtGui 模块的头文件

第 2 行声明我们的自定义对话框类 ExtensionDlg 单公有继承自 QDialog。

第 3 行加入 Q_OBJECT 宏,程序中用到诸如信号/槽等 Qt 核心机制的时候,都要加入这 个宏。

第 4-7 行声明了构造函数和初始化基础信息和扩展信息的函数。

第 8-9 行声明公有槽 slot2Extension(),它在用户点击【Detail】按钮时被触发。

第 10-12 行声明两个私有成员变量 baseWidget 和 detailWidget,它们都是 QWidget 的 实例,分别代表伸缩前后的对话框窗体。

再来看看 ExtensionDlg 的实现文件。

#include "extensionDlg.h"
ExtensionDlg::ExtensionDlg()
{
    setWindowTitle(tr("Extension Dialog"));
    initBasicInfo();
    initDetailInfo();
    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(baseWidget);
    layout->addWidget(detailWidget);
    layout->setSizeConstraint(QLayout::SetFixedSize);
    layout->setSpacing(6);
    setLayout(layout);
}
void ExtensionDlg::initBasicInfo()
{
    baseWidget = new QWidget;
    QLabel *nameLabel = new QLabel(tr("Name"));
    QLineEdit *nameEdit = new QLineEdit;
    QLabel *sexLabel = new QLabel(tr("Sex"));
    QComboBox *sexComboBox = new QComboBox;
    sexComboBox->addItem(tr("male"));
    sexComboBox->addItem(tr("female"));
    QPushButton *okButton = new QPushButton(tr("OK"));
    QPushButton *detailButton = new QPushButton(tr("Detail"));
    connect(detailButton,SIGNAL(clicked()),this,SLOT(slot2Extension()));
    QDialogButtonBox *btnBox = new QDialogButtonBox(Qt::Horizontal);
    btnBox->addButton(okButton,QDialogButtonBox::ActionRole);
    btnBox->addButton(detailButton,QDialogButtonBox::ActionRole);
    QFormLayout *formLayout = new QFormLayout;
    formLayout->addRow(nameLabel,nameEdit);
    formLayout->addRow(sexLabel,sexComboBox);
    QVBoxLayout *vboxLayout = new QVBoxLayout;
    vboxLayout->addLayout(formLayout);
    vboxLayout->addWidget(btnBox);
    baseWidget->setLayout(vboxLayout);
}
void ExtensionDlg::initDetailInfo()
{
    detailWidget = new QWidget;
    QLabel *ageLabel = new QLabel(tr("Age"));
    QLineEdit *ageEdit = new QLineEdit;
    ageEdit->setText(tr("25"));
    QLabel *deptLabel = new QLabel(tr("Department"));
    QComboBox *deptComboBox = new QComboBox;
    deptComboBox->addItem(tr("department 1"));
    deptComboBox->addItem(tr("department 2"));
    deptComboBox->addItem(tr("department 3"));
    deptComboBox->addItem(tr("department 4"));
    QLabel *addressLabel = new QLabel(tr("address"));
    QLineEdit *addressEdit = new QLineEdit;
    QFormLayout *formLayout = new QFormLayout;
    formLayout->addRow(ageLabel,ageEdit);
    formLayout->addRow(deptLabel,deptComboBox);
    formLayout->addRow(addressLabel,addressEdit);
    detailWidget->setLayout(formLayout);
    detailWidget->hide();
}
void ExtensionDlg::slot2Extension()
{
    if (detailWidget->isHidden())
    {
        detailWidget->show();
    }
    else
    {
        detailWidget->hide();
    }
}

第 1-9 句是构造函数中的内容。

第 1 句设置应用程序的标题。

第 2 句调用 initBasicInfo()函数,初始化基本信息船窗体。 第 3 句调用 initDetailInfo()函数,初始化扩展信息窗体。 第 4-9 句设置窗体的布局。

第 4 句定义一个垂直布局类实体 layout。

第 5、6 两句,分别将 baseWidget 和 detailWidget 加入到布局中。

第 7、8 两句对于布局管理来说是非常常见的用法,必须熟练掌握。其中 setSizeConstraint()函数用于设置窗体的缩放模式,其默认取值是 QLayout::SetDefaultConstraint。这里取参数值为 Qlayout::SetFixedSize 是为了使窗体 的大小固定,不可经过鼠标拖动而改变大小;如果不这样设置,当用户再次点击 【Detail】按钮时,对话框将不能恢复到初始状态。 setSpacing()函数用于设置位于布局之中的窗口部件之间的间隔大小。

这里暂时不对布局管理的相关内容展开讲解,在《布局管理》这一章中将有详细的介绍。

第 9 句将刚刚设置好的布局应用加载到窗体上。

第 10-29 句是 initBasicInfo()函数体的内容。

第 10 句实例化 baseWiaget,注意 baseWidget 是全局变量。

第 11-18 句,依次定义窗体中的部件,注意在输入字符时,前面都加上了 tr()函数。 第 19 句对于整个程序来说是关键,它使用信号 /槽机制连接了 detailButton 的单击信号和窗口类 ExtensionDlg 的 slot2Extension()函数,这就使得整个对话框变得可伸缩。

第 20-22 行示范了 QDialogButtonBox 类的用法,它用于创建一个符合当前窗口部件样 式的一组按钮,并且它们被排列在某种布局之中。在 Qt Designer 中,最为常见的用法是 从窗口部件盒里面把默认的那个 QDialogButtonBox 窗口部件拖到界面上来,不过显然这并 不如使用代码来得方便。

第 20 行的 Qt::Horizontal 实参表示创建水平方向的按钮组合。

第 21、22 两行把两个按钮加入到这个组合之中。其中的 QDialogButtonBox::ActionRole 参数表示创建的按钮具有实际的功能,单击它可以引起对话框的某种变化。该参数可以有很 多不同的值,使得这些按钮具有不同的功能,它们都被包含在 QDialogButtonBox::ButtonRole 这个枚举值之中。

第 23-29 行设置窗体的布局。窗体的顶级布局是一个垂直布局,而其中嵌套了一个表 单布局。

第 23-25 行是设置表单布局的常用方法,表单布局常用于窗体界面元素可以整齐的分 成两列的情况。addRow()方法用于向布局中加入整行的界面元素。

第 26-29 行定义窗体的顶级布局,并将其两个元素 formLayout 和 btnBox 依次加入其中。

小贴士:布局也是一种窗口部件,认识到这一点很关键。

第 30-47 行定义了 initDetailInfo()函数。其实现过程与 initBasicInfo()函数大同 小异。只是需要注意第 47 行,正是由于对 detailWidget 调用了 hide()函数,才使得程序 初始时,显示的是基本信息,而将扩展信息隐藏了起来。 hide()函数用于将窗口隐藏起来, 它是 Qt 默认的槽函数之一,其原型如下:

void QWidget::hide () [slot]

它的作用等同于调用 setVisible(false)函数。

第 48-55 行定义了 slot2Extension()函数。前面讲到,它是我们自定义的槽函数,在 点击【Detail】按钮时,将被触发。它的内容很简单,就是判断扩展窗口是否被隐藏,如果被隐藏,就显示它;否则就隐藏它。 isHidden()函数用于判断窗体的显示窗体的显隐状态。

然后就可以像下面这样书写主函数。第 1 句是必需的。第 2-4 句定义 ExtensionDlg 的 对象,并模态的显示这个窗体。

#include <QApplication>
#include "extensionDlg.h"
int main(int argc, char * argv[])
{
    QApplication app(argc,argv);
    ExtensionDlg exDlg;
    exDlg.show();
    return app.exec();
}

最后就是书写项目文件 ExtensionDlg.pro 了。

TEMPLATE = app
TARGET =
DEPENDPATH += .
INCLUDEPATH += .
# Input
HEADERS += extensionDlg.h
SOURCES += extensionDlg.cpp main.cpp

第 1 句,表示程序的类型是 app。

第 2 句中 TARGET 指定可执行文件或库的基本文件名,其中不包含任何的扩展、前缀或 版本号,默认的就是当前的目录名。

第 4 句中,INCLUDEPATH 指定 C++编译器搜索全局头文件的路径。 第 5 句是注释。

第 6 句中,HEADERS 指定工程的 C++头文件(.h)。多个头文件的情况下,用空格隔 开。

第 7 句中,SOURCES 指定工程的 C++实现文件(.cpp),多个头文件的情况下,用空格隔开。

qmake 的语法规则是比较简明的,详细情况请参阅附录。 好了,到此我们就完成了使用子类化的方法创建自定义对话框窗口程序。这其中有几点还想再强调一下,一是要明白所谓子类化自 QDialog 类就是指通过继承自 QDialog 类来创建自定义的对话框类;二是要知道如果程序中用到了 Qt 的核心机制(如信号/槽),不 要忘记在类的声明处的第一行加入 Q_OBJECT 宏;三是要掌握判断和控制显隐对话框的方法;四是掌握定义窗口界面元素的方法(先定义,再设置其属性);五是了解布局管理的基本方法(后面还会学到);六是要掌握书写工程文件的基本方法。七是掌握 Qt Creator 的基本用法。

在下一节,我们将采用 Qt Designer 完成这个程序界面的布局,大家将会了解到,有 时候使用 Qt Designer 设计界面会方便一些。