在密集处理过程中保持响应

发布: 2008-08-12 23:07

       当我们调用QApplication::exec()时,我们就启动了Qt的事件循环。Qt在启动的时候抛出几个显示以及绘制物件的事件。在这之后,事件循环就不停运行,不断地检查中否有什么事件发生并把这些事件分发到程序的QObject。
       当一个事件正在处理过程中,其他事件可能产生并追加到Qt的事件队列中。如果我们花费太多的时间处理特定的事件,用户界面可能会变得无法响应。例如,在程序把文件保存到磁盘上时任何窗口系统生成的事件将不能被处理,直到该文件保存完成。在保存过程中,程序不能响应窗口系统的请求来重绘它自己。
       一种解决方法是使用多线程:一个用于程序的用户界面的线程和另一个用于执行保存(或者任何其他耗时的操作)的线程。这样,程序的用户界面在保存文件的过程中依旧能保持响应。我们将在第18章中看如何实现它。
       一种的解决方法是在文件保存代码中频繁调用QApplication::processEvents()。该函数告诉Qt处理任何未决的事件,然后再把控制返回给调用者。实际上,QApplication::exec()只比一个在while循环中的processEvents()函数调用强一点。
       下面是我们如何使用processEvents()保持用户界面响应状态的例子,它基于Spreadsheet(第80页)的文件保存代码:
[code type="cpp-qt"]
bool Spreadsheet::writeFile(const QString &fileName)
{
QFile file(fileName);
...
for (int row = 0; row < RowCount; ++row) {
for (int column = 0; column < ColumnCount; ++column) {
QString str = formula(row, column);
if (!str.isEmpty())
out << quint16(row) << quint16(column) << str;
}
qApp->processEvents();
}
return true;
}
[/code]
       这种方法的一个危险是用户可能在程序正在保存过程中关闭主窗口,或者第二次点击”File|Save”,这将导致未定义的行为。该问题的简单地解决方法是把 qApp->processEvents(); 替换为 qApp->processEvents(QEventLoop::ExcludeUserInputEvents); 来告诉Qt 鼠标和键盘事件。

       通常我们希望在一个长时间运行的操作发生时显示一个QProgressDialog。QProgressDialog有一个能通过用户程序的处理进度的进度条。QProgressDialog还有一个能让用户中断操作的”Cancel “按钮。下面是使用这种方法保存Spreadsheet 文件的代码:
[code type="cpp-qt"]
bool Spreadsheet::writeFile(const QString &fileName)
{
QFile file(fileName);
...
QProgressDialog progress(this);
progress.setLabelText(tr("Saving %1").arg(fileName));
progress.setRange(0, RowCount);
progress.setModal(true);
for (int row = 0; row < RowCount; ++row) {
progress.setValue(row);
qApp->processEvents();
if (progress.wasCanceled()) {
file.remove();
return false;
}
for (int column = 0; column < ColumnCount; ++column) {
QString str = formula(row, column);
if (!str.isEmpty())
out << quint16(row) << quint16(column) << str;
}
}
return true;
}
[/code]
       我们创建了一个总共有NumRows步的QProgressDialog。然后对于每一行,我们调用setValue()来更新进度条。QProgressDialog通过当前的进度值除以总步骤值自动计算进度百分比。我们调用QApplication::processEvents()处理任何重绘事件或者任何用户点击或者按键事件(例如,允许用户点击“Cancel”)。如果用户点击了“Cancel”,我们中断保存操作并删除该文件。
       我们不需要调用QProgressDialog的 show(),因为处理进度对话框自己做这工作。如果操作结果短暂,可能是因为要保存的文件很小或者因为机器非常快,QProgressDialog会检测这种情况并从不显示它自己也是可能的。
       除了多线程和使用QProgressDialog之外,还有一种完全不同的处理长时间运行的操作的方法:与在用户请求时执行该处理不同,我们能延迟这一处理直到程序变成空闲状态。如果处理可安全中断和恢复这种方法能很好好工作,因为我们无法预测程序会空闲多长时间。
       在Qt中,这种方法可使用一个0毫秒计时器实现。这种计时器会在没有未决事件时超时。下面是显示空闲时处理方法的timerEvent()实现的例子:
[code type="cpp-qt"]
void Spreadsheet::timerEvent(QTimerEvent *event)
{
if (event->timerId() == myTimerId) {
while (step < MaxStep && !qApp->hasPendingEvents()) {
performStep(step);
++step;
}
} else {
QTableWidget::timerEvent(event);
}
}
[/code]
       如果hasPendingEvents()返回true,我们停止处理并把控制返回给Qt。处理过程将在Qt已经处理所有未决事件后恢复。




原文: http://qtchina.tk/?q=node/236

Powered by zexport