第2章 对话框——QDialog
对话框是一种特殊的窗口,它一般用来提供反馈信息或从用户处获取输入,对话框有各种各样的形状和尺寸,其范围包括从简单用于显示单行信息的对话框到包含复杂控件的大型对话框。
对话框给用户提供了一种同应用程序进行交互的便捷方式。因此,较大型的GUI应用程序中都少不了形形色色的对话框,像系统选项、字体和风格的设置,以及各种形式的向导等。这一章将学习如何在Qt GUI应用程序中创建和使用对话框,了解Qt的内建(built-in)对话框。
2.1 自定义对话框
QDialog是所有Qt对话框窗口的基类,它继承自QWidget,如图2-1所示。
图2-1 对话框类图
下面,根据Qt提供的QDialog基类建立一个自定义的对话框类。通过这个例子,将学习如何继承QDialog实现自定义的对话框,如何添加新的窗口部件,以及如何添加自己的界面控制代码。
在第1章中,使用了两种工具建立Qt应用程序。在接下来的章节中,将使用KDevelop集成开发工具进行代码的编辑和调试。
2.1.1 建立新类
首先,建立名字为“mydialog”的KDevelop工程。然后,为工程新建文件,步骤如下。
选择“文件”菜单中的“新建”命令,打开“新建文件”对话框,如图2-2所示;
在“新建文件”对话框中输入新建文件的名称“logindlg.h”,选中“添加到工程中”选项,然后单击“确定”按钮。
这时,新建的logindlg.h将会添加到工程中。打开KDevelop界面右侧的“QMake管理器”选项卡,将会在它的“HEADERS”列表中看到这个文件,如图2-3所示。
图2-2 新建文件logindlg.h
图2-3 新建文件在工程中
使用同样的方法,在工程中加入“logindlg.cpp”文件。
自定义对话框类CLoginDlg的定义文件logindlg.h内容如下。
// chapter02/mydialog/src/logindlg.h. #ifndef _LOGINDLG_H_ #define _LOGINDLG_H_
定义宏变量,确保该头文件只被包含一次,防止头文件被多次包含。
#include <QtGui/QDialog> class CLoginDlg : public QDialog { Q_OBJECT public: CLoginDlg(QWidget* = 0); }; #endif
自定义类CLoginDlg继承自QDialog,因此必须包含基类QDialog的定义。
Q_OBJECT宏的作用是启动Qt元对象系统的一些特性(比如支持信号和槽等),它必须放置到类定义的私有区。Qt元对象系统将在第3章介绍。
CLoginDlg(QWidget* = 0)是构造函数,它指定了一个默认值为NULL的指向QWidget的参数。该形参定义了自定义对话框对象的父窗口部件,默认值NULL意味着自定义的对话框没有父窗口部件。
目前,CLoginDlg类的文件dialog.cpp仅仅实现一个空白的构造函数。
// chapter02/mydialog/src/logindlg.cpp. #include "logindlg.h" CLoginDlg::CLoginDlg(QWidget* parent) : QDialog(parent) { }
编译、连接并运行程序。此时用户界面是一个没有任何内容的空白对话框。
2.1.2 添加子窗口部件
上述定义的对话框是一个空白对话框,现在加入一些新的窗口部件。自定义对话框的界面布局如图2-4所示。
图2-4 登录对话框界面布局
现在重新定义该类,其头文件logindlg.h内容如下所示。
//chapter02/mydialog/src/logindlg.h. #ifndef _LOGINDLG_H_ #define _LOGINDLG_H_ #include <QtGui/QDialog> class QLineEdit; class CLoginDlg : public QDialog { Q_OBJECT public: CLoginDlg(QWidget* = 0); public slots: virtual void accept(); private: QLineEdit* usrLineEdit; QLineEdit* pwdLineEdit; }; #endif
代码“class QLineEdit”是类QLineEdit的传递声明,因为在类CLoginDlg的头文件中仅仅使用了指向QLineEdit对象的指针,因此在编译头文件时,gcc编译器不需要知道QLineEdit类的定义。该行代码的作用是告诉编译器,QLineEdit类已经存在。这样做有如下好处:
● 首先,它减小了头文件的大小,增加了编译速度(特别是当该头文件被其他文件多次包含引用时);
● 其次,这样做可以避免因包含头文件的顺序不当而造成连接错误,特别是在大的工程当中更应该避免随意地在一个头文件中包含另一个头文件。
在头文件中,重新声明了基类QDialog类的虚函数accept()。在类CLoginDlg的实现文件logindlg.cpp中,将重写该函数,目的是为了验证用户输入的用户名和密码的有效性。
在类的私有区,声明了指向QLineEdit对象的指针成员。其中,usrLineEdit对象存放、显示用户输入的用户名,pwdLineEdit对象存放、显示用户输入的密码。
现在来看一下自定义对话框类CLoginDlg的实现文件logindlg.cpp。
//chapter02/mydialog/src/logindlg.cpp. #include <QtGui/QtGui> #include "logindlg.h"
在文件开头,包含了Qt用户界面头文件QtGui。QtGui头文件包含了QtCore模块和QtGui模块的所有Qt类的定义。由于在类CLoginDlg的实现文件logindlg.cpp中用到了很多的QtCore类和QtGui类,通过包含QtGui头文件,可以避免一一包含所有要使用到的Qt类的头文件。
下面是CLoginDlg类的构造函数,该函数完成登录对话框的初始化操作。
CLoginDlg::CLoginDlg(QWidget* parent) : QDialog(parent) {
调用CLoginDlg的父类QDialog的构造函数,并将实参parent传递给父类的构造函数,以设置登录对话框的父窗口部件。
QLabel* usrLabel = new QLabel(tr("用户名:")); QLabel* pwdLabel = new QLabel(tr("密码:")); usrLineEdit = new QLineEdit; pwdLineEdit = new QLineEdit; pwdLineEdit->setEchoMode(QLineEdit::Password);
创建QLabel标签对象usrLabel和pwdLabel,以提示用户输入“用户名”和“密码”。
接下来,创建输入用户名及其密码的行编辑框QLineEdit对象usrLineEdit和pwdLineEidt。
函数QLineEdit::setEchoMode() 设置密码编辑框对象pwdLineEdit的内容显示方式为QLineEdit::Password,即采用星号“*”代替用户输入的字符。通常行编辑框QLineEdit窗口部件的显示方式有下列几种方式:
● QLineEdit::Normal,默认的显示方式,显示用户实际输入的内容。
● QLineEdit::Password,用星号“*”代替用户实际输入的内容。
● QLineEdit::NoEcho,不显示用户输入的任何内容,尽管QLineEdit::Password可以有效地起到密码保护的作用,但它仍然显示了用户输入字符的个数;而QLineEdit::NoEcho对显示内容采取了更进一步的保密措施。
● QLineEdit::PasswordEchoOnEdit,仅仅用户在行编辑框里编辑文本的内容时,才显示用户输入的字符,而在用户完成编辑后以星号“*”代替输入的内容。
QGridLayout* gridLayout = new QGridLayout; gridLayout->addWidget(usrLabel, 0, 0, 1, 1); gridLayout->addWidget(usrLineEdit, 0, 1, 1, 3); gridLayout->addWidget(pwdLabel, 1, 0, 1, 1); gridLayout->addWidget(pwdLineEdit, 1, 1, 1, 3);
创建一个网格布局管理器QGridLayout对象gridLayout,并将窗口部件添加到该布局管理器中。
函数QGridLayout::addWidget()将先前生成的标签和行编辑框添加到网格管理器gridLayout中。在上面的代码中,QGridLayout::addWidget(usrLineEdit, 0, 1, 1, 3)函数共有5个参数,实参usrLineEdit指出哪一个窗口部件将被放置在网格布局管理器gridLayout中;后4 个参数确定了行编辑框对象usrLineEdit在网格布局管理器gridLayout中的具体位置,其中前两个实参分别表示行和列的位置(行号和列号),后两个参数分别表示行的跨度和列的跨度,如图2-5所示。
图2-5 网格布局管理器的布局管理
QPushButton* okBtn = new QPushButton(tr("确定")); QPushButton* cancelBtn = new QPushButton(tr("取消")); QHBoxLayout* btnLayout = new QHBoxLayout; btnLayout->setSpacing(60); btnLayout->addWidget(okBtn); btnLayout->addWidget(cancelBtn);
创建“确定”和“取消”两个QPushButton按钮对象。其中,okBtn按钮完成对话框的登录验证;cancelBtn按钮完成取消登录并退出对话框的功能。
接下来,创建排布和管理按钮窗口部件的水平布局管理器QHBoxLayout对象btnLayout。
函数QHBoxLayout::setSpacing()设置水平布局管理器btnLayout对象内部窗口部件之间的间隔(如图2-4所示)为60。
函数QHBoxLayout::addWidget()将okBtn按钮和cancelBtn按钮加入到水平布局管理器btnLayout中。这两个按钮将按放置的先后顺序依次从左向右排布(默认值),且之间的间隔为60。
QVBoxLayout* dlgLayout = new QVBoxLayout; dlgLayout->setMargin(40); dlgLayout->addLayout(gridLayout); dlgLayout->addStretch(40); dlgLayout->addLayout(btnLayout); setLayout(dlgLayout);
创建一个垂直布局管理器QVBoxLayout对象dlgLayout。该管理器将排布和管理先前创建的所有布局管理器,进而完成对所有子窗口部件的管理。
函数QVBoxLayout::addLayout()将布局管理器gridLayout和btnLayout添加到dlgLayout布局管理器中。这样,dlgLayout管理gridLayout和btnLayout,而后两个管理器进一步管理放在它们当中的子窗口部件。
函数QVBoxLayout::setMargin() 设置布局管理器dlgLayout边框的宽度为40,即其内部子窗口部件距离布局管理器边界(布局管理器的边界是不可见的)的距离(如图2-4所示)为40。
函数QVBoxLayout::addStretch() 函数在垂直布局管理器dlgLayout对象中加入一个大小为40的stretch,这将使得布局管理器gridLayout和btnLayout之间的默认距离设置为40,同时当上下拉伸对话框的高度时,该stretch可以自由伸缩,从而保证gridLayout和btnLayout管理器内部各窗口部件的高度以及彼此间的垂直距离保持不变。
函数QWidget::setLayout()函数将垂直布局管理器dlgLayout设置为登录对话框的顶层布局管理器。此时,当任意拉伸对话框时,dlgLayout布局管理器都能够有效地管理对话框内部的所有子窗口部件。
connect(okBtn, SIGNAL(clicked()), this, SLOT(accept())); connect(cancelBtn, SIGNAL(clicked()), this, SLOT(reject()));
关联“确定”按钮的QpushButton::clicked()信号到登录对话框的accept()槽,当用户单击“确定”按钮时能够进行用户名和密码的验证。
接下来,关联“取消”按钮的QpushButton::clicked() 信号到登录对话框的reject()槽,以使得对话框能够响应用户的退出操作。QDialog::reject() 槽将会隐藏登录对话框,并将对话框的返回代码设置为QDialog::Rejected。此时对话框将关闭,启动对话框的槽函数QDialog::exec()将返回运行结果QDialog:: Rejected。
setWindowTitle(tr("登录")); resize(300, 200); }
函数QWidget::setWindowTitle() 设置对话框的标题为“登录”。
最后,函数QWidget::resize() 重新设置对话框的大小为(300,200),即高为300、宽为200。
为了实现对“用户名”和“密码”的有效性验证,本例重写了QDialog的虚槽函数accept(),该函数响应用户单击“确定”按钮的QPushButton::clicked()信号。
void CLoginDlg::accept() { if(usrLineEdit->text().trimmed() == tr("lcf") && pwdLineEdit->text() == tr("lcf")) { QDialog::accept(); }
取出用户输入的用户名和密码并和预定的值进行比对,如果用户名和密码全部正确,调用父类的QDialog::accept()槽函数,该函数将关闭模态对话框,设置对话框的运行结果为QDialog::Accepted,并发送QDialog::finished(int result)信号。此时对话框将关闭,启动对话框的槽函数QDialog::exec()将返回运行结果QDialog::Accepted。
函数QLineEdit::text()返回行编辑框中的文本内容,返回的类型是一个QString字符串。函数QString::trimmed()移除在字符串开头和结尾的空白字符,并返回移除空白字符后的字符串。这些空白字符包括ASCII字符“\t”、“\n”、“\v”、“\f”、“\r”和空格“ ”。
else { QMessageBox::warning(this, tr("警告"), tr("用户或密码错误!"), QMessageBox::Yes); usrLineEdit->setFocus(); } }
如果用户输入的用户名或密码有一个错误,那么程序将执行上面这段代码。它将显示一个警告提示框,告诉用户其输入的用户名和密码是有错误的。
函数QMessageBox::warning()将会创建并显示一个模态的警告对话框,提示用户输入的用户名或密码错误。
函数QLineEdit::setFocus()将鼠标的焦点定位到行编辑框对象usrLineEdit,以方便用户进行“用户名”或“密码”的修改。此段代码运行后,登录对话框将返回到可输入/操作的状态,此时用户可以进行相关的操作,或修改用户名/密码,或选择退出。
QMessageBox类提供了显示操作信息的几种模态对话框:
● QMessageBox::about,一个仅仅带有标题和简单文本的消息框(如图2-6所示),一般用于显示帮助提示信息。
● QMessageBox::aboutQt,显示关于Qt的消息框(如图2-7所示),包括Qt版本以及Nokia公司的产品信息等。
图2-6 关于消息框
图2-7 关于Qt消息框
● QMessageBox::information,一个具有主题和提示文本的提示消息框(如图2-8所示),程序员可以根据情况定制按钮的个数,以及各个按钮的角色。
● QMessageBox::question,一个具有标题和文本信息的询问消息框(如图2-9 所示),程序员可以根据情况定制按钮的个数,以及各个按钮的角色。
图2-8 提示消息框
图2-9 询问消息框
● QMessageBox::warning,一个具有标题和文本信息的警告消息框(如图2-10所示),程序员可以根据情况定制按钮的个数,以及各个按钮的角色。
● QMessageBox::critical,一个具有标题和文本信息的致命错误消息框(如图2-11所示),程序员可以定制按钮的个数,以及各个按钮的角色。
图2-10 警告消息框
图2-11 致命错误消息框
QMessageBox的几种模态消息框的标题栏(title bar)使用父窗口部件的图标;此外,MessageBox提供的后四种消息框都具有返回值,程序可以根据用户的响应情况进行下一步的处理。下面看一个消息框类QMessageBox高级应用的例子:
QMessageBox box; box.setWindowTitle(tr("警告")); box.setIcon(QMessageBox::Warning); box.setText(tr("程序安装错误,是否退出?")); box.setStandardButtons(QMessageBox::Yes | QMessageBox::No); box.setDetailedText(tr("请查看安装介质有无损坏。")); switch(box.exec()) { case QMessageBox::Yes: // 进行下一步处理 break; case QMessageBox::No: // 进行下一步处理 break; default: // 进行默认处理 break; }
第1行代码调用QMessageBox的默认构造函数创建一个QMessageBox栈对象box,父窗口部件设置为NULL。
函数QMessageBox::setIcon() 设置消息框的图标为QMessageBox::Warning,即消息框是一个警告消息框。
函数QMessageBox::setWindowTitle() 设置消息框的标题为“警告”。
函数QMessageBox::setStandardButtons() 设置消息框的标准按钮(也可以调用QMessageBox::addButton() 函数添加自定义的按钮,并通过QMessageBox::clickedButton()函数获取用户单击的按钮),此处添加了两个标准按钮:QMessageBox::Yes和QMessageBox::No。
函数QMessageBox::setDetailedText() 添加“Show Details...”按钮,详细信息内容设置为“请查看安装介质有无损坏。”。
消息框的运行效果如图2-12所示。
图2-12 一个复杂的消息框