系統繁忙時的響應(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 沒有需要處理的事件時,操作繼續。