第二章 创建对话框(创建 QDialog 的子类)

发布: 2008-05-29 22:19

第二章. 创建对话框




  • 创建 QDialog 的子类

  • 深入理解信号与槽的机制(Signals and Slots)

  • 快速创建对话框

  • 可变形对话框

  • 动态对话框

  • 内建物件和对话框类


这一章将教你如何利用 Qt 创建对话框。对话框向用户提供选项(options)和选择(choices),允许用户设置他们喜欢的选项值并做出他们的选择。它们之所以被称为对话框或者简单的称为对话,是因为它们提供了一种用户和应用程序用来相互对话的方式。


大多数 GUI 应用程序是由一个主窗口、一个菜单栏、若干工具栏和一些作为主窗口补充的对话框构成。单独创建通过适当的行为直接响应用户的选择的对话框程序也是可能的(比如,一个计算器程序)。


首先,我们将通过手写代码创建一个仅有对话框的应用程序向你展示它是怎样被完成的。然后我们将看看如何用 Qt Designer —— Qt 的可视化设计工具创建对话框。用 Qt Designer 要比手写代码快很多,并且使得以后测试不同的设计和更改设计更加容易。


创建 QDialog 的子类


我们的第一个例子是一个完全用 C++ 写的搜索对话框。我们将用一个单独的类来实现这个对话框。这样做,可以使这个对话框成为一个独立的、自包含的组件,有它自己的信号与槽。


2.1. The Find dialog


 


源代码被分别放在两个文件:finddialog.h finddialog.cpp。我们先从 finddialog.h 开始。


1 #ifndef FINDDIALOG_H

2 #define FINDDIALOG_H

3 #include <QDialog>

4 class QCheckBox;

5 class QLabel;

6 class QLineEdit;

7 class QPushButton;

1 2 (和行27)防止这个头文件被多次包含。


3 包含了 QDialog 的定义, Qt 的对话框基类。 QDialog  继承自 Qwidget 类。


4 7 前置声明了我们将用来实现对话框的类。前置声明告诉 C++ 编译器一个类的存在,不需要给出类定义的所有细节(通常可以从头文件和文件本身找到)。很快我们将对此做进一步介绍。


接下来,我们定义一个 QDialog 的子类 FindDialog


8 class FindDialog : public QDialog

9 {

10     Q_OBJECT

11 public:

12     FindDialog(QWidget *parent = 0);

类定义开头的Q_OBJECT 宏是所有定义信号或槽的类所必需的。


构造函数 FindDialog 显示了 FindDialog 是一个典型的 Qt 物件类。parent 参数指向其父物件。默认是一个空指针,说明这个对话框没有父物件。


13 signals:

14     void findNext(const QString &str, Qt::CaseSensitivity cs);

15     void findPrevious(const QString &str, Qt::CaseSensitivity cs);

信号(signals)部分声明了当用户点击 Find 按钮后对话框将发出的两个信号。如果向上选择选项被打开,对话框发出 findPrevious() 信号;否则,它发出 findNext()信号。


关键字 signals 实际上是一个宏。C++ 预处理器在它被送到编译器之前将把它转化成标准 C++Qt::CaseSensitivity 是一个取值为 Qt::CaseSensitive 和Qt::CaseInsensitive的枚举类型数据。


16 private slots:

17     void findClicked();

18     void enableFindButton(const QString &text);

19 private:

20     QLabel *label;

21     QLineEdit *lineEdit;

22     QCheckBox *caseCheckBox;

23     QCheckBox *backwardCheckBox;

24     QPushButton *findButton;

25     QPushButton *closeButton;

26 };

27 #endif

   在类的私有定义(private)部分,我们定义了两个槽。要实现这两个槽,我们需要访问对话框的大部分子物件,所以我们也保存了指向他们的指针。关键词 slots,就像 signals,也是一个可以转化为一个 C++ 编译器能够识别的结构体


对于私有变量,我们前置声明了他们的类指针。这是可能的,因为他们都是指针并且我们不在头文件中存取它们,所以编译器不需要类的完整定义。当然,我们本应该包含相关的头文件(<QCheckBox>,<QLabel>,),但是利用前置声明如果可能的话可以使编译的速度稍微加快。


我们现在看看包含了 FindDialog 类实现的 finddialog.cpp 的内容


1  #include <QtGui>

2  #include "finddialog.h"

 


首先,我们包含了 <QtGui>,一个包含 Qt GUI 类定义的头文件。Qt由几个模块组成,每个模块都是自成一体独立的库最重要的几个模块是:QtCore, QtGui, QtNetwork,  QtOpenGL, QtSql, QtSvg QtXml<QtGui>头文件包含了QtCore QtGui 模块中所有类的定义。包含这个头文件可以免去我们一个一个包含每个单独的类的麻烦。


