Part 02 对话框(Qt)
——2012.01.20
0. 本次学习Qt时主要方法:分析代码,从分析中学习语法,从分析中掌握Qt的程序设计思路。
1. 编写学习笔记主要形式:展示程序功能,从功能分析程序,总结程序设计思路,摘录(经修改)代码。
2. 主要参考学习书籍《零基础学Qt编程》(吴迪)和《C++ GUI Qt 4编程(第二版)》电子工业出版社。
3. 本Part内容:对话框,了解如何使用Qt设计模态、非模态和不同表现形态的对话框。
004 Program– ExtensionDlg
01.展示程序功能
如图,本Dialog Box 可以输入名字,选择性别,还可以按下确定,按下Detail按钮(如下图)。
按下“Detail”按钮后,在原Dialog Box的基础上扩展开来了,一般把这个按钮称为“toggle button”,可以让用户在对话框的简单外观和扩展外观之间来回切换。
02.从功能分析程序设计思路
看见这程序,可以想到的问题有:
第一,可以将这个窗口划分为多少个子窗口,
第二,每个子窗口之间又是如何布局,
第三,如何去完成Detail的功能。
首先,我们可以使用一个类来完成这一整个功能,而这个类中,我们需要两个子窗口的数据成员。
即,我们可以把这个窗口先划分为两个大的子窗口,根据点击”Detail”前后的窗口可以按下图来划分。
这样划分以后,然后,我们就可以在编写类的时候,分别给这两个Widget创建一个初始化函数,而这个初始化函数需要完成的工作就是将每一个子窗口再写出来,我们再细入到每一个窗口中去,可以把这两个窗口按下图划分:
待写好每一个Widget后,就可以开始给这些Widget布局了:
按照这个先合并列,使其成为行,再将行合并,使其成为一个整体的思想去设计整个窗口的布局。
最后,如果需要完成toggle button的功能,只需要加上一slots函数,让用户点击时触发这个函数即可。
03. 从功能总结程序设计思路
04.程序代码
# 表示程序类型是app TEMPLATE = app # 指定可执行文件或库的基本文件名,其中不包含任何的扩展、前缀或版本号,默认的就是当前的目录名 TARGET = DEPENDPATH += . # INCLUDEPATH 指定C++编译器搜索全局头文件的路径 INCLUDEPATH += . # Input # HEADERS 指定工程的C++头文件(.h)多个文件可用空格或回车隔开 HEADERS += \ extensionDlg.h # SOURCES 指定工程的C++实现文件(.cpp)多个文件可用空格或回车隔开 SOURCES += \ main.cpp \ extensionDlg.cpp
// main.cpp #include <QApplication> #include "extensionDlg.h" int main(int argc, char * argv[]) { QApplication app(argc, argv); ExtensionDlg exDlg; exDlg.show(); return app.exec(); }
// extensionDlg.h #ifndef EXTENSIONDLG_H #define EXTENSIONDLG_H #include <QtGui> // 引入QtGui模块的头文件 class ExtensionDlg: public QDialog // 声明自定义对话框类ExtensionDlg单公有继承自QDialog { Q_OBJECT // 加入Q_OBJECT宏,程序中用到诸如[信号/槽]等Qt核心机制的时候,都要加入这个宏 public: ExtensionDlg(); // 构造函数 void initBasicInfo(); // 初始化基础信息 void initDetailInfo(); // 扩展信息 public slots: // 声明公有槽类 void slot2Extension(); // 这个公有槽将在用户单击Detail按钮时触发 private: QWidget * baseWidget; // 伸缩前的对话框框体 QWidget * detailWidget; // 伸缩后的对话框框体 }; #endif // EXTENSIONDLG_H
// extensionDlg.cpp #include "extensiondlg.h" // 构造函数 ExtensionDlg::ExtensionDlg() { setWindowTitle(tr("Extension Dialog")); // 设置应用程序的标题 initBasicInfo(); // 初始化基本信息窗体 initDetailInfo(); // 初始化扩展信息窗体 // 设置窗体的布局 QVBoxLayout * layout = new QVBoxLayout; // 定义一个垂直布局类实体layout layout -> addWidget(baseWidget); // 将baseWidge加入布局中 layout -> addWidget(detailWidget); layout -> setSizeConstraint(QLayout::SetFixedSize); // setSizeConstraint()设置窗体的缩放模式 // QLayout::SetDefaultConstraint 设定为默认值 // QLayout::SetFixedSize 固定的窗体大小,不可通过鼠标拖动改变 // 如果这里不这样设置,用户再次单击Detail将无法恢复原来大小 layout -> setSpacing(6); // 设置位于布局之中的窗口部件之间的间隔大小 setLayout(layout); // 将设置好的布局应用加载到窗体上 } // 初始化基础信息 void ExtensionDlg::initBasicInfo() { baseWidget = new QWidget; // 实例化私有数据成员 baseWidge QLabel * nameLabel = new QLabel(tr("Name")); // 新建一个“name”的标签, 用QLabel QLineEdit * nameEdit = new QLineEdit; // 新建的“name”的标签的后面加一行可输入行,用QLineEdit QLabel * sexLabel = new QLabel (tr("Sex")); // 新建一个"sex“的标签,用QLabel QComboBox * sexComboBox = new QComboBox; // 新建的"sex"后面加入一个下拉列表框,用QcomboBox sexComboBox -> addItem(tr("male")); // 为下拉列表框中加入选项 sexComboBox -> addItem(tr("female")); QPushButton * okButton = new QPushButton(tr("OK")); // 新建按钮窗口部件,名字为"OK" QPushButton * detailButton = new QPushButton(tr("Detail")); // 同上,名字为"Detail" connect(detailButton, SIGNAL(clicked()), this, SLOT(slot2Extension())); // 使用信号/槽机制连接detailButton的单击信号和this窗口类中的slot2Wxtension()函数 // 下面三个语句,使用QDialogButtonBox创建一个符合当前窗口部件样式的一组按钮,并且它们被排列在某种布局之中。 QDialogButtonBox * btnBox = new QDialogButtonBox(Qt::Horizontal); // Qt::Horizontal 创建水平方向的按钮组合 btnBox -> addButton(okButton, QDialogButtonBox::ActionRole); // addButton将按钮加入到这个组合中 btnBox -> addButton(detailButton, QDialogButtonBox::ActionRole); // 并且ActionRole,创建的按钮有实际的功能 // 设置窗口的布局。窗体的顶级布局是一个垂直布局,而其中嵌套了一个表单布局。 QFormLayout * formLayout = new QFormLayout; // 设置表单布局,使其可以整齐地分成两列的情况 formLayout -> addRow(nameLabel, nameEdit); // addRow加入行,行里面包含了哪些变量(窗口部件) formLayout -> addRow(sexLabel, sexComboBox); QVBoxLayout * vboxLayout = new QVBoxLayout; // 创建窗体的顶级布局,并将其两个元素formLayout和btnBox都加入其中 vboxLayout -> addLayout(formLayout); vboxLayout -> addWidget(btnBox); baseWidget -> setLayout(vboxLayout); // 将设定好的布局都加载到窗体上 return; } // 初始化扩展信息 void ExtensionDlg::initDetailInfo() { detailWidget = new QWidget; // 实例化私有数据成员detailWidget // 定义“age"一栏的两个Widget QLabel * ageLabel = new QLabel(tr("Age")); QLineEdit * ageEdit = new QLineEdit; ageEdit -> setText(tr("25")); QLabel * deptLabel = new QLabel(tr("Department")); // 定义Department一栏的两个Widget QComboBox * deptComboBox = new QComboBox; deptComboBox -> addItem(tr("department 1")); deptComboBox -> addItem(tr("department 2")); deptComboBox -> addItem(tr("department 3")); deptComboBox -> addItem(tr("department 4")); // 定义address一栏的两个Widget QLabel * addressLabel = new QLabel(tr("address")); QLineEdit * addressEdit = new QLineEdit; // 定义表单布局,并且将三行内容分别添加进去 QFormLayout * formLayout = new QFormLayout; formLayout -> addRow(ageLabel, ageEdit); formLayout -> addRow(deptLabel, deptComboBox); formLayout -> addRow(addressLabel, addressEdit); detailWidget -> setLayout(formLayout); detailWidget -> hide(); return; } // 在用户单击Detail按钮时触发 void ExtensionDlg::slot2Extension() { // 通过一个if语句,可以控制它显示和隐藏两个动作 // 当时,也可以用 isShown()函数 if(detailWidget -> isHidden()) { detailWidget -> show(); } else { detailWidget -> hide(); } return; }
005 Program – FindDialog
01. 展示程序功能
如图,本FindDialog有六个子窗口,可以实现关键字查找,查找匹配,往后查找,关闭的功能。
在未在Findwhat的EditLine输入字符时,”Find”Button不允许用户单击,输入字符后,情况则不同了。
最后,这个对话框中,还有两个可选项,可以根据用户需求选择功能。
02. 从界面分析总结程序设计思路
看见这程序,可以想到的问题有:
第一,可以将这个窗口划分为多少个子窗口,
第二,每个子窗口之间又是如何布局,
第三,如何去完成LineEdit、2个CheckBox与FindButtonBox之间建立信号/槽机制。
首先,这个程序与上一个程序的情况有点不同,因为它不需要实现切换的功能,不需要“ toggle Button”,所以,不用创建多个QWidget父类,来实现整个窗口的布局了,只需要创建QPushButton, QLabel, QCheckBox, QLineEdit这些子类就可以完成窗口布局了。如下图所示。
然后,关于第三个问题,它们之间如何建立信号/槽机制。
03. 程序代码
#FindDialog.pro HEADERS += \ finddialog.h SOURCES += \ finddialog.cpp \ main.cpp
// 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> // 包含Qt对话框的基类 class QCheckBox; class QLabel; class QLineEdit; class QPushButton; class finddialog: public QDialog { Q_OBJECT // 对于所有定义了信号和槽的类,在类定义开始处的Q_OBJECT宏都是必需的 public: finddialog(QWidget * parent = 0); // parent参数指定父窗口对象,空指针则指明该对话框无父对象 signals: // signals,用户消息事件,这关键字实际上也是一个宏,C++预处理器会在编译前转换成标准的C++代码。 void findNext(const QString &str, Qt::CaseSensitivity cs); void findPrevious(const QString & str, Qt::CaseSensitivity cs); private slots: // slots,槽,这关键字也是一个宏,C++预处理器会在编译前转换成标准的C++代码。 void findClicked(); void enableFindButton(const QString & test); private: // 这里的私有数据成员,实际上就是按照对话框需要的Widget的顺序而定义的。 QLabel * label; QLineEdit * lineEdit; QCheckBox * caseCheckBox; QCheckBox * backwardCheckBox; QPushButton * findButton; QPushButton * closeButton; }; #endif // FINDDIALOG_H /* C++知识补充: * 这里使用了类的前置声明。 * 由于这些私有数据成员都是指针,也没有在头文件中就访问它们,所以编译程序就不需要这些类的完整定义。 * 这也是这里没有包含与这几个类相关的头文件的原因。 * 只是使用一些前置声明,这可以使编过程更加快些。 **/
// finddialog.cpp #include "finddialog.h" #include <QtGui> // 包含了QDialog, QCheckBox, QLabel, QLineEdit, QPushButton // 构造函数 finddialog::finddialog(QWidget * parent):QDialog(parent) // 把parent参数传递给基类的构造函数 { // 设置Widget // 设置第一列第一行的两个Widget label = new QLabel(tr("Find &what:")); // 在字符串中的“&”可以表示快捷键 lineEdit = new QLineEdit; label -> setBuddy(lineEdit); // 设置标签伙伴(buddy),接收焦点(focus) // 即,当按下label的快捷键后,焦点(focus)移动到LineEdit上. // 设置第一列第二行的两个Widget caseCheckBox = new QCheckBox(tr("Match & case")); backwardCheckBox = new QCheckBox(tr("Search &backward")); // 设置第二列的第一个 Widget--fidButton findButton = new QPushButton(tr("&Find")); findButton -> setDefault(true); // 让findButton按钮成为默认按钮 findButton -> setEnabled(false); // 禁用Find按钮,使其不能与用户进行交互操作 // 设置第二列的第二个 Widget -- closeButton closeButton = new QPushButton(tr("Close")); // 设置槽 // 只要行编辑器中的文本发生变化,就会调用私有槽enableFindNutton(const QString&); connect(lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(enableFindButton(const QString &))); // 单击Find按钮时,能实现其功能,调用findClicked()函数 connect(findButton, SIGNAL(clicked()), this, SLOT(findClicked())); // 单击closeButton时,可以调用QWidget的close()函数,将对话框关闭 connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); // 设置布局 // 设置最上面两个Widget为水平排列 QHBoxLayout * topLeftLayout = new QHBoxLayout; topLeftLayout -> addWidget(label); topLeftLayout -> addWidget(lineEdit); // 设置左边的三行竖排列 QVBoxLayout * leftLayout = new QVBoxLayout; leftLayout -> addLayout(topLeftLayout); leftLayout -> addWidget(caseCheckBox); leftLayout -> addWidget(backwardCheckBox); // 设置右边的两个按钮竖排列 QVBoxLayout * rightLayout = new QVBoxLayout; rightLayout -> addWidget(findButton); rightLayout -> addWidget(closeButton); // 使整个窗口水平排列 QHBoxLayout * mainLayout = new QHBoxLayout; mainLayout -> addLayout(leftLayout); mainLayout -> addLayout(rightLayout); // 设置该 dialog box的主要参数 setLayout(mainLayout); // 设定布局 setWindowTitle(tr("Find")); // 设置标题 setFixedHeight(sizeHint().height());// QWidget::sizeHint()返回一个窗口部件“理想”的尺寸大小 } // 对话框中所用到的槽 void finddialog::findClicked() { QString text = lineEdit -> text(); Qt::CaseSensitivity cs = caseCheckBox -> isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive; // backwardCheckBox功能启动时,则激发信号,向后找,否则,向前找 if(backwardCheckBox -> isChecked()) { emit findPrevious(text, cs); } else { emit findNext(text, cs); } return; } // 判断是否允许开始查找的功能 void finddialog::enableFindButton(const QString &text) { findButton -> setEnabled(!text.isEmpty()); return; }
006 Program – GoToCellDialog(QtDesigner)
01. 展示程序功能
该对话框的WindowTitle为Go to Cell, 对话框内有正常的确认取消按钮,有一个QLabel一个LineEdit,当在LineEdit输入时,仅能按照"[A-Za-z][1-9][0-9]{0,2}"这样的格式输入。
02. 使用Qt Desinger设计程序的一般思路
第一步:使用Qt Designer创建.ui文件:(设置窗口部件及窗口布局)
① 创建并初始化子窗口部件
② 把子窗口部件放到布局中
③ 设置Tab键顺序
④ 建立信号-槽之间的连接
⑤ 实现对话框中的自定义槽
第二步:创建.h头文件。(通过简单地增加另一个间接层,解决直接使用.ui的大多数问题)
第三步:创建.cpp源文件。(包括main, 类)
第四步:创建.pro工程文件。
03. 程序编写过程
第一步:使用Qt Designer创建.ui文件:(设置窗口部件及窗口布局)
① 创建并初始化子窗口部件
-> 创建
-> 设置属性
-> 设置属性,伙伴关系
② 把子窗口部件放到布局
-> 弄好基本布局
-> 这是令窗口能自动调整大小的前提
-> 调整大小
-> 布局完成
③ 设置Tab键顺序
④ 建立信号-槽之间的连接
⑤ 实现对话框中的自定义槽
第二步:创建.h头文件。(通过简单地增加另一个间接层,解决直接使用.ui的大多数问题)
// gotocelldialog.h #ifndef GOTOCELLDIALOG_H #define GOTOCELLDIALOG_H #include <QDialog> #include "ui_gotocelldialog.h" // uic工具会将gotocelldialog.ui文件转换为C++并且将转换结果存放在ui_gotocelldialog.h文件中。 // 生成的ui_gotocelldialog.h文件中包含了类Ui::GoToCellDialog(用QtDesigner时定义的类名)的定义 class GoToCellDialog:public QDialog, public Ui::GoToCellDialog { Q_OBJECT // 程序中用到诸如信号/槽等Qt核心机制的时候,都要加入这个宏。 public: GoToCellDialog(QWidget * parent = 0); private slots: // 建立私有槽 void on_lineEdit_textChanged(); }; #endif
第三步:创建.cpp源文件。(包括main, 类)
// 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)); // 注意,这里的okButton变量和cancelButton变量是从gotocelldialog.ui那里设置的。 connect(okButton, SIGNAL(clicked()), this, SLOT(accept())); // 接受信号 connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); // 取消信号 } // 一个关于okButton的私有槽 void GoToCellDialog::on_lineEdit_textChanged() { // 当lineEdit有输入数据时,okButton的按钮变为可操作的。 // 注意,这里的okButton变量是从gotocelldialog.ui那里设置的。 okButton -> setEnabled(lineEdit -> hasAcceptableInput()); }
// main.cpp #include <QApplication> #include <QDialog> #include "gotocelldialog.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); GoToCellDialog * dialog = new GoToCellDialog; dialog -> show(); return app.exec(); }
第四步:创建.pro工程文件。
# GoToCellDialog(QtDesigner).pro HEADERS += \ gotocelldialog.h SOURCES += \ main.cpp \ gotocelldialog.cpp FORMS += \ gotocelldialog.ui
007 Program – sortdialog(QtDesigner)
01. 展示程序功能
这个对话框是一个用于电子制表软件应用程序的排序对话框(Sort对话框),在这个对话框中,用户可以选择一列或多列进行排序。More按钮允许用户在简单外观和扩展外观之间切换。
02. 使用Qt Desinger设计程序的一般思路
第一步:使用Qt Designer创建.ui文件:(设置窗口部件及窗口布局)
① 创建并初始化子窗口部件
② 把子窗口部件放到布局中
③ 设置Tab键顺序
④ 建立信号-槽之间的连接
⑤ 实现对话框中的自定义槽
第二步:创建.h头文件。(通过简单地增加另一个间接层,解决直接使用.ui的大多数问题)
第三步:创建.cpp源文件。(包括main, 类)
第四步:创建.pro工程文件。
03. 程序编写过程
第一步:使用Qt Designer创建.ui文件:(设置窗口部件及窗口布局)
① 创建并初始化子窗口部件
-> 先创建出三个Group Box
-> 然后,创建子窗口部件
-> 这时,记得将MoreButton的属性设置为Checkable
->然后,设置它们的伙伴关系
② 把子窗口部件放到布局中
③ 设置Tab键顺序
④ 建立信号-槽之间的连接
⑤ 实现对话框中的自定义槽
第二步:创建.h头文件。(通过简单地增加另一个间接层,解决直接使用.ui的大多数问题)
// 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_H
第三步:创建.cpp源文件。(包括main, 类)
// sortdialog.cpp #include "sortdialog.h" SortDialog::SortDialog(QWidget * parent):QDialog(parent) { setupUi(this); groupBox_2 -> hide(); groupBox_3 -> hide(); layout() -> setSizeConstraint(QLayout::SetFixedSize); // 使用户不能再修改这个对话框窗体的大小 setColumnRange('A', 'Z'); } // 根据电子制表软件,选择初始化组合框中的内容。 void SortDialog::setColumnRange(QChar first, QChar last) { // 清空组合框中的内容 comboBox -> clear(); comboBox_3 -> clear(); comboBox_5 -> clear(); // 给组合框中添加None选项 comboBox_6 -> addItem(tr("None")); comboBox_4 -> addItem(tr("None")); comboBox_2 -> setMinimumSize(comboBox_3 -> sizeHint()); // 给组合框添加内容,使用一个循环语句 QChar ch = first; while (ch <= last) { comboBox -> addItem(QString(ch)); comboBox_3 -> addItem(QString(ch)); comboBox_5 -> addItem(QString(ch)); ch = ch.unicode() + 1; } return; }
// main.cpp #include <QApplication> #include "sortdialog.h" int main(int argc, char * argv[]) { QApplication app(argc, argv); SortDialog * dialog = new SortDialog; dialog -> setColumnRange('A', 'G'); // 给ComboBox添加选项 dialog -> show(); return app.exec(); }
第四步:创建.pro工程文件。
# sortdialog(QtDesigner).pro SOURCES += \ main.cpp \ sortdialog.cpp HEADERS += \ sortdialog.h FORMS += \ sortdialog.ui
第五步:最后小结
设计一个扩展对话框并不比设计一个简单对话框难,需要:
1. 一个切换按钮 2. 信号-槽连接 3. 一个不可改变大小的布局