使用QProgressDialog显示长时间任务的执行进度

目录

直接在GUI进程中执行时间长的任务会导致主界面卡死、无响应,需要用其他机制保持界面响应,或提示用户正在进行某项长时间的任务。

第一种方案,我试过QThread多线程编程,将耗时长的任务放到子线程里执行,参见之前的一篇博文《QThread的一种用法》。使用QThread的方案需要编写的代码比较多,不太方便。Qt还有其他多线程的机制,参见Qt的文档《Thread Support in Qt》,除了QThread,其它的我还没有研究过。
第二种方案,我想到软件中常见的进度条,提示用户当期任务的处理进度。Qt为这类操作提供专门的对话框类QProcessDialog。其中封装进度条,并提供设置进度的槽函数。由该类负责更新GUI中的进度条数值,保证界面可以相应。只要设置好相关的信号、槽,我们只需顺序执行任务即可。
代码示例:
[cpp]
// 创建对话框
QProgressDialog progress_dialog(“Loading grib file…”,QString(),0,100);
progress_dialog.setWindowModality(Qt::WindowModal);
CategoryModelGenerator generator(this);
// 连接信号和槽:进度条更改、对话框关闭
connect(&generator, SIGNAL(currentProcessChanged(int)),
&progress_dialog, SLOT(setValue(int)));
connect(&generator, SIGNAL(resultReady()),
&progress_dialog, SLOT(accept()));
// 执行长时间的任务
generator.addFile(category_model_, file_path);
[/cpp]
需要连接的槽有两个:

  1. 进度条当前数值更新:提醒任务进度
  2. 对话框关闭:任务结束时关闭对话框
    如上,就可以显示进度条对话框,GUI线程不再直接卡死。只需要在代码中加入QProgressDialog的几行代码,而不要重新组织程序逻辑,简单易用。

实现(未完成)

QProgressDialog的源代码参见:
qprogressdialog.h
qprogressdialog.cpp
QProgressDialog是QDialog的子类。

界面部件

私有类QProgressDialogPrivate包含进度条对话框的主要元素:标签、取消按钮和进度条。
[cpp]
QLabel *label;
QPushButton *cancel;
QProgressBar *bar;
[/cpp]

延时启动器

QProgressDialogPrivate还有一个启动延时器,保证执行超过指定时间后才显示该对话框。
[cpp]
QTimer *forceTimer;
int showTime;
[/cpp]
默认延迟时间由一个静态变量给出。
[cpp]
// If the operation is expected to take this long (as predicted by
// progress time), show the progress dialog.
static const int defaultShowTime = 4000;
[/cpp]
私有类QProgressDialogPrivate中的延迟时间对应公有类QProgressDialog中的minimumDuration属性:
[cpp]
void QProgressDialog::setMinimumDuration(int ms)
{
Q_D(QProgressDialog);
d->showTime = ms;
if (d->bar->value() == 0) {
d->forceTimer->stop();
d->forceTimer->start(ms);
}
}
int QProgressDialog::minimumDuration() const
{
Q_D(const QProgressDialog);
return d->showTime;
}
[/cpp]

启动

程序运行时QProcessDialog自动显示,因为QProcessDialog在构造函数调用显示函数。具体流程:
QProcessDialog调用QProcessDialogPrivate的init函数
[cpp]
d->init(labelText, cancelButtonText, minimum, maximum);
[/cpp]
而QProcessDialogPrivate调用将定时器timeout信号与QProcessDialog的槽函数forceShow相连。
[cpp]
forceTimer = new QTimer(q);
QObject::connect(forceTimer, SIGNAL(timeout()), q, SLOT(forceShow()));
[/cpp]
forceShow函数显示对话框:
[cpp]
void QProgressDialog::forceShow()
{
Q_D(QProgressDialog);
d->forceTimer->stop();
if (d->shown_once || d->cancellation_flag)
return;
show();
d->shown_once = true;
}
[/cpp]
如此,在时间到时显示对话框。