filedialog.h 中,我们可以简单地用包含 <QtGui> 来代替 <QDialog> 的包含和QCheckBox, Qlabel, QlineEdit, QPushButton的前置声明。但是,在一个头文件中包含另一个大的头文件通常是一个不好的风格,尤其是在大型应用程序的开发中。


3 FindDialog::FindDialog(QWidget *parent)

4     : QDialog(parent)

5 {

6    label = new QLabel(tr("Find &what:"));

7     lineEdit = new QLineEdit;

8     label->setBuddy(lineEdit);

9     caseCheckBox = new QCheckBox(tr("Match &case"));

10     backwardCheckBox = new QCheckBox(tr("Search &backward"));

11     findButton = new QPushButton(tr("&Find"));

12    findButton->setDefault(true);

13    findButton->setEnabled(false);

14     closeButton = new QPushButton(tr("Close"));

在第 4 行,我们向基类的构造函数传递 parent 参数。然后我们创建子物件。 用字符串文本作为参数调用 tr() 函数可以标记出它们来翻译成其他语言。这个函数在 QObject 和所有包含 Q_OBJECT 宏的子类中声明。用 TR() 包围用户可视的字符串是一个好的习惯,即使你没有立即将你的应用程序翻译成其他语言的打算。翻译 Qt 应用程序将在 第十七章做详细介绍。


在字符串文本中,我们'&'符号指示快捷键。例如,在行 11 创建了一个 Find 按钮,用户可以在支持快捷键的系统上用快捷键 Alt+F来激活它。'&'符号还可以用来控制焦点:在第6行,我们创建了一个带有一个快捷键(Alt+W)的标签,在第8行我们设置标签的伙伴(buddy)为行编辑器。一个伙伴(buddy)是一个在标签的快捷键被按下时接受焦点的物件。所以当用户按下 Alt+W(标签的快捷键),行编辑器(标签的伙伴)将拥有焦点。


在第12行,我们调用 setDefault(true) Find 按钮设置为对话框的默认按钮。默认按钮是当用户敲回车后将被按下的按钮。在第13行,我们禁用了 Find 按钮。当一个物件被禁用,它通常灰化显示并且不再响应用户的交互。


15     connect(lineEdit, SIGNAL(textChanged(const QString &)),

16            this, SLOT(enableFindButton(const QString &)));

17     connect(findButton, SIGNAL(clicked()),

18            this, SLOT(findClicked()));

19     connect(closeButton, SIGNAL(clicked()),

20             this, SLOT(close()));

私有槽 enableFindButton(const QString &) 将在每次行编辑器中的文本有所改变的时候被调用。私有槽 findClicked() 将在用户点击 Find 按钮时被调用。当用户点击 Close 时对话框将关闭。槽 close() 是从 Qwidget 继承而来,它的默认行为是从视图中隐藏物件(而不是删除它)。稍后,我们将看一下 enableFindButton() findClicked() 的源代码。


因为 QObject FindDialog 的一个祖先类,你可以省略 connect() 调用前面的 QObject:: 前缀。


21     QHBoxLayout *topLeftLayout = new QHBoxLayout;

22     topLeftLayout->addWidget(label);

23    topLeftLayout->addWidget(lineEdit);

24     QVBoxLayout *leftLayout = new QVBoxLayout;

25    leftLayout->addLayout(topLeftLayout);

26    leftLayout->addWidget(caseCheckBox);

27    leftLayout->addWidget(backwardCheckBox);

28     QVBoxLayout *rightLayout = new QVBoxLayout;

29    rightLayout->addWidget(findButton);

30    rightLayout->addWidget(closeButton);

31    rightLayout->addStretch();

32     QHBoxLayout *mainLayout = new QHBoxLayout;

33    mainLayout->addLayout(leftLayout);

34    mainLayout->addLayout(rightLayout);

35    setLayout(mainLayout);

 


接下来,我们用布局管理器来布局子物件。布局可以包含物件和其他布局。通过不同的组合嵌套 QHBoxLayoutsQVBoxLayoutsQGridLayouts,你可以创建一些很专业的对话框。


对于 Find 对话框,我们用了两个 QHBoxLayouts 和两个 QVBoxLayouts,就像 2.2 展示的那样,外层布局是主要布局;它在35行被安装到 FindDialog 用来响应整个对话框的行为。其它三个布局是子布局。 2.2右下角的弹簧是一个空白项 (或称伸展”)。它用来填充 Find Close 按钮下的空白,确保这两个按钮占据他所在布局的上部。


2.2. The Find dialog's layouts


 


比较微妙的一点是布局管理类不是物件,相反,它继承自 Qlayout,而 QLayout则继承了QObject。图中,物件用实线框代表而布局用虚线框代表作为两者之间的区别。布局在程序运行时是不可见的。


当子布局被加入到父布局时 ( 25, 33, 34),子布局的父布局会被自动重新布局。然后,当主布局被安装到对话框(行 35),它会变成对话框的子布局。2.3描述了这一父子继承关系。


2.3. The Find dialog's parentchild relationships


 


36     setWindowTitle(tr("Find"));

37     setFixedHeight(sizeHint().height());

38 }

 


