Part 02 对话框(Qt)
0. 本次学习Qt时主要方法:分析代码,从分析中学习语法,从分析中掌握Qt的程序设计思路。
1. 编写学习笔记主要形式:展示程序功能,从功能分析程序,总结程序设计思路,摘录(经修改)代码。
2. 主要参考学习书籍《零基础学Qt编程》(吴迪)和《C++ GUI Qt 4编程(第二版)》电子工业出版社。
3. 本Part内容:对话框,了解如何使用Qt设计模态、非模态和不同表现形态的对话框。
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. 一个不可改变大小的布局