本文写于 2004 年第三季度

Another Look at Events
by Jasmin Blanchette
What is a spontaneous event? Which event types can be propagated or compressed? What is the difference between posting and sending an event? When should I call accept() or ignore() on an event? If you don't know all the answers, read on!

The Origins of Events

Events can be divided into three categories based on how they are created and how they are dispatched:

When we call
QApplication::exec() at the end of our main() function, the application enters Qt's event loop. Conceptually, the event loop looks like this:

    while (!exit_was_called) {
        while (!posted_event_queue_is_empty) {
            process_next_posted_event();
        }
        while (!spontaneous_event_queue_is_empty) {
            process_next_spontaneous_event();
        }
        while (!posted_event_queue_is_empty) {
            process_next_posted_event();
        }
    }
    

First, the event loop processes any posted events until the queue is empty. Then, it processes the spontaneous events until there are none left. Finally, it processes any posted events that were generated during the processing of the spontaneous events.

Sent events are not handled by the event loop. They are delivered directly to the object.

Let's see how this works in practice with paint events. When a widget is made visible for the first time, or when it's made visible after being obscured, the window system generates a (spontaneous) paint event to ask the program to redraw the widget. The event loop eventually picks up the event and dispatches it to the widget that needs to be redrawn.

Not all paint events are generated by the window system. When you call QWidget::update() to redraw a widget, the widget posts a paint event to itself. The paint event is queued and eventually dispatched by the event loop.

If you're impatient and can't wait for the event loop to redraw a widget, you could in theory call paintEvent() directly to force an immediate repaint. But in practice this isn't always possible because paintEvent() is a protected function. This would also bypass any existing event filter. For that reason, Qt provides a mechanism for sending events directly rather than posting them. QWidget::repaint() uses this mechanism to force an immediate repaint.

One advantage of posting events as opposed to sending them is that posting gives Qt the opportunity to compress them. If you call update() ten times in succession on the same widget without returning to the event loop, the ten events generated by update() will automatically be merged into a single event with the union of the regions specified in all their QPaintEvents. Compressible event types include paint events, move events, resize events, layout hint events, and language change events.

Finally, note that you can call QApplication::sendPostedEvents() at any time to force Qt to process an object's posted events at the time of the call.

Synthetic Events

Qt applications can generate their own events, either of predefined types or of custom types. This is done by creating an instance of QEvent (or a subclass) and calling QApplication::postEvent() or QApplication::sendEvent().

Both functions take a QObject * and a QEvent * as arguments. If you call postEvent(), you must create the event object using new and Qt will automatically delete it after it is processed. If you call sendEvent(), you must create the event on the stack. Here's an example of posting an event:

    QApplication::postEvent(mainWin, new QKeyEvent(QEvent::KeyPress, Key_X, 'X', 0));
    

Here's an example of sending an event:

    QKeyEvent event(QEvent::KeyPress, Key_X, 'X', 0);
    QApplication::sendEvent(mainWin, &event);
    

Qt applications rarely need to call postEvent() or sendEvent() directly because most events are generated automatically by Qt or by the window system when necessary. In most of the cases where you want to send an event, Qt includes a high-level function that does it for you (for example, update() and repaint()).

Custom Event Types

Qt lets you create your own event types. This technique is particularly useful in multithreaded applications, as a means of communicating with the GUI thread; see Chapter 17 of C++ GUI Programming with Qt 3 (p. 359) for an example.

Custom types can also be useful in single-threaded applications, as an inter-object communication mechanism. The main reason why you would use events rather than standard function calls, or signals and slots, is that events can be used both synchronously and asynchronously (depending on whether you call sendEvent() or postEvents()), whereas calling a function or invoking a slot is always synchronous. Another advantage of events is that they can be filtered. More on this in the next section.

Here's a code snippet that shows how to post a custom event:

    const QEvent::Type MyEvent = (QEvent::Type)1234;
    ...
    QApplication::postEvent(obj, new QCustomEvent(MyEvent));
    