最后,我们将为对话框设置在标题栏显示的标题并为窗口设置一个固定高度,因为对话框中没有任何一个物件可以占据额外的垂直空间。 QWidget::sizeHint() 函数返回一个物件的理想大小。


以上我们完整的定义了 FindDialog 的构造函数。既然我们用 new 创建了一个对话框物件和布局,看起来我们需要写一个析构函数对每一个我们创建的物件调用 delete 。但这并不是必需的,因为 Qt 会在父对象销毁时自动释放子对象,并且子物件和布局都是 FindDialog 的派生物。


现在我们看一下对话框的槽:


39 void FindDialog::findClicked()

40 {

41     QString text = lineEdit->text();

42     Qt::CaseSensitivity cs =

43            caseCheckBox->isChecked() ? Qt::CaseSensitive

44                                      : Qt::CaseInsensitive;

45     if (backwardCheckBox->isChecked()) {

46        emit findPrevious(text, cs);

47     } else {

48         emit findNext(text, cs);

49     }

50 }

51 void FindDialog::enableFindButton(const QString &text)

52 {

53     findButton->setEnabled(!text.isEmpty());

54 }

当用户点击 Find 按钮时槽 findClicked() 会被调用,Find 按钮会发出 findPrevious() findNext() 信号,这取决于向后选择 (Search backward) 选项。关键词 emit Qt 独有的;象其它 Qt 扩展它会被 C++ 预处理器转换成标准的 C++


无论何时,在用户改变行编辑器的文本的时候,槽 enableFindButton() 会被调用,如果编辑器里有文本会使按钮可用,没有则不可用。


这两个槽完善了对话框。现在我们创建一个main.cpp 文件来测试我们的uanggeFindDialog 物件:


1 #include <QApplication>

2 #include "finddialog.h"

3 int main(int argc, char *argv[])

4 {

5     QApplication app(argc, argv);

6     FindDialog *dialog = new FindDialog;

7     dialog->show();

8     return app.exec();

9 }

要编译这个程序,运行 qmake。因为 FindDialog 类的定义包含了 Q_OBJECT 宏,由 qmake 生成的 makefile 将包含运行 moc ——Qt 的元对象编译器的特殊规则(Qt 元对象系统将在下一部分讲解)。


为了使 moc 正确工作,我们必需将类的定义放到一个头文件,从实现文件中分离出来。由 moc 生成的代码包含这个头文件并且添加一些 C++ 自有的魔力(magic)


运用 Q_OBJECT 宏的类必需运行 moc。这不是问题因为 qmake 会自动向makefile 添加需要的规则。但是如果你忘记用 qmake 重新生成你的 makefile 文件,并且没有运行 moc,连接器会报告一些函数没有实现就被声明了。显示信息可能相当晦涩。GCC 产生的警告象下面这样:


     finddialog.o: In function 'FindDialog::tr(char const*, char const*)':

     /usr/lib/qt/src/corelib/global/qglobal.h:1430: undefined reference to

     'FindDialog::staticMetaObject'

 


Visual C++ 则会这样显示:


     finddialog.obj : error LNK2001: unresolved external symbol

     "public:~virtual int __thiscall MyClass::qt_metacall(enum QMetaObject

     ::Call,int,void * *)"

如果这在你身上发生,运行 qmake 升级你的makefile,然后重新编译应用程序。


现在运行程序。如果你的系统支持快捷键,用 Alt+WAlt+CAlt+B Alt+F验证它们是否触发正确的行为。按 Tab 键浏览所有的物件。默认的 Tab 顺序是物件被创建的顺序。可以用 QWidget::setTabOrder() 改变默认顺序。


提供一个明智的 Tab顺序和键盘快捷键确保不喜欢(或者不能)用鼠标的用户能够充分利用这个应用程序。完全的键盘控制也被快速的打字员所欣赏。


在 第三章,我们将把 Find 对话框运用到真实的应用程序,并且连接 findPrevious()和 findNext()信号到某些槽。



原文: http://qtchina.tk/?q=node/98

Powered by zexport