【C2】对话框【S1】Subclassing QDialog

    我们的第一个例子是用纯C++写一个查找对话框。我们将实现一个对话框类。为了能完成它,我们把它做成一个独立的组件,有自己的信号和槽。最终效果如下图:

    源代码分布在这两个文件中:finddialog.h和finddialog.cpp。我们从finddialog.h开始。

Code:
  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行(后面的#endif)是保护头文件不被重复包含

    第3行是包含了QDialog类的定义,在Qt中QDialog类继承了QWidget。

    第5行到第7行为Qt类的前向声明(forward declarations),在实现代码中才真正使用这些类。一个前向声明告诉C++编译器某个类是存在的,但是没有给出这个类定义所有的细节,这些细节通常在类本身的头文件提供。我们稍后将对此讨论更多。

    下一步,我们定义FindDialog类为QDialog类的子类:

Code:
  1. class FindDialog : public QDialog   
  2. {   
  3.      Q_OBJECT   
  4.  public:   
  5.      FindDialog(QWidget *parent = 0);   

    在类定义的最开始的Q_OBJECT宏对于所有定义了信号或槽的类来说是必不可少的。

    FindDialog类的构造函数是典型的Qt的Widget类的构造函数。parent形参描述了parent的widget。默认形参是个空指针,表示这个dialog没有parent。

Code:
  1. signals:   
  2.     void findNext(const QString &str, Qt::CaseSensitivity cs);   
  3.     void findPrevious(const QString &str, Qt::CaseSensitivity cs);   

    signals部分声明了两个信号,当用户点击"查找"按钮时就发送信号。如果"反方向查找选项"被选上,则dialog发送findPrevious()信号;否则发送findNext()信号。

    signals关键字实际上是个宏。C++与处理器在编译器看到它前把它转换为标准的C++代码。Qt::CaseSensitivity是一个枚举类型,包含了Qt::CaseSensitive和Qt::CaseInsensitive。

Code:
  1. private slots:   
  2.     void findClicked();   
  3.     void enableFindButton(const QString &text);   
  4. private:   
  5.     QLabel *label;   
  6.     QLineEdit *lineEdit;   
  7.     QCheckBox *caseCheckBox;   
  8.     QCheckBox *backwardCheckBox;   
  9.     QPushButton *findButton;   
  10.     QPushButton *closeButton;   
  11. };   
  12. #endif   

    在类的私有部分,我们声明了两个槽。为了实现这两个槽,我们需要访问这个dialog的大部分的child widgets,所以我们也声明了指向它们的指针。slots关键字像signals关键字一样也是一个宏,会被扩展C++编译器就可识别之。

    为了这些私有变量,我们使用这些变量所属的类的前向声明。这样是可行的,因为它们都是指针,我们不需要在头文件中访问它们,所以编译器不需要完整的类定义。我们本可以包含对应的头文件,诸如<QCheck>,<QLabel>,等等。但是使用前向声明时在编译时可以稍微快些。

    我们现在来看看finddialog.cpp,此文件包含了FindDialog类的实现。

Code:
  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>头文件和使用QCheck,QLabel,QLineEdit和QPushButton类的前向声明。然后,包含一个头文件中包含一个很大的头文件是一种坏习惯,尤其是在更大的应用程序。

Code:
  1. FindDialog::FindDialog(QWidget *parent)   
  2.      : QDialog(parent)   
  3. {   
  4.      label = new QLabel(tr("Find &what:"));   
  5.      lineEdit = new QLineEdit;   
  6.      label->setBuddy(lineEdit);   
  7.      caseCheckBox = new QCheckBox(tr("Match &case"));   
  8.      backwardCheckBox = new QCheckBox(tr("Search &backward"));   
  9.      findButton = new QPushButton(tr("&Find"));   
  10.      findButton->setDefault(true);   
  11.      findButton->setEnabled(false);   
  12.      closeButton = new QPushButton(tr("Close"));   

    第4行,我们传了parent形参到基本的构造函数中。然后我们创建这个子类widget。tr()函数调用标记它的形参,就是字符串字面值,要被翻译成其它的语言。这个函数在QObject类和所有包含有Q_OBJECT宏的子类声明。把用户可见的串都包在tr()函数中是很好的习惯,虽然你并不想马上把你的应用程序翻译成其它语言。翻译Qt应用程序在第17章中涉及。

    在字符串字面值中,我们使用&来标识快捷键。例如,第9行创建了一个查找按钮,用于可以在支持快捷键的平台下按下ALT+F来激活这个按钮。&号也能用来控制焦点:第4行我们创建了一个带有快捷键的标签(ALT+W),然后在第6行我们设置这个标签的搭档(buddy)为文本框。一个widget的搭档就是当标签的快捷键被按下时接收焦点。所以当用户按下ALT+W时(标签的快捷键),焦点就会来到文本框(标签的搭档)。

    在第10行,我们调用setDefault(true)函数来使dialog的默认按钮为找找按钮。默认按钮就是当用户按下回车键时被按下的按钮。在第11行我们禁用查找按钮。当一个widget被禁用,通常会变成灰色且不会响应用户的交互。

Code:
  1. connect(lineEdit, SIGNAL(textChanged(const QString &)),   
  2.         this, SLOT(enableFindButton(const QString &)));   
  3. connect(findButton, SIGNAL(clicked()),   
  4.         this, SLOT(findClicked()));   
  5. connect(closeButton, SIGNAL(clicked()),   
  6.         this, SLOT(close()));   

    只要文本框的文本变动时私有槽enableFindButton(const QString &)就会被调用。当用户点击查找按钮时私有槽findClicked()就会被调用。当用户点击Close按钮dialog就会关闭。close()槽继承自QWidget,默认的行为是从视觉上隐藏widget(并没有delete掉)。我们等会会看到enableFindButton()和findClicked()槽的代码。

    因为QObject类是FindDialog类的其中一个祖先类,所以我们可以省略connect函数前的QObject前缀。

Code:
  1. QHBoxLayout *topLeftLayout = new QHBoxLayout;   
  2. topLeftLayout->addWidget(label);   
  3. topLeftLayout->addWidget(lineEdit);   
  4. QVBoxLayout *leftLayout = new QVBoxLayout;   
  5. leftLayout->addLayout(topLeftLayout);   
  6. leftLayout->addWidget(caseCheckBox);   
  7. leftLayout->addWidget(backwardCheckBox);   
  8. QVBoxLayout *rightLayout = new QVBoxLayout;   
  9. rightLayout->addWidget(findButton);   
  10. rightLayout->addWidget(closeButton);   
  11. rightLayout->addStretch();   
  12. QHBoxLayout *mainLayout = new QHBoxLayout;   
  13. mainLayout->addLayout(leftLayout);   
  14. mainLayout->addLayout(rightLayout);   
  15. setLayout(mainLayout);   

    下一步,我们使用布局管理者来布局子widgets。布局可以包含widgets和其它的布局。可以使用不同的组合来嵌套QHBoxLayout,QVBoxLayout,QGridLayout,可以建造出非常精密的对话框。

    对于这个查找对话框,我们使用两个QHBoxLayout和两个QVBoxLayout,如下图所示。外层的布局为主布局;在第15行被安置到查找对话框中,主布局负责整个对话框的布局。其它3个布局为子布局。右下角的空格占用符。它用来在查找按钮和退出按钮的下面填充空白,来确保这些按钮占据它们的布局的顶端。

    一个微妙的方面是布局管理者并不是widgets,而是从QLayout继承而来,相应的也继承自QObject。在上图中,widgets表示为实线围起,而布局被虚线包围起来,以此来强调它们之间的不同。在运行中的应用程序,布局是不可见的。

    当子布局被加到父布局中时,子布局被自动重定父级。然后,当主布局安置到查找对话框中时,它就变成对话框的child,这个布局所有的widgets被重定父级变成对话框的child。这个父子继承层次描绘如下图:

Code:
  1.     setWindowTitle(tr("Find"));   
  2.     setFixedHeight(sizeHint().height());   
  3. }   

    最后,我们设置对话框的标题栏的文字,我们还设置窗口有一个固定的高度,因为窗口没有任何widgets来占据其它的垂直空间。QWidget::sizeHint()函数返回一个widget的理想的尺寸。

    OK,这里我们把FindDialog的构造函数就过了一遍。既然我们使用new关键字创建了对话框的widgets和布局,看起来我们需要写一个析构函数来使用delete关键字释放我们创建的每一个widget和布局。但这不是必须的,因为当parent被销毁时Qt会自动销毁所有的自级,所有的widgets和布局都是FindDialog的后裔。

    现在我们来看看对话框的槽:

Code:
  1. void FindDialog::findClicked()   
  2. {   
  3.     QString text = lineEdit->text();   
  4.     Qt::CaseSensitivity cs =   
  5.             caseCheckBox->isChecked() ? Qt::CaseSensitive   
  6.                                       : Qt::CaseInsensitive;   
  7.     if (backwardCheckBox->isChecked()) {   
  8.         emit findPrevious(text, cs);   
  9.     } else {   
  10.         emit findNext(text, cs);   
  11.     }   
  12. }   
  13. void FindDialog::enableFindButton(const QString &text)   
  14. {   
  15.     findButton->setEnabled(!text.isEmpty());   
  16. }   

    当用户点击查找按钮时findClicked槽被调用。这会发出findPrevious()信号或findNext()信号,这取决于向后搜索选选项。emit关键字是Qt特有的;像Qt其它的扩展,它被C++预处理器转换成标准C++。

    当用户何时该表文本框的文本时enableFindButton()槽会被调用。如果文本框中没有文字时查找按钮可用,反之禁用。

    OK,我们现在创建一个main.cpp文件来测试我们的FindDialog widget:

Code:
  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. }   

    现在运行程序,如果你的平台支持快捷键,核实快捷键ALT+W,ALT+C,ALT+B,和ALT+F能出发正确的行为。按下TAB来遍历所有的widgets。默认的TAB顺序是各个widgets创建的顺序。这可以通过调用QWidget::setTabOrder()来设置。

    提供合理的TAB顺序和快捷键确保不想使用鼠标的用户能完全使用这个程序。完全的键盘控制也被有的打字员的欣赏。

    在第3章我们在一个真实的程序中使用这个Find Dialog,到那时我们将findPrevious()和findNext()信号与一些槽连接起来。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值