• No results found

Installing Event Filters

5. We can subclass QApplication and reimplement notify().

Qt callsQApplication::notify() to send out an event. Reimplementing this function is the only way to get all the events, before any event filters get the opportunity to look at them. Event filters are generally more useful, because there can be any number of concurrent event filters, but only onenotify()function.

Many event types, including mouse and key events, can be propagated. If the event has not been handled on the way to its target object or by the target object itself, the whole event processing process is repeated, but this time with the target object’s parent as the new target. This continues, going from parent to parent, until either the event is handled or the top-level object is reached.

Caption QDialog QGroupBox QCheckBox QCheckBox QCheckBox QCheckBox➊ ➋ ➌

Figure 7.2. Event propagation in a dialog

Figure 7.2 shows how a key press event is propagated from child to parent in a dialog. When the user presses a key, the event is first sent to the widget that has focus, in this case the bottom-rightQCheckBox. If theQCheckBoxdoesn’t han- dle the event, Qt sends it to theQGroupBox, and finally to theQDialogobject.

Staying Responsive During Intensive Processing

When we callQApplication::exec(), we start Qt’s event loop. Qt issues a few events on startup to show and paint the widgets. After that, the event loop is running, constantly checking to see if any events have occurred and dispatch- ing these events toQObjects in the application.

While one event is being processed, additional events may be generated and appended to Qt’s event queue. If we spend too much time processing a par- ticular event, the user interface will become unresponsive. For example, any events generated by the window system while the application is saving a file to disk will not be processed until the file is saved. During the save, the appli- cation will not respond to requests from the window system to repaint itself. One solution is to use multiple threads: one thread for the application’s user interface and another thread to perform file saving (or any other time-consum-

while the file is being saved. We will see how to achieve this in Chapter 17. A simpler solution is to make frequent calls toQApplication::processEvents()

in the file saving code. This function tells Qt to process any pending events, and then returns control to the caller. In fact,QApplication::exec()is little more than awhileloop around aprocessEvents()function call.

Here’s an example of how we can keep the user interface responsive using

processEvents(), based on the file saving code forSpreadsheet(p. 77):

bool Spreadsheet::writeFile(const QString &fileName) {

QFile file(fileName); ···

for (int row = 0; row < NumRows; ++row) { for (int col = 0; col < NumCols; ++col) { QString str = formula(row, col); if (!str.isEmpty())

out << (Q_UINT16)row << (Q_UINT16)col << str; }

qApp->processEvents(); }

return true; }

One danger with this approach is that the user might close the main window while the application is still saving, or even click File|Save a second time, resulting in undefined behavior. The easiest solution to this problem is to replace the

qApp->processEvents();

call with a

qApp->eventLoop()->processEvents(QEventLoop::ExcludeUserInput);

call, which tells Qt to ignore mouse and key events.

Often, we want to show aQProgressDialogwhile a long running operation is taking place. QProgressDialoghas a progress bar that keeps the user informed about the progress being made by the application. QProgressDialog also provides aCancelbutton that allows the user to abort the operation. Here’s the code for saving a Spreadsheet file using this approach:

bool Spreadsheet::writeFile(const QString &fileName) {

QFile file(fileName); ···

QProgressDialog progress(tr("Saving file..."), tr("Cancel"), NumRows);

progress.setModal(true);

for (int row = 0; row < NumRows; ++row) { progress.setProgress(row);

Staying Responsive During Intensive Processing 173

if (progress.wasCanceled()) { file.remove();

return false; }

for (int col = 0; col < NumCols; ++col) { QString str = formula(row, col); if (!str.isEmpty())

out << (Q_UINT16)row << (Q_UINT16)col << str; }

}

return true; }

We create aQProgressDialogwithNumRowsas the total number of steps. Then, for each row, we callsetProgress()to update the progress bar. QProgressDialog

automatically computes a percentage by dividing the current progress value by the total number of steps. We callQApplication::processEvents()to process any repaint events or any user clicks or key presses (for example, to allow the user to clickCancel). If the user clicksCancel, we abort the save and remove the file.

We don’t callshow()on theQProgressDialogbecause progress dialogs do that for themselves. If the operation turns out to be short, presumably because the file to save is small or because the machine is fast,QProgressDialogwill detect this and will not show itself at all.

There is a completely different way of dealing with long running operations. Instead of performing the processing when the user requests, we can defer the processing until the application is idle. This can work if the processing can be safely interrupted and resumed, since we cannot predict how long the application will be idle.

In Qt, this approach can be implemented by using a special kind of timer: a 0-millisecond timer. These timers time out whenever there are no pending events. Here’s an exampletimerEvent()implementation that shows the idle processing approach:

void Spreadsheet::timerEvent(QTimerEvent *event) {

if (event->timerId() == myTimerId) {

while (step < MaxStep && !qApp->hasPendingEvents()) { performStep(step); ++step; } } else { QTable::timerEvent(event); } }

IfhasPendingEvents()returnstrue, we stop processing and give control back to

8

8