对话框为用户提供了许多选项和多种选择,允许用户把选项设置为他们喜欢的变量值并从中做出选择,之所以把它们称为对话框,或者简称为“对话框”,是因为它们为用户和应用程序之间提供了一种可以相互“交谈”的交互方式。
前置声明(forward declaration)会告诉C++编译程序类的存在,而不用提供类定义中的所有细节(通常放在它自己的头文件中)。
1.子类化对话框类
class FindDialog:public QDialog
{
Q_OBJECT //对于所有定义了信号和槽的类,在类定义开始处的Q_OBJECT宏都是必须的。
public:
FindDialog(QWidget *parent = 0);
signals: //signals关键字实际上是一个宏,C++预处理器会在编译程序找到它之前把它转换成标准C++代码。
void findNext(const QString &str, Qt::CaseSensitivity cs); //Qt::CaseSensitivity是一个枚举类型,它有Qt::CaseSensitive和Qt::CaseInsensitive两个取值。
void findPrevious(const QString &str, Qt::CaseSensitivity cs);
private slots: //关键字slots就像signals一样也是一个宏,也可以扩展成C++编译程序可以处理的一种结构形式。
void findClicked();
void enableFindButton(const QString &text);
}
finddialog.cpp的关键处理:
#include <QtGui> //该文件包含了Qt GUI类的定义。
//Qt有数个模块构成,每个模块都有自己的类库,最为重要的模块有QtCore,QtGui,QtNetwork,QtOpenGL,QtScript,QtSql,QtSvg和QtXml。
//其中在<QtGui>头文件中为构成QtCore和QtGui组成部分的所有类进行了定义。
FindDialog::FindDialog(QWidget* parent)
{
//tr()函数调用是把它们翻译成其他语言的标记。在每个QObject对象以及包含有Q_OBJECT宏的子类中都有这个函数的声明,尽管现在并没有将你的应用程序立刻翻译成其他语言的打算,但是在每一个用户可见的字符串周围使用tr()函数还是一个很不错的习惯。在第18章中将对翻译Qt应用程序进行详细的讲述。
//符号"&"可以用来控制焦点,通过支持快捷键的平台下通过按下Alt+F快捷键来激活它。
label = new QLabel(tr("Find &what:"));
lineEdit = new QLineEdit;
//所谓"伙伴"(buddy),就是一个窗口部件,它可以在按下标签的快捷键时接收焦点(focus)。
label->setBuddy(lineEdit);
//在字符串中,使用符号"&"来表示快捷键。
findButton = new QPushButton(tr"&Find“);
//通过调用setDefault(true),让Find按钮成为对话框的默认按钮。默认按钮(default button)就是当用户按下Enter键时能够按下对应的按钮。
findButton->setDefault(true);
//禁用了Find按钮,当禁用一个窗口部件时,它通常会显示为灰色,并且不能和用户发生交互操作。
findButton->setEnable(false);
//关联信号与槽 由于QObject是FindDialog的父对象之一,所以可以省略connect()函数前面的QObject::前缀。
connect(lineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(enableFindButton(const QString&)));
//布局管理器类的一个精妙之处在于它们不是一个窗口部件,相反,它们派生自QLayout,因而也就是进一步派生自QObject
//布局管理器的使用方法 布局管理器自动设置父窗口
QHBoxLayout *topLeftLayout = new QHBoxLayout;
topLeftLayout->addWidget(label);
QVBoxLayout *leftLayout = new QVBoxLayout;
leftLayout->addLayout(topLeftLayout);
leftLayout->addWidget(caseCheckBox);
//添加一个小"弹簧"是一个分隔符(或者称为"伸展器")
leftLayout->addStretch();
setLayout(leftLayout);
//设置窗口的标题
setWindowTitle(tr("Find"));
//设置窗口具有一个固定的高度,这是因为在对话框的垂直方向上再没有其他窗口部件可以去占用多出的空间了
setFixedHeight(sizeHint.height());
}
void FindDialog::findClicked()
{
QString text = lineEdit->text();
Qt::CaseSensitivity cs = caseCheckBox->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitice;
if (backwardCheckBox->isChecked())
{
emit findPrevious(text, cs);
}
else
{
emit findNext(text, cs); //emit是Qt中的关键字,像其他Qt扩展一样,它也会被C++预处理转换成标准的C++代码。
}
}
2.深入介绍信号和槽
信号和槽机制是Qt编程的基础,它可以让应用程序编程人员把这些互不了解的对象绑定在一起。槽和普通的C++成员函数几乎是一样的—可以是虚函数,可以被重载,可以是公有的,保护的或者使用的,并且也可以被其它C++成员函数直接调用,还有它们的参数可以是任意类型。唯一不同是,槽还可以可信号连接在一起,在这种情况下,每当发射这个信号的时候,就会自动调用这个槽。
connect()语句看起来会是如下的样子:
connect(sender, SIGNAL(signal), receiver,SLOT(slot));
这里的sender和receiver是指向QObject的指针,signal和slot是不带参数的函数名,实际上,SIGNAL()宏和SLOT()宏会把它们的参数转换成相应的字符串。
(1)一个信号可以连接多个槽,在发射这个信号的时候,会以不确定的顺序一个接一个地调用这些槽。
(2)多个信号可以连接同一个槽,无论发射那个信号,都会调用这个槽。
(3)一个信号可以与另外一个信号相连接,当发射第一个信号时,也会发射第二个信号,除此之外,信号与信号之间的连接和信号与槽之间的连接是难以区分的。
(4)连接可以被移除,这种情况较少用到,因为当删除对象时,Qt会自动移除和这个对象相关的所有连接。
要把信号成功连接到槽(或者连接到另外一个信号),它们的参数必须具有相同的顺序和相同的类型。如果信号的参数比它所连接的槽的参数多,那么多于的参数将会被简单地忽略掉。如果参数类型不匹配,或者如果信号或槽不存在,则当应用程序使用调试模式构建后,Qt会在运行时发出警告。与之相类似的是,如果在信号和槽的名字中包含了参数名,Qt也会发出警告。
3.Qt的元对象系统
Qt的主要成就之一就是使用了一种机制对C++进行了扩展,并且使用这种机制创建了独立的软件组件。这些组件可以绑定在一起,但任何一个组件对于它所要连接的组件的情况事先都一无所知。
这中机制称为元对象系统(meta-object system),它提供了关键的两项技术:信号-槽以及内省(introspection)。内省功能对于实现信号和槽是必需的,并且允许应用程序的开发人员在运行时获得有关QObject子类的“元信息”(meta-infomation),包括一个含有对象的类名以及它所支持的它也为QtScript模块奠定了基础。从Qt4.2开始,可以动态添加属性,这一特性将会在第19章和第22章中付诸实施。
标准C++没有对Qt的元对象系统所需要的动态元信息提供支持。Qt通过提供一个独立的moc工具解决了这个问题,moc解析Q_OBJECT类的定义并且通过C++函数来提供可供使用的信息。由于moc使用纯C++来实现它的所有功能,所以Qt的元对象系统可以在任意C++编译器上工作。
这一机制是这样工作的:
Q_OBJECt宏声明了在没一个QObject子类中必须实现的一些内省函数:metaObject(),tr(),qt_metacall(),以及其他一些函数。
Qt的moc工具生成了用于有Q_OBJECT声明的所有函数和所有信号的实现。
像connect()和diconnect()这样的QObject的成员函数使用这些内省函数来完成它们的工作。
由于所有这些工作都是有qmake,moc和QObject自动处理的,所以很少需要再考虑这些事情。但是如果你对此充满好奇心,那么也可以阅读一下有关QMetaObject类的文档和有moc生成的C++源代码文件,可以从中看出这些实现工作是如何进行的。
4.内置检验器类
Qt提供了三个内置检验器类:QIntValidator,QDoubleValidator和QRegExpValidator。
5.Qt的父-子对象机制
Qt的父-子对象机制是在QObject中实现的。当利用一个父对象创建一个子对象(一个窗口部件,一个检验器,或是任意的其他类型)时,父对象会把这个字对象添加到自己的字对象列表中。但删除这个父对象时,它会遍历子对象列表并且删除每一个子对象。然后这些字对象再去删除它们自己所包含的每一个字对象。如此反复递归调用,直至清空所有字对象为止。这种父-子对象机制可在很大程度上简化内存管理工作,降低内存泄漏的风险。需要名且删除的对象是那些使用new创建的并且没有父对象的对象。并且,如果在删除一个父对象之前先删除它的字对象,Qt会自动地从它的父对象的字对象列表中将其移除。
对于窗口部件,父对象还有另外一层含义:字对象部件会显示在它的父对象所在的区域中。当删除这个父窗口部件时,不仅字对象会从内存中消失,而且它也会在屏幕上消失。
6.动态对话框
动态对话框(dynamic dialog)就是在程序运行时使用的从Qt设计师的.ui文件创建而来的那些对话框。动态对话框不需要通过uic把.ui文件转化成C++代码。相反。它是在程序运行的时候使用QUiLoader类载入该文件的,就像下面这种方式:
QUiLoader uiLoader;
QFile file ("sortdialog.ui");
QWidget *sortDialog = uiLoader.load(&file);
if (sortDialog)
{...}
可以使用QObject::findChild<T>()来访问这个窗体中的各个子窗口部件:
QCombox *primaryColumnCombo = sortDialog->findChild<QComboBox *>("primaryColumnCombo");
if (primaryColumnCombo)
{....}
这里的findChild<T>()函数是一个模板成员函数,它可以返回与给定的名字和类型相匹配的子对象。由于受编译器的制约,还不定在MSVC6中使用该函数。如果需要使用MSVC6编译器,那么可以通过调用全局函数qFindChild<T>()来替代这个函数,这个全局函数同样也可以完全相同的方式工作。
QUiloader类放在一个独立的库中。为了在Qt应用程序中使用QUiLoader,必须在这个应用程序的.pro文件中加入这一行内容:
CONFIG+=uitools
动态对话框是不重新编辑应用程序而可以改变窗体布局的做法称为可能。动态对话框也同样用于创建小型终端应用程序,这些程序只有一个内置的前端窗体,并且只是在需要的时候才会去创建所有的其他窗体。