系统繁忙时的响应(Staying Responsive During Intensive Processing)
当我们调用QApplication::exec()时,Qt 就开始了事件循环。启动时,Qt 发出显示和绘制事件,把控件显示出来。然后,事件循环就开始了,不停检查是否有事件发生,然后把事件分派到程序中的QObject 对象。
一个事件正在处理时,其他的事件已经产生并加入到 Qt 的事件队列中,如果我们在处理某一个事件时花费了很多事件,这期间用户界面就不会有任何响应。例如,在程序保存文件时,窗口产生的事件就不会处理,只有在保存结束后才能处理。在保存的过程中,应用程序也不会处理窗口的绘制事件。解决这个问题的方法是多线程:一个线程处理用户界面,另一个线程进行文件保存或者其他耗时的操作。这样,程序的用户界面就会在文件保存期间保持响应。在第 18 章会介绍这种方法。还有一个简单的方法是在保存文件的过程中多次调用 QApplication::processEvents()。调用时Qt 就会处理暂停的事件,然后返回继续保存文件。其实,QApplication::exec()也是一个调用processEvents()的while 循环。下面的例子是Spreadsheet 在保存文件时用 processEvents()响应用户界面:
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;
}
但是这样做有一个危险,如果用户在保存文件期间关闭了主窗口,或者又点击了一次 File|Save
菜单,很容易造成死循环。解决的方法是把代码 qApp->processEvents()用qApp->processEvents(QEventLoop::ExcludeUserInputEvents);代替,这样,Qt 就会不处理键盘和鼠标事件。应用程序在进行长时间的操作时,经常使用 QProgressDialog,提示用户正在进行的操作的完成情况。QProgressDialog 还提供了一个 Cancel 按钮,允许用户取消当前的操作。下面的代码是Spreadsheet 保存文件时使用 QProgressDialog 的代码:
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;
}
- 首先,创建一个QProgressDialog,设置NumRows 做为步骤的总数。然后,保存一行以后,调用setValue()更新进度条的状态。QProgressDialog 根据当前步骤数和总步骤数自动计算完成的百分比。调用 QApplication::processEvents()处理可能发生的绘制事件,用户点击事件 ,或者键盘事件,如果用户点击了 Cancel 按钮,则取消保存操作,删除正在保存的文件。
我们没有调用QProgressDialog 的show()函数,因为QProgressDialog 会自己处理。如果需要保存的文件很小,所需时间很短,QProgressDialog 能够发觉这个情况,不显示进度条。
除了使用多线程和QProgressDialog,还有一种完全不同的方法处理这种耗时较长的操作:在程序空闲时进行这类操作,而不是等待用户的请求才做。但是程序空闲的时间无法预计,这种方法的条件是所进行的操作能够安全中止和继续。具体实现是,启动一个 0 毫秒的计时器。只要程序中没有其他须处理的事件,这个事件就会触发。下面的 timeEvent()函数就是这个方法的实现:
void Spreadsheet::timerEvent(QTimerEvent *event)
{
if (event->timerId() == myTimerId) {
while (step < MaxStep && !qApp->hasPendingEvents()) { performStep(step);
++step;
}
} else {
QTableWidget::timerEvent(event);
}
}
如果hasPendingEvents()返回true,暂停操作,让 Qt 控制程序运行。当Qt 没有需要处理的事件时,操作继续。