C++ Qt4 编程学习笔记(三)——对话框

来源:互联网 发布:淘宝女士手提包 编辑:程序博客网 时间:2024/06/14 06:11

声明:在笔记(一)里面我已经说啦,这书是完全跟随C++ GUI Qt4 编程(第二版)一书的,而且我非常推荐喜欢Qt的人去买这本书,的确是学习Qt的好书,这些笔记也的确是个人的复习笔记,用于将来遗忘的时候快速补课。我可能会对书中讲述的内容做一些整合或者更改,但是一切都是未定义的,切记……


1.关于对话框

如图,这就是一个对话框:

在我没有保存WPS文字的情况下,点击了关闭按钮,系统就会弹出这样一个提示。对话框提供了一种用户与程序的交互方式,结构简单,目的明确,这一节,我们就要做类似的这样一个对话框。

不过呢,我还是得先说,这节的代码开始很长咯,如果是学习的话,还是推荐自己手打代码了。还有就是你一定得要坚持,坚持到我告诉你其实这些代码都可以不用写,嘿嘿,为什么?现在不告诉你(⊙o⊙)…


2.手工代码实现

首先来看看我们要做的对话框是什么样子的:

嘿嘿,就这样,其实就是我们的查找对话框啦,这个对话框有什么特别的呢?

首先,在Find what栏里面如果没有输入,那查找按钮就为不可用(灰色,不能点击),如果有输入,则Find可用(如图)。然后呢,对话框的竖直方向尺寸不可更改。

好的,大概就是这样子,这一次我们的源文件分为3部分,第一部分(main.cpp):

#include <QApplication>#include "finddialog.h" // 我们将要自己定义的类(就是对话框啦)的头文件int main(int argc, char *argv[]){   QApplication app(argc, argv);   FindDialog *dialog = new FindDialog;   dialog->show();   return app.exec();}

呵呵,还是那么简单,只不过这一次创建的对象是我们自己的类了,这没什么说的,接下来是第二部分(finddialog.h):

#ifndef FINDDIALOG_H // 防止二次包含#define FINDDIALOG_H#include <QDialog> // 对话框的头文件class QCheckBox; // 复选框class QLabel;class QLineEdit; // 行编辑框class QPushButton;/** 这里要说明一下,这样的前置声明可以让头文件不必包含相应的头文件,以提高编译速度* 通常这些都包含在实现文件中*/class FindDialog : public QDialog //继承自QDialog,因此,我们的类就拥有QDialog的默认属性了{   Q_OBJECT // 如果类中定义了信号和槽,那么这样的宏是必须的,以后会说明public:   FindDialog(QWidget *parent = 0); // 典型Qt窗口部件类的构造函数,参数指定了父窗口部件,0则是没有signals: // C++扩展,这其实是一个宏,表明了我们这个对话框可以发送的信号,下面即是信号的定义   void findNext(const QString &str, Qt::CaseSensitivity cs); // CaseSensistivity是个枚举类型                                                               //用来说明是否大小写敏感   void findPrevious(const QString &str, Qt::CaseSensitivity cs); // QString是Qt的字符串类型private slots: // 这就是我们的类支持的信号操作,即槽函数,槽函数必须定义   void findClicked(); // find按钮被按下   void enableFindButton(const QString &text); // 在text不为空的情况下让Find可用private: // 不应该被直接访问的成员,可以大概注意一下这些变量(对象)对应于窗口中的哪些部件   QLabel *label;   QLineEdit *lineEdit;   QCheckBox *caseCheckBox;   QCheckBox *backwardCheckBox;   QPushButton *findButton;   QPushButton *closeButton;};#endif

从这个头文件中,我们应该大致了解这个对话框类的构成,包括信号和槽,下面我们看看这样的类如何实现:

