创建对话框
接下来我们用两种方法来创建一个对话框,一种是纯手工编码,一种是用Qt Designer(一种可视化的工具)来创建。
第一个例子是建立一个查找对话框。
创建两个文件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行避免头文件被多次包含
第3行包含QDialog类的定义,是Qt中所有对话框的基类, QDialog继承与QWidget.
第4至7行是这个类定义中的前向申明。 前向申明(forward declaration)告诉C++编译器这个类存在,而不包含这个类的详细的定义,通常这个定义在这个类的额头文件中,这样加快编译速度。
接下来我们定义FindDialog类
8 class FindDialog : public QDialog
9 {
10 Q_OBJECT
11 public:
12 FindDialog(QWidget *parent = 0);
Q_OBJECT是一个宏,对于所有需要定义信号和槽的类,这个宏必须放在此类定义的开头。
FindDialog构造函数是一个典型的Qt widget类。 Parent参数用于指定父widget.默认值是null,意味着这个对话框没有父窗口。
13 signals:
14 void findNext(const QString &str, Qt::CaseSensitivity cs);
15 void findPrevious(const QString &str, Qt::CaseSensitivity cs);
signals部分定义2个信号,当用户点击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部分我们定义了2个槽函数。Slots关键字跟signals一样是一个宏。
接下来看看finddialog.cpp文件,里面包含finddialog类的具体实现。
1 #include <QtGui>
2 #include "finddialog.h"
首先我们包含了<QtGui>, 这是一个头文件里面包含了所有Qt GUI类的定义。
Qt由很多模块组成,每个模块都有自己的库。最重要的几个模块是 QtCore, QtGui, QtNetwork, QtOpenGL, QtScript, QtSql, QtSvg和QtXml。<QtGui>头文件包含了所有QtGui模块和一部分QtCore模块中的类。包含这个类我们就不需要单独的一个一个去包含我们需要用到的类了。
在finddialog.h头文件中,我们也可以简单的包含<QtGui>,但这不是一个好的编程风格,<QtGui>是一个很大的头文件,在一个头文件中包含另一个大的头文件会大大降低编译速度。所以这里我们只包含<QDialog>,再用几个前向申明来代替。
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参数传给基类QDialog.然后创建一个QLabel。函数tr()用于把字符串转换为其他语言。tr()在QOject类中定义以及所有包含Q_OBJECT宏的子类。这是一个很好的编程习惯,把所有用户可见的字符串用函数tr()进行转换,即使目前你还没有计划把程序转换为其他语言。在第18章中我们还会讲。
字符串中&代表快捷键。例如第11行中创建一个Find按钮,用户按下Alt+F可以触发一个按钮事件。&符号还可以用来控制焦点:第6行我们创建一个label,快捷键为Alt+W,在第8行我们设置label的密友(buddy)为line editor.当label的快捷键按下后,它的密友widget就得到焦点。所以当用户按下Alt+W时,line editor得到焦点。
第12行我们设置Find按钮为对话框的默认按钮,即当用户按下Enter时,默认按钮被触发。
第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()));
当line editor中的文本发生变化时,私有槽函数enableFindButton(const QString &)被调用。当Find按钮按下,槽函数findClicked()被调用。当Close按钮按下时,槽函数close()被调用,关闭对话框。Close()槽函数继承与QWidget,默认行为是隐藏widget,并没有真正被删除。稍后我们会看到槽函数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);
接下来,我们用Qt的布局管理器来布局这些Widgets.Layouts可以包含widget和其它的layouts.通过QHBoxLayouts,QVBoxLayouts和QGridLayouts三个类的不同组合,我们可以定制非常复杂的对话框。
在Find对话框中,我们使用两个QHBoxLayoutsuts和两个QVBoxLayouts,如下图。
mainLayout是最外层的layout,被安装在FindDialog上,负责整个对话框的布局。其他三个layouts是其sub-layouts,右下角的小弹簧是一个spacer,用于填充Find和Close按钮下面的空白空间,以确保这两个按钮位于layout的上部。
有一点需要注意的是,布局管理器不是widgets,它们不是从基类QWidget继承,而是从QLayout基类继承,QLayout和QWidget都是继承与父类QObject。
当子布局(sublayouts)被加入到父布局(parent layout),如第25,33,34行,布局的父子关系被重新绑定。最后,主布局(main layout)被安装在对话框上,则主布局变为对话框的子窗口,这样主布局中所有的widgets变为对话框的子widget(子窗口)。
36 setWindowTitle(tr("Find"));
37 setFixedHeight(sizeHint().height());
38 }
最后,我们设置对话框的标题栏,设置窗口为固定高度。函数QWidget::sizeHint()返回一个widget的理想size.
我们已经浏览了整个FindDialog类的构造函数。构造函数中,我们动态创建了各个widget和layouts,这里我们不需要在析构函数中显式的delete它们,因为Qt会帮我们做这个事,在Qt中,当一个父窗口被删除时,它的所有子widget和layouts都会被自动删除。
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()槽被触发,它发射findPrevious()或findNext()信号。 Emit是Qt中的关键字。
当用户改变line editor中的文本时,enableFindButton槽函数被触发。如果line editor中有文本,按钮使能。
接下来我们创建一个main.cpp来测试我们的对话框。
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的元对象编译器(meta-object compiler).
要使moc正确工作,我们必须把类定义放在一个头文件中,从类的实现文件中独立出来。Moc产生的代码中包含这个头文件,并且加入了一些C++的模板代码。
使用Q_OBJECT宏的类必须运行moc, qmake会自动增加必要的规则到makefile中。但是有时候如果我们忘了用qmake重新生成makefile,链接器(linker)就会提示未定义的错误。
信号和槽(Signals和Slots)
信号和槽机制是Qt编程的基础也是核心概念,这个机制可以使得编程人员绑定不同的对象,这些对象可以互不关联的情况下被信号和槽绑定。
槽函数几乎跟一般的C++成员函数一样。它们可以虚拟的,它们也可以重载;它们可以公有的,也可以受保护的或是私有的成员;它们可以像任何C++成员函数一样被直接调用,它们的参数可以是任何类型。唯一的不同是,槽函数还可以被用来连接一个信号,在这种情况下,每当这个信号被发射,槽函数会自动被调用。
connect()函数申明如下:
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
函数中sender和receiver是指向QObject对象的指针,而signal和slot是不带参数名的函数签名。 SIGNAL()和SLOT()是宏,用来把这些函数签名转化为字符串。
信号和槽的连接有如下几种情况:
1.个信号可以与多个槽连接:
a) connect(slider, SIGNAL(valueChanged(int)), spinBox, SLOT(setValue(int));
b) connect(slider, SIGNAL(valueChanged(int)), this, SLOT(updateStatusBarIndicator(int));
c) 当一个信号被发射时,这些被连接的槽函数被逐个调用,顺序是任意的。
2.多个信号与同一个槽连接:
a) connect(lcd, SIGNAL(overflow()), this, SLOT(handleMathError()));
b) connect(calculator, SIGNAL(divisionByZero()), this, SLOT(handleMathError)));
c) 当任何一个信号被发射时,槽函数被执行。
3.一个信号可以被连接到另一个信号:
a) connect(lineEdit, SIGNAL(textChanged(const QString &)), this, SIGNAL(updateRecord(const QString &)));
b) 当第一个信号被发射时,第二个信号也被立即发射。除了这一点,其他的跟信号-槽连接一模一样。
4.连接可以被取消:
a) disconnect(lcd, SIGNAL(overflow()), this, SLOT(handleMathError)));
b) 这个一般不需要,因为当一个对象被删除时,Qt会自动把对象相关的连接给取消。
要成功建立一个信号-槽连接,信号函数和槽函数必须要有相同的参数类型及次序:
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)), this, SLOT(processReply(int, const QString &)));
一种情况,当信号函数中有多与向连接的槽函数的参数,额外的参数将会被忽略:
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)), this, SLOT(checkErrorCode(int)));
如果参数类型不匹配,或者如果信号或槽函数不存在,Qt在debug模式下的运行时会发出一个警告。同样,如果信号或槽函数签名中包含参数名,Qt也会发出警告。
到目前为止,我们只在widget中应用信号-槽机制。其实这个机制在类QObject中实现,不仅仅限与GUI编程,这个机制可以拥有任何QObject的子类中。
class Employee : public QObject
{
Q_OBJECT
public:
Employee() { mySalary = 0; }
int salary() const { return mySalary; }
public slots:
void setSalary(int newSalary);
signals:
void salaryChanged(int newSalary);
private:
int mySalary;
};
Qt元-对象(Meta-Object)系统
Qt其中一个亮点是扩展了C++标准,它使得独立的软件模块可以被绑定起来,而模块彼此之前不需要互相了解。
这个机制叫做元-对象系统, 它提供两个关键的服务:信号-槽机制和内省(introspection)机制。内省机制允许编程人员在运行时得到QObject子类的元信息(meta-information),其中包含这个对象支持的信号和槽,以及对象的类名。这个机制也支持属性和文本转换(国际化internationalization),是QtScript模块的基础。从Qt 4.2开始,属性可以被动态添加,我们会在19和22章中详细了解这个特性。
标准C++不提供支持动态元信号。Qt中的moc工具解决了这个问题,它用来解析Q_OBJECT类定义。Moc工具完全用纯C++编写,是得Qt的元-对象系统可以在任何C++编译器中工作。
整个机制工作如下:
a) Q_OBJECT宏定义一些内省函数,这些内省函数必须在每个QObject的子类中实现: metaObject(), tr(), qt_metacall()等等。。。
b) Qt的moc工具产生由宏Q_OBJECT申明函数的实现,以及信号的实现。
c) QObject的成员函数如connect()和disconnect()使用内省函数来正常工作。
所有这些都是由qmake,moc和QObject自动处理,所以编程一般无需考虑这些。如果你很好奇,你可以读QMetaObject类文档,也可以看看moc生成的C++源代码进而了解它们是怎么工作的。
用Qt Designer来快速创建对话框
不管是纯手工编码还是用Qt Designer,创建对话框都涉及以下相同步骤:
1. 创建并初始化子widgets。
2. 把子widgets放在布局管理器中。
3. 设置tab顺序。
4. 建立信号-槽连接。
5. 实现对话框自己的槽函数。
接下来我们用Qt Design来实现一个go to cell对话框。
从Widgets箱子中拖放一个Label, 一个line editor,一个水平的spacer,和两个按钮。Spacer在最终编译后是不可见的。
当前不需要花太多时间去调整对话框的布局,过会我们会用Qt中的布局管理器帮我们完成的。
1. 点击Label,设置objectName属性为label,设置text属性为&Cell Location.
2. 点击line editor,设置objectName属性为lineEditor。
3. 点击左边一个按钮,设置objectName属性为okButton, 设置enabled属性为false,text属性为OK, default属性为true.
4. 点击右边一个按钮,设置objectName属性为cancelButton,设置text属性为Cancel.
5. 点击整个form的背景选中form,设置objectName属性为GoToCellDialog,设置windowTitle苏醒为Go to cell.
所有的widgets现在看起来基本已经okay了,除了text label还显示了&Cell Location.点击Edit|Edit Buddies 进入设置Buddies模式。然后点击label,拖动红色箭头指向line editor,释放鼠标。现在,label应该显示Cell Location,line editor即是这个label的buddy(密友)。点击Edit|Edit Widgets离开buddy模式。
接下来的任务是布局form上的widgets.
1. 点击Cell Location label,按住Ctr键,点击line editor,这样同时选中这两个widgets。右键选择Form|Lay out Horizontally.
2. 同样,同时选中spacer, 和两个按钮。右键选择Form|Lay Out Horizontally.
3. 点击form背景,右键选择Form|Lay Out Vertically.
4. 点击Form|Ajust Size,这样得到一个理想的form大小。
点击Edit|Edit Tab Order,进入编辑tab顺序模式。
点击Form|Preview,可以预览整个对话框。
保存对话框,得到一个.ui文件gotocelldialog.ui。
uic工具会把.ui文件转化为标准C++头文件ui_gotocelldialog.h,里面包含了类Ui::GoToCellDialog的定义。
Uic产生的类没有任何基类。要使用这个类,我们还需要写一些代码,创建一个新类,从QDialog和Ui::GoToCellDialog继承,实现一些必要的功能,可以取名为GoToCellDialog,注意不带前缀Ui::.
创建一个文件gotocelldialog.h,包含一列代码:
#ifndef GOTOCELLDIALOG_H
#define GOTOCELLDIALOG_H
#include <QDialog>
#include "ui_gotocelldialog.h"
class GoToCellDialog : public QDialog, public Ui::GoToCellDialog
{
Q_OBJECT
public:
GoToCellDialog(QWidget *parent = 0);
private slots:
void on_lineEdit_textChanged();
};
#endif
创建实现文件gotocelldialog.cpp
#include <QtGui>
#include "gotocelldialog.h"
GoToCellDialog::GoToCellDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
QRegExp regExp("[A-Za-z][1-9][0-9]{0,2}");
lineEdit->setValidator(new QRegExpValidator(regExp, this));
connect(okButton, SIGNAL(clicked()), this, SLOT(accept()));
connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
}
void GoToCellDialog::on_lineEdit_textChanged()
{
okButton->setEnabled(lineEdit->hasAcceptableInput());
}
在构造函数中调用setupUi()来初始化整个form,由于我们用来多继承,我们可以直接调用Ui::GoToCellDialog的成员函数setupUi()。建立UI以后,setupUi()还会在遵循名称约定的条件下自动建立一些连接。这些slot的名字必须遵循on_objectName_signalName(),意味着这个slot被连接到objectName的signalName() 信号上。在我们的例子中,就是说setupUi()会建立下类信号-槽连接。
connect(lineEdit, SIGNAL(textChanged(const QString &)),
this, SLOT(on_lineEdit_textChanged()));
在构造函数里面,我们会检查输入的有效性。Qt提供了3个类来验证有效性: QIntValidator, QDoubleValidator和QRegExpValidator. 这里我们用了QRegExpValidator,用正则表达式[A-Za-z][1-9][0-9]{0,2}来检验输入的有效性,这个正则表达式的含义是:允许一个大写或小写的字母,后跟一个1到9范围内的数字,后跟0,1或者两个0到9范围内的数字。
把指针this传递给QRegExpValidator构造函数,指明这个类为GoToCellDialog对象一个子对象。这样做的好处是,我们以后不需要考虑删不删除这个对象,当这个对话框对象删除时,这个校验类会自动被删除。
Qt的父-子机制在QObject类中实现。当我们创建一个对象时(widget, validator,或者其他类型),把父类的指针传递给这个构造函数,父类就会把这个子对象添加到它的的子类列表中。那些子类也会删除它们自动的类子,这样递归下午知道所有相关的对象都删除。这个父-子机制让编程人员简化了内存管理方面的代码,减少了内存泄漏的风险。编程人员唯一要用delete显式删除的是那些用new分配的并且没有父类的对象。如果我们在父对象被删除之前删除了子对象,这也没关系,Qt会自动把这个子对象从它的父对象的子对象列表中删除。
对于widgets对象,父对象还有另外一层意思:子widgets显示在父widgets的区域里面。当我们删除父widget时,子widget不但从内存中删除,也从屏幕上消失了。在构造函数的最后部分,我们建立了ok按钮和对话框的accept()槽函数的连接,cancel按钮和对话框的reject()槽函数连接。这两个槽函数都会关闭对话框,但是accept()设置对话框的返回值为QDialog::Accepted(等于1),而reject()使得返回值为QDialog::Rejected(等于0).当我们使用这个对话框是,我们可以用这个返回值来判断用户按了ok还是cancel按钮。
On_lineEdit_textChanged()槽函数根据line editor内是否含有有效cell lodation来控制ok按钮(enable或diable)。QLineEdit::hasAcceptableInput()用我们在构造函数时传递给它的校验方法。
代码到此结束,我们现在可以写一个main.cpp来测试这个对话框。
Qt Designer极大的方便了编程人员设计GUI form,编程人员可以自由的修改form样式而不需要修改任何代码。当你纯粹用C++代码来设计form的话,修改form的显示将会很费时。使用Qt Design,这些工作uic都会帮我们做好。对话框的UI被保存为.ui文件(一个基于XML格式的文件)。
Shape-Changing Dialogs(可变形对话框)
我们已经知道了怎么创建对话框。有些情况下,我们需要能够改变形状的对话框。常见的两种可变形对话框是扩展对话框(extension dialogs)和多页对话框(multi-page dialogs).这两种对话框都可以在Qt中实现,可以用纯代码实现也可以用Qt Designer实现。
扩展对话框通常有一个很简洁的界面,但是有一个触发的按钮允许用户来回切换到扩展后的对话框。扩展对话框通常用于隐藏高级选项的应用程序中。在这一部分,我们用Qt Designer创建一个扩展对话框。如下图:
这是一个在spreadsheet应用程序中的排序对话框,用户可以在spreadsheet上选中一列或多列来进行排序。简洁界面允许用户输入单个排序值,而它的扩展界面提供两个额外的排序键值。More按钮让用户可以在这两个界面之间自由切换。
我们在Qt Designer中创建一个扩展界面,一开始隐藏第二和第三个键值。这个对话框看起来很复杂,但是在Qt Designer中实现起来非常容易。只要先把主键值部分完成,然后拷贝两次,就可以得到第二第三键值部分了。
1. 点击File|New Form,然后选“Dialog without Buttons”模板。
2. 拖放一个OK按钮到FORM的右上角。改变objectName属性为okButton,设置default属性为true.
3. 拖放一个cancel按钮到OK按钮下面,设置objectName属性为cancelButton.
4. 拖放一个垂直spacer到cancel按钮下面,然后拖放一个More按钮到垂直spacer下面,设置objectName属性为moreButton,设置text属性为&More,设置checkable属性为true.注意:这里必须把checkable属性设置为true,不然这个按钮不会发射toggled()信号,这样扩展部分就不会展开了。
5. 选中OK按钮,cancel按钮,spacer和More按钮,点击Form|Lay Out Vertically,进行垂直排序。
6. 拖放一个group box,两个labels,两个comb boxes和一个水平spacer。
7. 把其它的widgets都放到group box里面,按适当的位置排号。
8. 拖动第二个comb box右下角,使得它大概是第一个两倍宽度。
9. 设置group box的title属性为&Primary Key,第一个label的text属性为Column,设置第二个label的text属性为Order。
10.右击第一个comb box,选择Edit Items,弹出comb box编辑对话框,创建一项内容为None。
11.右击第二个comb box,同样创建一项Ascending和一项Descending。
12.选中group box,然后点击Form|Lay out in a Grid。再次选中group box,选中Form|Adjust Size,这样就会得到理想的布局了。
如果布局不是很合理,有总是可以点击Edit|Undo或Form|Break Layout,然后重新排布这些widgets再试。注意:把两个垂直spacer的高度设置为0,以达到理想的效果。
现在我们增加Secondary Key和Tertiary Key group box.
1. 使得对话框足够高来容纳其他两部分。
2. 按住Ctrl键,点击拖动Primary Key group box来创建一份拷贝,拖动到Primary Key group box的下面,同样的方法再创建一份group box,放到下面。
3. 改变它们的title属性为&Secondary Key和&Tertiary Key.
4. 拖放一个垂直spacer到第一个和第二个group box之间。
5. 选中form,点击Form|Lay Out in a Grid.
6. 设置两个垂直spacer的sizeHint属性为[20, 0]。
网格布局的结果有两列和四行,总共8个格子。Primary Key group box,左边的垂直spacer,Seconday Key group box和Tertiary Key group box各占一个格子。垂直布局包含OK,cancel和more 按钮,占两个格子。
右下角留下两个空格子。如果这不是你想要的布局,取消布局,重新放置widget,再试。
重命名form为SortDialog,并且把窗口标题设置为Sort。
点击Edit|Edit Tab Order,设置tab次序。
既然form已经设计好了,现在我们要设置一些信号-槽使它可以工作起来。Qt Designer允许我对同一个form里面的widget之间建立连接。我们需要建立两个连接。
点击Edit|Edit Signals/Slots进入Qt Designer连接模式。蓝色箭头来表示widgets之间的连接,同时它们也在Qt Designer的signal/slot编辑窗口中列出。要在两个widgets之间建立一个连接,点击发送信号的widget,拖动红色箭头到接收信号的widget,释放鼠标。期间会弹出一个对话框让你选择要连接的信号和槽函数。
第一个要连接的是ok按钮和form的accept()槽函数。从ok按钮拖动红色箭头到form的空白部分,释放鼠标弹出配置连接对话框,选中clicked()作为信号函数,accept()作为槽函数,点击确认。
第二个连接是cancel按钮和form的reject()槽函数。同样从cancel按钮拖动红色箭头到form的空白部分,释放鼠标弹出配置连接对话框,选中clicked()作为信号函数,reject()作为槽函数。
第三个连接是在more按钮和secondaryGroupBox建立连接。拖动红色箭头线到第二个group box,释放鼠标弹出对话框,选中toggled(bool)作为信号函数,在group box一端选中setVisible(bool)作为槽函数。默认情况下,Qt Designer的槽函数列表中不会列出setVisible(bool)槽函数,当你选中Show all signals and slots选项时,这个槽函数就会出现了。
第四个也是最后一个连接是建立在more按钮和terriaryGroupBox之间。跟上面一个一样,选中more按钮的toggled(bool)信号函数和group box的setVisible(bool)槽函数。所有连接建立好以后,点击Edit|Edit Widgets以离开连接编辑模式。
保存对话框文件为sortdialog.ui。我们用多继承的方法添加一些代码来使用这个form。
首先创建一个sortdialog.h的头文件。
#ifndef SORTDIALOG_H
#define SORTDIALOG_H
#include <QDialog>
#include "ui_sortdialog.h"
class SortDialog : public QDialog, public Ui::SortDialog
{
Q_OBJECT
public:
SortDialog(QWidget *parent = 0);
void setColumnRange(QChar first, QChar last);
};
#endif
现在创建sortdialog.cpp
1 #include <QtGui>
2 #include "sortdialog.h"
3 SortDialog::SortDialog(QWidget *parent)
4 : QDialog(parent)
5 {
6 setupUi(this);
7 secondaryGroupBox->hide();
8 tertiaryGroupBox->hide();
9 layout()->setSizeConstraint(QLayout::SetFixedSize);
10 setColumnRange('A', 'Z');
11 }
12 void SortDialog::setColumnRange(QChar first, QChar last)
13 {
14 primaryColumnCombo->clear();
15 secondaryColumnCombo->clear();
16 tertiaryColumnCombo->clear();
17 secondaryColumnCombo->addItem(tr("None"));
18 tertiaryColumnCombo->addItem(tr("None"));
19 primaryColumnCombo->setMinimumSize(
20 secondaryColumnCombo->sizeHint());
21 QChar ch = first;
22 while (ch <= last) {
23 primaryColumnCombo->addItem(QString(ch));
24 secondaryColumnCombo->addItem(QString(ch));
25 tertiaryColumnCombo->addItem(QString(ch));
26 ch = ch.unicode() + 1;
27 }
28 }
构造函数隐藏了对话框中第二个和第三个group box。同时又设置了对话框布局的sizeContraint属性为QLayout::SetFixedSize,这样用户就不可以改变对话框的大小来。当第二个和第三个group box显示或隐藏时,Qt的布局管理器会自动改变对话框的大小,确保对话框的大小总是合理的。
setColumnRange()槽函数根据spreadsheet中选中的列来初始化combbox。我们插入None项到可选的第二和第三个键值。
第19,20行布局常用的方法。QWidget::sizeHint()函数返回widget理想的大小。这可以解释为什么不同种类的widget或者相似的widgets包含不同的内容,布局管理器会给它们设置不同的大小。就combbox来说,这意味着包含None项的第二个和第三个combboxes会比第一个combbox要打,因为第一个combbox只包含单个字母。为了避免这种问题,我们设置了第一个commbox的最小大小为第二个combbox的理想大小。
接下来写一个main()测试程序,设置column range ‘C’~‘F’,然后show对话框。
#include <QApplication>
#include "sortdialog.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
SortDialog *dialog = new SortDialog;
dialog->setColumnRange('C', 'F');
dialog->show();
return app.exec();
}
这样我们就完成了可扩展对话框的设计。正如列子所示,可扩展对话框的设计相对于一般的对话框而言也不是很难,我们需要做的是一个触发(toggle)按钮,一些额外的信号-槽连接,一个不可改变大小的布局。一般应用中,当扩展部分不显示时,普遍把控制扩展部分的按钮显示为Advanced>>>,而扩展部分显示时按钮显示为Advanced<<<。这个在Qt中很容易就搬到了,只要在按钮被点击时调用setText()函数。
其它的可变形对话框如多也对话框,在Qt中创建甚至跟容易。有很多中方法来创建这种对话框:
一个QTabWidget可以直接用来创建多页对话框。它提供一个tab栏来控制一个内置的QStackWidget.
一个QListWidget和一个QStackedWidget可以一起使用。QListWidget的当前项来决定QStackedWidget中哪一页来显示。这个功能可以通过绑定QlistWidget的currentRowChanged()信号和QStackedWidget的setCurrentIndex槽来实现。
一个QTreeWidget也可以和QStackedWidget一起使用,实现同上的功能。
我们将在第6章详细讲述QStackedWidget类。
动态对话框(Dynamic Dialogs)
以前的章节中,我们都是在预编译阶段通过uic工具把.ui文件转换为C++代码,这类对话框我们暂且叫作静态对话框,因为这类对话框编译完就存在可执行程序中,而动态对话框是在在运行时用QUiLoader类来加载.ui这个文件,在运行时这个对话框才被创建,所以我们叫做动态对话框。
QUiLoader uiLoader;
QFile file("sortdialog.ui");
QWidget *sortDialog = uiLoader.load(&file);
if (sortDialog) {
...
}
我们可以用QObject::findChild<T>()函数来得到form中的子widget.
QComboBox *primaryColumnCombo =
sortDialog->findChild<QComboBox *>("primaryColumnCombo");
if (primaryColumnCombo) {
...
}
findChild<T>()是一个模板成员函数,根据指定的widget类型和对象名可以得到子对象。由于编译器的限制,在MSVC 6上这个函数不可用。如果你必须用MSVC6编译器,可以调用全局函数qFindChild<T>()来代替。
QUiLoader类位于一个独立库中,所以要使用这个类的话,就要在项目文件中加入下面一行:
CONFIG += uitools
动态对话框使得我们可以在不需要重新编译的情况下改变form的布局。这个技术也可以被用来创建瘦小的(thin-client,指界面比较少,代码量比较少)程序,这类程序只有一个简单的前端form,所有其他的form只有在需要用到时才被创建。
内置widget和对话框类(Built-in Widget and Dialog Classes)
Qt提供了一整套内置的widgets和常用的对话框,基本可以满足于大多数应用界面的开发。在这一部分,我们来看看它们的截屏。一些比较特殊的widgets我们等会来看。我们会在第3章已经讲解一些主要的窗口widgets,例如QMenuBar, QToolBar和QStatusBar,在第6章我们会讲解一些布局相关的widgets,例如QSplitter和QScrollArea。大多数内置的widgets和对话框都会在本书的例子中复盖到。在下面的截屏中显示的所有widgets都是用来Plastique风格。
按钮widgets
单页容器widgets(single-page container widgets)
多页容器widgets
项目浏览widgets(item view)
显示widgets
输入widgets
反馈对话框
颜色和字体对话框
文件和打印对话框
向导对话框
Qt提供4中类型的按钮:QPushButton, QToolButton, QCheckBox和QRadioButton; QPushButton和QToolButton通常被用来触发一个点击操作,但它们也可以被用来当作一个开关按钮(点击按下,再次点击恢复)。QCheckBox用来作独立的开/关选项;QRadioButton通常用来实现互斥。
Qt容器widgets可以包含其他widgets。 QFrame也可以自己单独画线,也可以作为其他widget的基类,包括QToolBox和QLable.
QTabWidget和QToolBox属于多页widgets。每一页是一个子widget,所有的页面从0开始按数字编号。另外QTabWidgets,我们既可以设置tab的形状也可以设置tab的位置。
列表视图(item view)用来处理大量数据,通常还需使用滚动条。滚动条机制在QAbstractScrollArea类中实现,这个类是列表视图还有其他可以滚动类widget的基类。
Qt库包含一个丰富的文本引擎,可用用来实现和编辑格式化的文本。这个引擎支持字体定制,文本对齐,列表,图片显示还有超级链接。多格式文本文档可以由编程逐步创建,也可以直接提供HTML格式的文本进行创建。详细的HTML标签及CSS属性的说明,参见http://doc.trolltech.com/4.3/richtext-html-subset.html.
Qt提供一些widgets纯粹用来显示一些信息。比如QLabel,它可以用来显示纯文本HTML和图片。
QTextBrowser类是QTextEdit的子类,只读属性,它可以显示一些格式化的文本。这个类可以用来显示大量的需要格式化的文本,不想QLabel,在需要时它自动提供滚动条,也额外的提供键盘和鼠标的导航。Qt Assistant 4.3就是用QTextBrowser来向用户展示文档的。
Qt还提供一些widgets来支持数据输入。QLineEdit可以用输入屏蔽,有效性检验来限制性的提供输入内容。QTextEdit是QAbstractScrollArea的子类,可以输入大量的文本。QTextEdit可以被设置为可编辑纯文本或格式化文本, 后一种情况,它能够显示所有Qt多格式文本引擎支持的格式。QLineEdit和QTextEdit都是跟剪切板集成的。
Qt提供多种消息框和一个错误对话框。QProgressDialog和QProgressBar可以用来指示一个耗时的操作。当需要用户输入单行文本或单个数字时,我们用QInputDialog类可以很方便的实现。
Qt向用户提供了一整套常用的对话框,可以让用户选择颜色,字体,或文件,或一个打印文档。
在Windows和Mac OS X中,Qt会尽可能的使用当前样式的对话框来取代通用的样式。颜色也可以用Qt提供的一个颜色选择widget来设置,字体也可以用内置的QFontCombBox类来设置。
最后,QWizard类提供一个创建向导的架构。向导对那些用户觉得复杂或很难以学习的任务很有用。
内置的widgets和常用的对话框已经提供了许多可直接使用的功能。那些更特殊的功能需要通常可以通过设置widget的属性来满足要求,或者通过建立信号-槽,在槽函数中实现用户想要的功能。
如果Qt提供的widgets和常用的对话框没有一个能直接满足需要的,也许我们可以在Qt Solutions中找到,或从第三方的商业或非商业的厂商得到。Qt Solutions提供了一些额外的widgets,包括颜色选择器,指轮控制,扇形菜单,浏览器,以及一个拷贝对话框。
有些情况下,用户可能需要创建定制的widget。在Qt中这个很容易实现,定制的widget可以直接使用所有的不依赖平台的绘画功能,就像Qt内置的widget一样。定制的widget还可能集成到Qt Designer当中,这样这些widget就可以像内置的widget一样在Qt Designer当中使用了。第5章我们会解释怎么创建定制的widget.