11.4 通用对话框:QErrorMessage、QFileDialog、QProgressDialog
本节介绍通用对话框:QErrorMessage、QFileDialog、QProgressDialog,并通过示例程序展示这三种对话框的使 用。
11.4.1 QErrorMessage
QErrorMessage 提供用于显示错误提示消息的对话框。QErrorMessage 对话框由一个文本显示框和一个复选框组成:
文本显示框展示错误消息字符串,复选框选中时,下次调用 showMessage() 就继续显示相同的错误消息;如果复选框处于非选中状态,那么下次调用
showMessage() ,就不显示相同的错误消息。
QErrorMessage 构造函数比较简单,只有指定父窗口指针的参数:
QErrorMessage(QWidget * parent = 0)
使用 QErrorMessage 对话框,可以直接调用 showMessage() 槽函数,或者将带 QString
参数的信号关联到该槽函数,都可以弹窗显示错误消息。
void showMessage(const QString &
message) //公有槽函数,按消息内容区分
void showMessage(const QString & message, const
QString & type) //公有槽函数,按 type 类型区分
第二个 showMessage() 槽函数参数就是消息字符串,如果弹窗后,用户取消复选框选中状态,那么相同字符串的消息不在弹窗显示。
第二个 showMessage() 槽函数带有消息字符串和消息类型两个参数,消息类型是自己定义的任意字符串,方便作区分。在指定 type
类型后,用户如果取消复选框选中状态,那么会判定该类型消息都不需要在显示,隐藏匹配 type 类型的所有消息。
对于 showMessage() 函数,正常情况消息会立即显示,但是如果 showMessage()
连续调用多次,排队的消息太多,就会按照先后顺序依次弹窗显示。
QErrorMessage 除了 showMessage() 函数显示消息,还有另一种用途,它的静态函数 qtHandler() 可以接管
qDebug(), qWarning() 和 qFatal() 消息显示:
QErrorMessage * qtHandler()
//静态函数
qtHandler() 函数内部调用 qInstallMessageHandler() 函数,专门创建一个 QErrorMessage 对话框,用于显示
qDebug(), qWarning() 和 qFatal() 输出的消息。这对于图形化程序很有用,如果没有命令行终端,调用 qtHandler()
保存对话框指针,那么所有 qDebug(), qWarning() 和 qFatal() 输出的消息都会自动通过对话框显示。
QErrorMessage 默认都是非模态的对话框,对于连续多次的 showMessage() 或者是接管了多次 qDebug(),
qWarning() 和 qFatal()
输出的消息,这些消息都会按顺序排队显示。对于取消复选框选中状态的消息,下次就会自动忽略重复的消息,不同的消息(不同 type 类型)总会至少显示一次。
11.4.2 QFileDialog
QFileDialog 用于获取打开或保存文件、文件夹、URL路径,之前章节多次使用过
QFileDialog::getOpenFileName() 静态函数获取打开的文件名。本小节详细介绍文件对话框,QFileDialog
对话框外观如下图所示:
文件对话框可以设置文件名 filter 过滤器(或叫筛选器),上图设置了过滤器,仅显示 *.h 和 *.cpp 文件,用户点选文件,然后点击“打开”按
钮,对话框就会关闭,并可以通过函数获取用户选择的文件名。
QFileDialog 也有两种使用方式,第一种是使用自定义对象方式,第二种是使用静态函数弹窗获取文件名。下面我们先介绍第一种自定义对象使用的
QFileDialog 普通成员函数,然后再介绍静态函数。
(1)QFileDialog 普通成员函数
QFileDialog 构造函数如下:
QFileDialog(QWidget * parent, Qt::WindowFlags flags)
QFileDialog(QWidget * parent = 0, const QString & caption = QString(),
const QString & directory = QString(), const QString & filter =
QString())
第一个构造函数参数比较简化,只有父指针和对话框标志位。
第二个构造函数除了父指针,还可以指定窗口标题文本 caption 、默认打开路径 directory 和文件名过滤器 filter 。
定义对话框对象之后,需要设置对话框的文件模式、文件名过滤器、视图模式等属性,然后再用 exec() 或 show()
显示对话框,文件对话框通常用模态显示。
QFileDialog 有五个主要的属性,控制对话框的工作和显示:
①接受模式 acceptMode
接受模式是指接受文件打开或文件保存两种模式,默认为 AcceptOpen,读写函数如下:
AcceptMode acceptMode() const
void setAcceptMode(AcceptMode mode)
文件打开模式总是打开已经存在的文件,而文件保存模式可以设置不存在文件名作为新文件写入。
AcceptMode 枚举值如下所示:
QFileDialog:: AcceptMode
枚举常量 |
数值 |
描述 |
QFileDialog::AcceptOpen |
0 |
接受文件打开,获取已存在文件名。 |
QFileDialog::AcceptSave |
1 |
接受文件保存,可以设置不存在的文件名(新建文件),如果是已存在的文件名,就是覆盖写入。 |
②文件模式 fileMode
文件模式是允许用户选择文件或文件夹的范围,就是对话框返回的文件名限定,默认为 AnyFile,读写函数如下:
FileMode fileMode() const
void setFileMode(FileMode mode)
FileMode 枚举值如下:
QFileDialog::FileMode 枚举常量 |
数值 |
描述 |
QFileDialog::AnyFile |
0 |
一个任意的文件名,无论该文件否存在。 |
QFileDialog::ExistingFile |
1 |
一个已存在的文件名。 |
QFileDialog::Directory |
2 |
一个文件夹名称,这时文件和文件夹都会显示。 |
QFileDialog::ExistingFiles |
3 |
多个已存在文件名。 |
③文件名默认后缀 defaultSuffix 以及后缀名筛选
defaultSuffix 是获取文件名的默认后缀,如果用户输入文件名时没有写后缀, 那么对话框给返回的文件名自动添加该默认后缀,读写函数为:
QString defaultSuffix() const
void setDefaultSuffix(const QString & suffix)
setDefaultSuffix() 参数的 suffix 字符串不需要以点号 . 打头,如果以点号打头,会自动删除点号,只存字母等后缀名,例如
"txt" 。
在文件对话框浏览时,可以设置后缀名过滤器,帮助筛选文件类型:
void setNameFilter(const QString &
filter)
void setNameFilters(const QStringList & filters)
QStringList nameFilters() const //获取后缀名过滤器列表
第一个设置后缀名过滤器的函数,参数是一个字符串,字符串可以是多种类型多种后缀名,例如:
"Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files
(*.xml)"
两个英文分号区分文件类型,英文括号前面是文件类型描述,英文括号里面是空格分隔的多种后缀名。
如果希望匹配所有文件,设置过滤器为 "All files (*)" 。
第二个设置后缀名过滤器的函数,其参数为 QStringList 字符串列表,不需要双分号分隔文件类型,例如:
QStringList filters;
filters << "Image files (*.png *.xpm *.jpg)"
<< "Text files (*.txt)"
<< "Any files (*)";
QFileDialog dialog(this);
dialog.setNameFilters(filters);
dialog.exec();
对于多种类型过滤器,可以选择一个作为当前显示的过滤器:
void selectNameFilter(const QString
& filter) //设置参数里的过滤器为当前显示的过滤器
QString selectedNameFilter() const //获取当前选中的后缀名过滤器
④显示模式 viewMode
文件对话框浏览文件目录时主要有列表显示和详情显示,默认是详情模式,读写函数为:
ViewMode viewMode() const
void setViewMode(ViewMode mode)
ViewMode 枚举值如下:
QFileDialog::ViewMode 枚举常量 |
数值 |
描述 |
QFileDialog::Detail |
0 |
详情模式,显示文件或文件夹条目的图标、名称和详情。 |
QFileDialog::List |
1 |
列表模式,只显示文件夹或文件夹的图标和名称。 |
⑤对话框选项 options
options 选项控制对话框的多种外观和文件筛选特性,默认不设置任何选项,读写函数如下:
Options options() const //获取多个选项
void setOptions(Options options) //设置多个选项
bool testOption(Option option) const //测试单个选项
void setOption(Option option, bool on = true)
//设置单个选项
Options 枚举标志位如下:
QFileDialog::Options 标志位 |
数值 |
描述 |
QFileDialog::ShowDirsOnly |
0x00000001 |
该标志位为 1,对话框只显示目录(仅在文件模式为 Directory 时生效)。默认标志位 0 显示所有文件和目录。 |
QFileDialog::DontResolveSymlinks |
0x00000002 |
标志位为 1 时解析符号链接(快捷方式)。默认标志位 0 解析符号链接。 |
QFileDialog::DontConfirmOverwrite |
0x00000004 |
标志位为 1 时不提示覆盖文件。默认标志位 0 会提示用户将要覆盖文件。 |
QFileDialog::DontUseNativeDialog |
0x00000010 |
不使用本地文件对话框。默认都是使用操作系统本地的文件对话框。如果要用非本土对话框,需要子类化QFileDialog并包含
Q_OBJECT 宏。 |
QFileDialog::ReadOnly |
0x00000020 |
指示显示模型为只读。 |
QFileDialog::HideNameFilterDetails |
0x00000040 |
指示后缀名详情隐藏或显示。 |
QFileDialog::DontUseSheet |
0x00000008 |
不使用工作表,兼容老版本Qt代码,在 Qt 4.5 之后已经没有用处了。 |
QFileDialog::DontUseCustomDirectoryIcons |
0x00000080 |
不使用定制文件夹图标,总是使用默认的文件夹图标。有些系统默认可以显示定制的文件夹图标,这个选项自从 Qt 5.2 开始生效。 |
这些选项需要在对话框显示之前设置完毕,如果对话框已经显示了,设置选项只能在对话框关闭后下次显示才能生效。
除了属性读写函数,文件对话框还有其他功能函数。
文件对话框界面的按钮和标签文本支持自定义内容,其读写函数为:
QString labelText(DialogLabel label)
const
void setLabelText(DialogLabel label, const QString &
text)
DialogLabel 指示读写的标签对象类型,如下列表所示:
QFileDialog::DialogLabel
枚举常量 |
数值 |
描述 |
QFileDialog::LookIn |
0 |
LookIn 标签。 |
QFileDialog::FileName |
1 |
文件名标签。 |
QFileDialog::FileType |
2 |
文件类型标签。 |
QFileDialog::Accept |
3 |
接受按钮标签,就是打开或保存按钮文本。 |
QFileDialog::Reject |
4 |
拒绝按钮标签。 |
Windows 7 系统实际测试时 QFileDialog::FileName、QFileDialog::Accept
设置有效,修改的是“文件名”标签,“打开”按钮的文本,其他的未发现效果,可能其他操作系统有效果。
文件对话框默认工作路径可以通过下面函数设置:
void setDirectory(const QString &
directory) //设置当前路径
void setDirectory(const QDir & directory)
//设置当前路径
QDir directory() const //获取当前路径
文件对话框支持模态显示和非模态显示,都是从基类继承,如 exec() 、show() 函数。
文件对话框也有一次性关联接收信号对象的打开函数:
void QFileDialog::open(QObject * receiver, const char *
member)
QFileDialog 根据文件模式决定关联到槽函数的信号:
文件模式为 ExistingFiles 时,将 filesSelected() 信号关联到槽函数;
文件模式为其他数值时,将 fileSelected() 信号关联到槽函数。
这个关联是一次性的,当对话框关闭时,解除信号和槽函数关联。
文件可以通过函数提前设置默认选中的文件,帮助用户简化点选默认文件的操作:
void selectFile(const QString &
filename) //通过代码选定默认的文件名
正常情况下,在对话框关闭后,获取用户最终选定的文件列表,使用下面函数:
QStringList selectedFiles() const
注意 selectedFiles() 返回的总是 QStringList 列表,如果打开或保存一个文件,那么文件名就是列表的第 0 号字符串。
对于非模态文件对话框,可以接收文件对话框的信号实时监测用户在对话框的点选动态:
void currentChanged(const QString &
path) //当前选中文件变化
void directoryEntered(const QString &
directory) //进入新的文件夹
void fileSelected(const QString & file) //选中的单
个文件
void filesSelected(const QStringList &
selected) //选中的多个文件,参数是列表
void filterSelected(const QString & filter)
//选中的后缀名过滤器
以上内容都是常规的获取系统文件路径形式,比如
"D:/QtProjects/ch10/stackedwidget.ui"
文件对话框还有一套针对 URL 形式路径的函数和信号,函数和信号名全都带 Url 字样,例如:
QList<QUrl> selectedUrls()
const //选择的多个URL
void urlsSelected(const QList<QUrl> &
urls) //信号,选择的多个URL
URL 路径格式举例:
"file:///D:/QtProjects/ch10/stackedwidget.ui"
就是加了前缀字符串 "file:///" ,URL 通常在网页浏览器里使用,在网页浏览器输入 URL 可以访问对应的系统文件。
系统文件路径与 URL 路径可以互相转换:
QString QUrl::toLocalFile() const //普通成员函数,将
QUrl 对象内容转为本地系统文件路径
QUrl QUrl::fromLocalFile(const QString & localFile)
//静态函数,根据本地系统文件路径转为 QUrl 对象
文件对话框的普通成员函数运用例子代码如下,针对保存一个文件,获取文件名:
QFileDialog dlg;
dlg.setAcceptMode(QFileDialog::AcceptSave);
//保存模式
dlg.setFileMode(QFileDialog::AnyFile); //选择任意一个文件
dlg.setNameFilter("Source files(*.h *.cpp);;All
files(*)"); //后缀名过滤器
dlg.setViewMode(QFileDialog::Detail); //详情显示
QString oneFileName;
if( dlg.exec() )
{
oneFileName =
dlg.selectedFiles()[0]; //确定只有一个文件名
qDebug()<<oneFileName;
}
针对打开多个文件名,示例代码:
QFileDialog dlg;
dlg.setAcceptMode(QFileDialog::AcceptOpen);
//打开模式
dlg.setFileMode(QFileDialog::ExistingFiles); //选择多个文件
dlg.setNameFilter("Source files(*.h *.cpp);;All
files(*)"); //后缀名过滤器
dlg.setViewMode(QFileDialog::Detail); //详情显示
QStringList listFileName;
if( dlg.exec() )
{
listFileName =
dlg.selectedFiles();
qDebug()<<listFileName;
}
(2)QFileDialog 静态成员函数
静态函数使用比较方便快捷,上面自定义对话框代码需要使用多个函数,逐一设置各种参数,而静态函数只需要一个函数搞定。
①获取一个打开的文件名
QString getOpenFileName(QWidget * parent
= 0, const QString & caption = QString(), const QString & dir =
QString(), const QString & filter = QString(), QString *
selectedFilter = 0, Options options = 0)
QUrl getOpenFileUrl(QWidget * parent = 0, const QString
& caption = QString(), const QUrl & dir = QUrl(), const QString
& filter = QString(), QString * selectedFilter = 0, Options options =
0, const QStringList & supportedSchemes = QStringList())
参数 parent 是父窗口指针,caption 是对话框标题文本,dir 是当前路径,filter
是后缀名过滤器,selectedFilter 是指向选中过滤器的指针,默认为 0,options 是对话框选项。
第一个函数获取的是系统文件名形式,第二个是 URL 路径形式。URL 函数最后的参数 supportedSchemes 用于限定用户允许选择的 URL
类型,文件对话框中一般默认就是选择本地系统文件。
②获取多个打开的文件名
QStringList getOpenFileNames(QWidget *
parent = 0, const QString & caption = QString(), const QString &
dir = QString(), const QString & filter = QString(), QString *
selectedFilter = 0, Options options = 0)
QList<QUrl> getOpenFileUrls(QWidget * parent = 0,
const QString & caption = QString(), const QUrl & dir = QUrl(),
const QString & filter = QString(), QString * selectedFilter = 0,
Options options = 0, const QStringList & supportedSchemes =
QStringList())
这两个函数参数与上面 ① 中两个函数一样,只是返回值为文件名列表、URL列表。
③获取一个保存的文件名
QString getSaveFileName(QWidget * parent
= 0, const QString & caption = QString(), const QString & dir =
QString(), const QString & filter = QString(), QString *
selectedFilter = 0, Options options = 0)
QUrl getSaveFileUrl(QWidget * parent = 0, const QString
& caption = QString(), const QUrl & dir = QUrl(), const QString
& filter = QString(), QString * selectedFilter = 0, Options options =
0, const QStringList & supportedSchemes = QStringList())
这两个函数参数与上面 ① 中两个函数一样,返回的文件名如果不存在就是新建文件保存,如果文件名已存在就是覆盖文件保存。
④获取一个存在的文件夹路径
QString getExistingDirectory(QWidget *
parent = 0, const QString & caption = QString(), const QString &
dir = QString(), Options options = ShowDirsOnly)
QUrl getExistingDirectoryUrl(QWidget * parent = 0, const
QString & caption = QString(), const QUrl & dir = QUrl(), Options
options = ShowDirsOnly, const QStringList & supportedSchemes =
QStringList())
这两个函数获取的是文件夹路径,参数 parent 是父窗口指针,caption 是窗口标题文本,dir 是当前路径,options
默认只显示文件夹,不显示文件,supportedSchemes 一般不用设置,默认是选择系统里的文件夹路径。
使用静态函数获取文件名就非常简单,例如:
fileName = QFileDialog::getOpenFileName(this,
tr("Open Image"), "/home/jana", tr("Image Files (*.png
*.jpg *.bmp)"));
一般填写前几个常用的参数即可,后面的参数和选项不用设置。
11.4.3 QProgressDialog
QProgressDialog 进度对话框用于程序长时间执行某个操作时,弹出显示长时间任务的进度,例如复制大文件,或者计算大文件的 MD5
校验值,这些操作耗时长,可以使用进度条 QProgressBar 或者进度对话框 QProgressDialog
显示这些长时间任务进度。QProgressDialog 外观如下图所示:
进度对话框主要由描述标签、进度条、中断按钮(或叫取消按钮)等构成,任务执行时,点击中断按钮可以中断任务。
QProgressDialog 构造函数如下:
QProgressDialog(QWidget * parent = 0, Qt::WindowFlags f =
0)
QProgressDialog(const QString & labelText, const QString &
cancelButtonText, int minimum, int maximum, QWidget * parent = 0,
Qt::WindowFlags f = 0)
第一个是简化的构造函数,参数只有父窗口指针和窗口标志位。
第二个构造函数参数 labelText 是对话框上面的描述标签文本,cancelButtonText 是中断任务按钮的文本,
minimum 是进度 0% 对应的初始值,maximum 是进度 100% 完成时对应的结束值,parent 是父窗口指针,f 是窗口标志位。
QProgressDialog 有 8 个属性,分别介绍如下:
① labelText 描述标签文本以及中止按钮设置
就是进度对话框上面的描述文本,读写函数为:
QString labelText() const
//获取描述标签文本
void setLabelText(const QString & text)
//槽函数,设置描述标签文本
对话框不仅能使用默认的标签对象,还可以自定义描述标签对象:
void setLabel(QLabel * label)
//设置自定义的标签对象
对话框取消任务的按钮文本可以修改,并可以自定义取消按钮和进度条控件:
void setCancelButtonText(const QString
& cancelButtonText) //槽函数,设置取消按钮文本
void setCancelButton(QPushButton * cancelButton)
//自定义取消按钮
void setBar(QProgressBar * bar) //自定义进度条控件
②minimum 最小值
最小值就是进度 0% 对应的值,默认值就是 0,读写函数为:
int minimum() const
void setMinimum(int minimum) //槽函数
③maximum 最大值
最大值就是进度 100% 对应的值,默认是100,读写函数为:
int maximum() const
void setMaximum(int maximum) //槽函数
可以同时设置最小值和最大值的区间:
void setRange(int minimum, int maximum)
//槽函数,设置区间
④minimumDuration 持续时间下限(单位毫秒)
持续时间下限是指当任务预期的持续时间低于该值时,不显示进度对话框;
只有任务预期的持续时间达到 minimumDuration 以上,才会显示进度对话框。这是针对时间较短任务做的优化,短时间任务没必要弹出进度条。默认的
minimumDuration 数值为 4000 毫秒,即 4 秒。
如果把 minimumDuration 修改为 0 毫秒,那么进度对话框就总是显示。minimumDuration 读写函数为:
int minimumDuration() const //单位毫秒
void setMinimumDuration(int ms) //槽函数,单位毫秒
⑤value 当前进度数值
value 就是位于 minimum 和 maximum 之前的数值,进度对话框自动把 value 计算为百分比的形式显示,例如 [0,100]
的区间,value 为 100 就是 100% 进度,但如果区间为 [0,1000] ,那么 100 的数值就只有 10% 进度。
value 读写函数为:
int value() const
void setValue(int progress) //槽函数
setValue() 槽函数会自动显示进度对话框,而不需要调用 show() 函数。
⑥ wasCanceled 是否取消任务
当用户点击取消按钮时,wasCanceled 被设置为 true,这个属性只有读取函数:
bool wasCanceled() const
在长时间任务执行过程中,每隔一段时间检查 wasCanceled(),如果为 true ,那么说明用户希望中止操作,不愿意继续等待,就可以执行中止任务的
代码,实现用户的意图。
⑦ autoClose 进度完成时自动关闭
autoClose 属性为 true 时, reset() 函数会自动关闭对话框。autoClose 默认值就是 true 。读写函数为:
bool autoClose() const
void setAutoClose(bool close)
⑧ autoReset 进度完成后自动重置
autoReset 是指当进度值达到 maximum 之后,自动调用 reset() 函数,隐藏对话框,并重置进度数值。autoReset 默认值也是
true。读写函数为:
bool autoReset() const
void setAutoReset(bool reset)
进度对话框还有两个槽函数和一个信号:
void cancel() //槽函数,取消任务
void reset() //槽函数,重置进度对话框
void canceled() //信号,用户点击取消按钮触发,该信号总是触发cancel()
槽函数
cancel() 槽函数是通过代码设置 wasCanceled() 为 true,进度对话框会隐藏,表示取消任务,并重置对话框。
reset() 槽函数重置进度对话框,如果 autoClose() 为 true 就自动隐藏对话框。
canceled() 信号由用户点击取消按钮触发,并且该信号默认关联到 cancel() 槽函数。
默认情况下,autoClose 和 autoReset 都是 true,当进度值设置为 maximum 后,自动重置进度对话框,并关闭该进度对话框。
进度对话框也可以使用 open() 打开对话框函数实现一次性的信号和槽关联:
void open(QObject * receiver, const char
* member)
注意进度对话框是将 canceled() 信号关联到接收对象的 member 槽函数。这个信号和槽函数关联是一次性的,触发一次后自动解除关联。
QProgressDialog 有两种使用方式,模态使用和非模态使用。模态使用的过程比较简单:
QProgressDialog progress("Copying
files...", "Abort Copy", 0, numFiles, this);
progress.setWindowModality(Qt::WindowModal);
for (int i = 0; i < numFiles; i++) {
progress.setValue(i);
if (progress.wasCanceled())
break;
//... copy one file
}
progress.setValue(numFiles);
定义 progress 对话框,描述标签文本为 "Copying files..." ,取消按钮文本为 "Abort Copy" ,区间就是 0 到
numFiles;
进度对话框使用 progress.setWindowModality(Qt::WindowModal)
模态设置,这样使得用户能够与进度对话框继续交互,方 便用户点击取消按钮,中止任务执行。
循环体用于处理长时间任务,在每轮循环里调用 progress.setValue(i) 更新进度,并检查用户是否取消任务;
循环结束后,调用 progress.setValue(numFiles) ,进度完成 100%,自动重置并关闭进度对话框。
QProgressDialog
非模态使用稍微复杂一些,常用于后台任务的处理,用户持续与前台窗口交互,后台进度用非模态进度对话框显示。后台操作常基于定时器
QTimer(或定时事件 QObject::timerEvent())、套接字通知 QSocketNotifier 、 QUrlOperator
,或者使用单独线程处理后台数据。
对于这些后台应用场景,在主窗口状态栏设置 QProgressBar 也能起到相同效果。
对于非模态进度对话框,需要有事件循环(event loop)持续运行,将进度对话框的 canceled() 关联到取消任务操作的槽函数上,并定期调用
setValue() 更新进度。例如:
// Operation constructor
Operation::Operation(QObject *parent)
: QObject(parent), steps(0)
{
pd = new QProgressDialog("Operation in progress.",
"Cancel", 0, 100);
connect(pd, SIGNAL(canceled()), this, SLOT(cancel()));
t = new QTimer(this);
connect(t, SIGNAL(timeout()), this, SLOT(perform()));
t->start(0);
}
void Operation::perform()
{
pd->setValue(steps);
//... perform one percent of the operation
steps++;
if (steps > pd->maximum())
t->stop();
}
void Operation::cancel()
{
t->stop();
//... cleanup
}
Operation 是持续任务的操作类,构造函数定义了进度对话框,并将进度对话框的 canceled() 信号关联到操作类对象的 cancel()
槽函数;然后新建了定时器 QTimer 对象,将定时器周期触发的信号 timeout() 关联到槽函数 perform() ,然后启动了定时器。
定时器的周期信号触发 perform() 槽函数,在该函数里面进行分批次的任务工作,并调用 pd->setValue(steps)
更新进度;当进度达到最大值 pd->maximum(),完成进度,停止定时器。
如果用户点击了进度对话框取消按钮,那么 canceled() 信号触发 Operation::cancel() 槽函数,在该函数里执行中止任务的代码。
进度对话框的内容介绍到这,下面我们通过一个示例展示本节三种对话框的使用。
11.4.4 CalcMD5 示例
我们学习一个计算文件 MD5 校验值的示例,展示 QErrorMessage、QFileDialog、QProgressDialog 三种对话框的使用。
我们打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 calcmd5,创建路径 D:\QtProjects\ch11,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,注意修改主窗口类名为 WidgetCalcMD5,然后点击下一步;
④项目管理不修改,点击完成。
由于使用 Qt 通用对话框,就不再需要额外新建自己的子窗口类和UI文件。
我们打开 widgetcalcmd5.ui 界面文件,拖入控件:
第一行控件是“文件名”标签,单行编辑器 lineEditFileName,“浏览”按钮 pushButtonBrowser,“计算MD5”按钮
pushButtonCalcMD5。
第二行是文本浏览框 textBrowserInfo 。
第一行控件按照水平布局器排列,然后在右边对象树选中根节点 WidgetCalcMD5,点击垂直布局按钮,窗口整体按照垂直布局排列。窗口尺寸为默认的
400 * 300 。
完成布局后我们依次右击两个按钮,为按钮添加 clicked() 信号的槽函数。
添加两个槽函数之后,我们保存并关闭 ui 文件,回到代码编辑界面。
我们首先编辑头文件 widgetcalcmd5.h 内容:
#ifndef
WIDGETCALCMD5_H
#define WIDGETCALCMD5_H
#include <QWidget>
#include <QErrorMessage>
#include <QFileDialog>
#include <QProgressDialog>
#include <QFile>
#include <QCryptographicHash> //计算MD5
namespace Ui {
class WidgetCalcMD5;
}
class WidgetCalcMD5 : public QWidget
{
Q_OBJECT
public:
explicit WidgetCalcMD5(QWidget *parent = 0);
~WidgetCalcMD5();
private slots:
void on_pushButtonBrowser_clicked();
void on_pushButtonCalcMD5_clicked();
private:
Ui::WidgetCalcMD5 *ui;
//错误消息框
QErrorMessage m_dlgErrorMsg;
//文件名
QString m_strFileName;
//计算文件对象 MD5 值
QByteArray CalcFileMD5( QFile &fileIn, qint64 nFileSize );
};
#endif // WIDGETCALCMD5_H
我们添加了 本节三个对话框的头文件包含,以及 QFile 和 QCryptographicHash 头文件包含。
QFile 、QCryptographicHash 用于读取文件内容和计算文件的 MD5 校验和。
WidgetCalcMD5 类内部自动添加了两个按钮槽函数声明,然后我们自定义了成员变量,
m_dlgErrorMsg 对话框用于显示错误消息,m_strFileName 保存文件名,
CalcFileMD5() 函数根据打开后的文件对象计算 MD5 校验和。
接下来我们分段编辑源文件 widgetcalcmd5.cpp 内容,首先是头文件包含和构造函数部分:
#include "widgetcalcmd5.h"
#include "ui_widgetcalcmd5.h"
WidgetCalcMD5::WidgetCalcMD5(QWidget *parent) :
QWidget(parent),
ui(new Ui::WidgetCalcMD5)
{
ui->setupUi(this);
}
WidgetCalcMD5::~WidgetCalcMD5()
{
delete ui;
}
头文件包含和构造函数、析构函数都是自动生成的内容,没有修改。
接下来我们编辑“浏览”按钮对应的槽函数:
//浏览要打开的文件
void WidgetCalcMD5::on_pushButtonBrowser_clicked()
{
QString strFileName = QFileDialog::getOpenFileName(
this, tr("选择文件"), "", "All files (*)");
if( strFileName.isEmpty() )
{
m_dlgErrorMsg.showMessage( tr("文件名为空,未选择文件!") );
return;
}
//文件名非空
m_strFileName = strFileName;
ui->lineEditFileName->setText( m_strFileName );
//清空旧的文本框
ui->textBrowserInfo->clear();
}
我们调用 QFileDialog::getOpenFileName() 静态函数获取要打开的文件名,文件对话框标题为 "选择文件",后缀名过滤器是所有文件,
然后判断文件名 strFileName 是否为空,空字符串就弹出消息框说明文件名为空,没有选择文件,直接返回;
strFileName 非空时,将文件名存到成员变量 m_strFileName,并将文件名字符串显示到 单行编辑器 ui->lineEditFileName ,最后将信息显示文本框清空,以便后续显示新打开文件的 MD5 信息。
接下来我们编辑“计算MD5”按钮槽函数内容:
//槽函数,计算文件 MD5
void WidgetCalcMD5::on_pushButtonCalcMD5_clicked()
{
//先从编辑框获取文件名
QString strFileName = ui->lineEditFileName->text().trimmed();
if( strFileName.isEmpty() )
{
m_dlgErrorMsg.showMessage( tr("文件名编辑框内容为空!") );
return;
}
//文件名非空
m_strFileName = strFileName;
//文件对象
QFile fileIn(m_strFileName);
//尝试打开
if( ! fileIn.open(QIODevice::ReadOnly) )
{
m_dlgErrorMsg.showMessage( tr("打开指定文件失败!") );
return;
}
//正常打开文件,检查文件大小
qint64 nFileSize = fileIn.size();
if( nFileSize < 1 )
{
m_dlgErrorMsg.showMessage( tr("文件大小为 0,没有数据!") );
fileIn.close();
return;
}
//计算MD5值
QByteArray baMD5 = CalcFileMD5( fileIn, nFileSize );
//构造信息字符串
QString strInfo = tr("文件名:") + m_strFileName;
strInfo += tr("\n文件大小:%1 字节").arg(nFileSize);
strInfo += tr("\nMD5校验值:\n");
strInfo += baMD5.toHex().toUpper();
//设置给文本框
ui->textBrowserInfo->setText(strInfo);
//关闭文件
fileIn.close();
}
我们先从单行编辑器获取文件名 strFileName,判断文件名是否为空,如果为空就弹窗显示错误消息,返回;
如果文件名非空,存到成员变量 m_strFileName,根据该文件名定义文件对象 fileIn,
以只读模式打开该文件,判断是否打开失败,如果失败就弹出错误消息框并返回,文件打开成功就继续后面的代码。
获取 fileIn 文件的大小,存到 nFileSize,如果文件大小 nFileSize 小于 1,说明是空的文件,
空文件就弹出显示没有数据的消息框,并关闭文件,返回。
对于正常有内容的文件,调用函数 CalcFileMD5() 计算得到 MD5 字节数组,存到 baMD5。
然后构造信息字符串 strInfo,将文件名、文件大小、文件 MD5 的十六进制字符串等内容都添加到信息字符串,
将 strInfo 显示到 ui->textBrowserInfo 文本框,最后关闭文件。
最后我们编辑 CalcFileMD5() 函数的内容:
//计算文件对象 MD5 值
QByteArray WidgetCalcMD5::CalcFileMD5( QFile &fileIn, qint64 nFileSize )
{
//返回的MD5字节数组
QByteArray baRet;
//定义算法对象
QCryptographicHash algMD5(QCryptographicHash::Md5);
//数据块
QByteArray baCurData;
//判断
if( nFileSize < 100*1000 ) //小于 100K
{
//一次读取全部数据计算MD5
baCurData = fileIn.readAll();
algMD5.addData(baCurData);
baRet = algMD5.result();
return baRet;
}
//如果是大于 100K,分块读取数据
qint64 oneBlockSize = nFileSize / 100; //可能有余数
int nBlocksCount = 100;
if( nFileSize % oneBlockSize != 0 )
{
//存在余数除不尽
nBlocksCount += 1;
}
//先设置进度对话框,然后开始循环读取文件内容
QProgressDialog dlgProgress(tr("正在计算MD5 ..."), tr("取消计算"),
0, nBlocksCount, this);
dlgProgress.setWindowModality( Qt::WindowModal );
dlgProgress.setMinimumDuration( 0 ); //总是显示对话框
//循环处理
for(int i=0; i<nBlocksCount; i++)
{
//设置进度,自动显示对话框
dlgProgress.setValue(i);
//判断是否取消
if( dlgProgress.wasCanceled() )
{
break;
}
//正常读取数据块,计算MD5
baCurData = fileIn.read(oneBlockSize);
algMD5.addData(baCurData);
}
//进度没有取消,获取正常MD5结果
if( ! dlgProgress.wasCanceled() )
{
baRet = algMD5.result();
}
//结束进度
dlgProgress.setValue( nBlocksCount );
//返回MD5字节数组
return baRet;
}
函数开头定义字节数组 baRet,用于保存 MD5 数据,定义算法对象 algMD5,用于计算文件内容的 MD5 数据,
定义 baCurData 保存读取的文件内容。
然后判断文件大小 nFileSize,如果小于 100K,那么一次性读取文件所有内容,添加到算法对象 algMD5,
从算法对象 algMD5 获取计算的 MD5 结果存到 baRet,返回该数组。
如果文件达到了 100K 和以上,我们分块读取文件内容,并用进度对话框展示实时的进度。
我们计划分 100 次(文件大小是100的倍数)或 101 次(非100整除的情况)完成文件读取。
计算 nFileSize / 100,得到一个块的大小 oneBlockSize ,
计算 nFileSize 模 oneBlockSize 的余数,如果为 0 就是整除,nBlocksCount 数值为 100 块,
如果余数非零,那么 nBlocksCount 数值为 101 块。
然后定义进度对话框 dlgProgress,对话框标签为 "正在计算MD5 ..." ,取消按钮文本 "取消计算",取值区间 0 到 nBlocksCount 。
设置 dlgProgress 为模态显示,处理过程中占据前台显示,并设置显示的最短持续时间为 0 毫秒,就是一直显示进度对话框。
然后开始循环处理文件分块,在循环体内部先调用 dlgProgress.setValue(i) 更新进度,并显示进度对话框,
调用 dlgProgress.wasCanceled(),判断是否取消计算任务,如果用户取消了就中断循环,
如果任务没有取消,正常读取 oneBlockSize 大小的文件分片,返回的数据块存到 baCurData,
将数据块 baCurData 添加到算法对象 algMD5 。
完成全部文件分块读取并添加给算法对象后,结束循环。
我们判断 dlgProgress.wasCanceled() ,如果没有取消任务,那么从算法对象获取 MD5 结果存到 baRet;
如果任务是被用户取消的,那么 baRet 是空数组。
接着我们调用 dlgProgress.setValue( nBlocksCount ) 结束进度,进度对话框自动重置并关闭,
最后返回 baRet 数组。
对于函数调用 fileIn.read(oneBlockSize) ,如果文件剩余部分有 oneBlockSize 大小,那么读取 oneBlockSize 大小的块数据返回,如果剩余部分不够 oneBlockSize 大小,那么读取剩余所有内容返回数据,这时候 baCurData 的数据长度是实际的数据块长度,而不会填充额外数据。
无论是 100 个数据分块,还是 101 个数据分块,上面循环体代码都能正确地分批次读取文件数据,并计算 MD5 校验值。
对于返回值,用户取消任务时,返回的数组是空的;用户没有取消任务,那么返回正确的 MD5 字节数组。
例子代码介绍到这,我们生成项目,运行程序,
文件名为空的情况下,直接点击“计算MD5”按钮,会弹出错误消息框:
关闭消息框,我们点击“浏览”按钮,弹出文件对话框,选择文件名:
我们在硬盘里选择一个几百兆或 1 GB 以上的文件名,然后点击“打开”按钮,选择的文件名会显示到单行编辑器,
我们再点击“计算MD5”按钮,可以看到进度对话框:
当全部任务完成后,进度对话框自动关闭,文本浏览框显示出信息:
文本浏览框显示了文件名、文件大小和 MD5 校验值。
本节内容就到这,下一节我们继续学习 Qt 通用对话框。