#include <QtGui> // 这就是Qt的GUI库,我们用到的Dialog,CheckBox等等都在里面啦               // 不过这个头文件因为太大了,所以一般是不会直接包含的,用什么包含什么了#include "finddialog.h"FindDialog::FindDialog(QWidget *parent) // 构造函数实现(头文件中已经制定默认值为0)   : QDialog(parent) // 对父类成员初始化{   label = new QLabel(tr("Find &what:")); // 初始化成员变量(就是把每个组件给定义出来),tr是翻译标记,以后会详细介绍   lineEdit = new QLineEdit;   label->setBuddy(lineEdit); // 绑定好基友,大家一起走(⊙o⊙)…这样当焦点被指向label时,lineEdit就会被选定   caseCheckBox = new QCheckBox(tr("Match &case")); // 字符前面的&让c成为这个选项的快捷键(ALT+C)   backwardCheckBox = new QCheckBox(tr("Search &backward"));   findButton = new QPushButton(tr("&Find"));   findButton->setDefault(true); // 这样让Find按钮成为对话框的默认按钮   findButton->setEnabled(false); // 初始化Find按钮不可用,也就是编程灰色了   closeButton = new QPushButton(tr("Close"));   //QObject是FindDialog的父类对象,因此可以不用写前缀   connect(lineEdit, SIGNAL(textChanged(const QString &)),           this, SLOT(enableFindButton(const QString &))); // 当lineEdit的值发生改变,调用我们自己的槽函数enableFindButton   connect(findButton, SIGNAL(clicked()),           this, SLOT(findClicked())); // (⊙o⊙)…   connect(closeButton, SIGNAL(clicked()),           this, SLOT(close())); // close()这个槽是本身就有的,可以F1一下   QHBoxLayout *topLeftLayout = new QHBoxLayout; // 好了,下面看看我们强力的布局是怎么完成的   topLeftLayout->addWidget(label); // 首先这是水平布局,把一堆好基友放平了   topLeftLayout->addWidget(lineEdit);   QVBoxLayout *leftLayout = new QVBoxLayout; // 然后要把下面两个复选框和上面的好基友竖直对齐   leftLayout->addLayout(topLeftLayout); // 这里注意是addLayout,就是这样布局才可以不断嵌套   leftLayout->addWidget(caseCheckBox); // 看这一部分的时候最好对着上面的完成图来看   leftLayout->addWidget(backwardCheckBox);   QVBoxLayout *rightLayout = new QVBoxLayout;   rightLayout->addWidget(findButton);   rightLayout->addWidget(closeButton);   rightLayout->addStretch(); // 这里注意啦,addStretch可以理解为一个伸缩占位空白,用来把它旁边的按钮给挤紧了,这里当然是竖直方向了   QHBoxLayout *mainLayout = new QHBoxLayout; // 好的,把左边的模块和右边的模块在做一次水平对齐   mainLayout->addLayout(leftLayout);   mainLayout->addLayout(rightLayout);   setLayout(mainLayout); // ...   setWindowTitle(tr("Find")); // 对话框的标题   setFixedHeight(sizeHint().height()); // 嘿嘿,这里就是让高度方向尺寸不可变,sizeHint()返回窗口合理的最小尺寸}void FindDialog::findClicked(){   QString text = lineEdit->text(); // 即是lineEdit里面的值   // 让cs得到是否大小写敏感的标识信息   Qt::CaseSensitivity cs =           caseCheckBox->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;   if (backwardCheckBox->isChecked()) {       emit findPrevious(text, cs); // 这是发送信号的句法       // 从这里我们可以看到,信号只起一个说明,会有这么一个信号,他本身不处理任何消息,因此并不需要实现       // 而槽则是专门用来处理信号的函数,因此需要自己实现,方式跟普通函数并无二致   } else {       emit findNext(text, cs);   }}void FindDialog::enableFindButton(const QString &text) // 这个就浅显易懂啦{   findButton->setEnabled(!text.isEmpty()); // 大牛都这么写(⊙o⊙)…}