The event must be of type QCustomEvent (or a subclass). The argument to the constructor is the type of event. Values under 1024 are reserved by Qt for predefined event types; other values can be used by applications.

To handle custom event types, reimplement the customEvent() function:

    void MyLineEdit::customEvent(QCustomEvent *event)
    {
        if (event->type() == MyEvent) {
            myEvent();
        } else {
            QLineEdit::customEvent(event);
        }
    }
    

The QCustomEvent class has a void * member that you can use for your own purposes. You can also subclass QCustomEvent and add other members if you want more type safety---but then you also need to cast the QCustomEvent to your specific type in customEvent().

Event Handling and Filtering

Events in Qt can be processed on five different levels.

Some event types can be propagated. This means that if a target doesn't handle an event, Qt tries to find another receiver for the event and calls QApplication::notify() with the new target.

For example, key events are propagated; if the widget that has the focus doesn't handle a certain key, Qt dispatches the same event to the parent widget, then to the parent's parent, and so on until it reaches the top-level widget.

Accept or Ignore?

Events that can be propagated have an accept() and an ignore() function that you can call to tell Qt that you "accept" or "ignore" the event. If an event handler calls accept() on an event, the event won't be propagated further; if an event handler calls ignore(), Qt tries to find another receiver.

If you're like most Qt developers, you probably never really bothered calling accept() and ignore() in your programs. And rightly so. Qt is designed in such a way that you normally never need to call them. The default value is "accept", and the default event handler implementations in QWidget call ignore(). If you want to accept the event, you just need to reimplement the event handler and avoid calling the QWidget implementation. If you want to ignore the event, simply pass it on to the QWidget implementation. The following code snippet illustrates the point:

    void MyFancyWidget::keyPressEvent(QKeyEvent *event)
    {
        if (event->key() == Key_Escape) {
            doEscape();
        } else {
            QWidget::keyPressEvent(event);
        }
    }
    

In this example, if the user presses Esc, we call doEscape() and the event is accepted (the default). The event won't be propagated to the parent widget. If the user presses any other key, we call QWidget's default implementation:

    void QWidget::keyPressEvent(QKeyEvent *event)
    {
        event->ignore();
    }
    

Thanks to the ignore() call, the event will be propagated to the parent widget.

So far, we've assumed that the base class is QWidget. However, the same idiom works at any level, by replacing QWidget with the base class. For example:

    void MyFancyLineEdit::keyPressEvent(QKeyEvent *event)
    {
        if (event->key() == Key_SysReq) {
            doSystemRequest();
        } else {
            QLineEdit::keyPressEvent(event);
        }
    }
    

If for some reason you handle the event in event() instead of in a specific handler such as keyPressEvent(), the procedure is somewhat different. The event() function returns a bool that tells the caller whether the event was accepted or not (true means "accept"). Calling accept() or ignore() on an event from event() is pointless. The "accept" flag is a communication mechanism between the specific event handlers and event(), whereas the bool return value of event() is used to communicate with QApplication::notify(). The default event() implementation in QWidget converts the "accept" flag into a bool as follows:

    bool QWidget::event(QEvent *event)
    {
        switch (e->type()) {
        case QEvent::KeyPress:
            keyPressEvent((QKeyEvent *)event);
            if (!((QKeyEvent *)event)->isAccepted())
                return false;
            break;
        case QEvent::KeyRelease:
            keyReleaseEvent((QKeyEvent *)event);
            if (!((QKeyEvent *)event)->isAccepted())
                return false;
            break;
            ...
        }
        return true;
    }
    

What has been said so far applies not only to key events but also to mouse, wheel, tablet, and context menu events.

Close events work differently. Calling QCloseEvent::ignore() cancels the close operation, whereas accept() tells Qt to continue closing the operation normally. To avoid any confusion, it's a good idea to call accept() or ignore() explicitly in your closeEvent() reimplementation, like this:

    void MainWindow::closeEvent(QCloseEvent *event)
    {
        if (userReallyWantsToQuit()) {
            event->accept();
        } else {
            event->ignore();
        }
    }