现在的位置: 首页 > 综合 > 正文

7-3 系统繁忙时的响应(Staying Responsive During Intensive Processing)

2012年08月25日 ⁄ 综合 ⁄ 共 2567字 ⁄ 字号 评论关闭

  当我们调用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没有需要处理的事件时,操作继续。

 

抱歉!评论已关闭.