小窍门:嘿嘿,这个小窍门出现在你头晕脑胀的时候呢,其实我们在读程序的时候不应该一行一行的读,这样的话,你对整个程序的框架可以说一无所知,所以我们在看一个东西是首先在东东的头文件(声明)里面就应该大概把这些东西要做什么事给明确了,怎么明确呢?其实就是函数名啦,比如enableFindButton从文字意思就可以看出来是让Find按钮可用的功能,这样你对整个程序的框架比较了解了,看到具体的实现也就不会觉得实现者的想法太奇特了,自己根本想不到(可以看到好的名字是多么的重要,Qt中的名字都是比较合理的,一般都能望文生义(⊙o⊙)…)。看这些源码后面的注释也是一样,你应该对整个框架了解之后再来仔细看细节的实现。

运行一下你的程序,看看有什么问题没?

好了,运行成功后,我们再来看一下看一下上面可能让你头晕的部分,一个是关于tr标记,一个就是信号与槽了,这一节我们先更深入的说说信号与槽了。


3.信号与槽

本来我说应该把这个单独放在一节的,一想,不对……这东西其实也没多少东西可以说的,不信?来瞧瞧!

首先我们在来说一下信号与槽的作用,看看我们之前做的按钮:


还是那么漂亮呢,不过这里我们得关心一个问题,为什么我按这个按钮程序就退出了?

其实这就是信号跟槽的作用啦,按钮在设计的时候就有一个叫clicked()的信号,如果我们点击了这个按钮,那他就会发出这个信号(即是用emit发送信号),告诉我们这个东西被点了。但是这个信号发送了,你总得做出响应吧,好的,那槽函数就是用来接收信号,然后执行操作的,这里呢,就是应用程序管理类(QApplication)的close()了。

还有个问题:这信号发送到哪儿呢,我怎么知道发送给谁呢?这个事情其实就交给QObject类(事实上,绝大部分Qt类都是继承自QObject类,也包括布局类)connect静态函数来做了,connect函数把,一个类中的信号与一个类中的槽连接起来,就相当于拿一条线给连起来了,一边的信号发送了,另一边的槽函数就跟着动作了。

好了,下面来说说怎么来使用信号与槽的机制:

1.信号的声明,在类中作为信号的函数应该在前面添加signals标号,如:

signals:

void findClicked (void); // 返回值应该为void;

注意:信号无需被实现;

2.槽的声明,在类中作为槽的函数应该在权限标号(private, public等)后添加slots,但是事实上,槽函数跟普通的函数几乎一模一样,因此就算没有标号也不会有问题,但是这样的代码就难以明白了。槽函数声明如下:

private slots:

void enableFindButton (QString &str); // 跟普通函数一模一样

3.发送信号,在任何时候需要发送信号时,只需要使用你的信号函数(前提是类中已有声明),并在前面加上emit关键字(宏),用以表示提交消息,如:

emit findClicked();

4.连接信号,将一个类的信号与另一个类的槽相连接,如:

QObject::connect ( pClass1, SIGNAL(...), pClass2, SLOT(...));

值得注意的是,如果你的对象继承于QObject,那这样的前缀可以省略;

5.所有使用信号与槽的类,都必须在类的声明开始添加Q_OBJECT宏(并无分号);

 

好的,下面来说说信号与槽之间的某些细节:

1.一个信号可以连接多个槽,即是说你可以在多个connect函数中使用相同的信号。

2.一个槽可以与多个信号相关联,同上。

3.信号可以与信号相连接,那么在connect左端信号发送时,右端信号也被发送。

4.连接可以被移除,但通常没有必要:

disconnect( pClass1, SINGAL(...), pClass2, SLOT(...));

5.在同一个connect中的信号与槽必须拥有相同的参数类型表,而且不能指定参数(变量名);

好了,这就是信号与槽的全部了。


4.Qt扩展简介

信号与槽的扩展可以说是Qt最重要的特征之一了,不过这并不是Qt在语言上的扩展,最终,Qt的代码都会被编译为标准的C++代码,然而这些是怎么实现的呢?其实这是用一个用标准C++实现的moc模块来做的,如果你用命令行编译Qt的代码,那你应该很容易就见到它了,然而理解这些事实上对Qt编程的影响并不大,因此我们完全可以忽略这一部分,写出优秀的Qt代码,如果你对此有兴趣,可以查阅相关资料,这里就不赘述了。

这一节真累,写得累,看着也累(⊙o⊙)…