Handlers
• Installing Event Filters
• Staying Responsive During Intensive Processing
GUI applications are event-driven: Everything that happens once the applica- tion has started is the result of an event. When we program with Qt, we sel- dom need to think about events, because Qt widgets emit signals when some- thing significant occurs. Events become useful when we write our own custom widgets or when we want to modify the behavior of existing Qt widgets. In this chapter, we will explore Qt’s event model. We will see how to handle the different types of events in Qt. We will also look at how to use event filters to monitor events before they reach their destinations. Finally, we will examine Qt’s event loop, reviewing how to keep the user interface responsive during intensive processing.
Reimplementing Event Handlers
Events are generated by the window system or by Qt in response to various occurrences. When the user presses or releases a key or mouse button, a key or mouse event is generated. When a window is moved to reveal a window that was underneath, a paint event is generated to tell the newly visible window that it needs to repaint itself. An event is also generated whenever a widget gains or loses keyboard focus. Most events are generated in response to user actions, but some, like timer events, are generated independently by the system.
Events should not be confused with signals. Signals are useful whenusinga widget, whereas events are useful whenimplementinga widget. For example, when we are usingQPushButton, we are more interested in itsclicked()signal
than in the low-level mouse or key events that caused the signal to be emitted. But if we are implementing a class likeQPushButton, we need to write code to
handle mouse and key events and emit theclicked()signal when necessary.
QObject. The event() implementation inQWidget forwards the most common
types of events to specific event handlers, such asmousePressEvent(),keyPress- Event(), andpaintEvent(), and ignores other kinds of events.
We have already seen many event handlers when implementingMainWindow, IconEditor,Plotter,ImageEditor, andEditor in the previous chapters. There
are many other types of events, listed in theQEventreference documentation,
and it is also possible to create custom event types and dispatch custom events ourselves. Custom events are particularly useful in multithreaded applications, so they are discussed in Chapter 17 (Multithreading). Here, we will review two event types that deserve more explanation: key events and timer events.
Key events are handled by reimplementingkeyPressEvent()andkeyRelease- Event(). ThePlotterwidget reimplementskeyPressEvent(). Normally, we only
need to reimplementkeyPressEvent()since the only keys for which release is
important are the modifier keysCtrl,Shift, andAlt, and these can be checked for in akeyPressEvent()usingstate(). For example, if we were implementing a CodeEditor widget, its stripped-downkeyPressEvent() that distinguishes be-
tweenHomeandCtrl+Homewould look like this:
void CodeEditor::keyPressEvent(QKeyEvent *event) {
switch (event->key()) { case Key_Home:
if (event->state() & ControlButton) goToBeginningOfDocument(); else goToBeginningOfLine(); break; case Key_End: ··· default: QWidget::keyPressEvent(event); } }
TheTabandBacktab(Shift+Tab) keys are special cases. They are handled by
QWidget::event()before it callskeyPressEvent(), with the semantic of passing the focus to the next or previous widget in the focus chain. This behavior is usually what we want, but in aCodeEditorwidget, we might prefer to makeTab
indent a line. Theevent()reimplementation would then look like this:
bool CodeEditor::event(QEvent *event) {
if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = (QKeyEvent *)event; if (keyEvent->key() == Key_Tab) {
insertAtCurrentPosition(’\t’); return true;
} }
Reimplementing Event Handlers 165
return QWidget::event(event); }
If the event is a key press, we cast theQEventobject to aQKeyEventand check which key was pressed. If the key isTab, we do some processing and return
trueto tell Qt that we have handled the event. If we returnedfalse, Qt would propagate the event to the parent widget.
A higher-level approach for implementing key bindings is to use aQAction. For example, if goToBeginningOfLine() andgoToBeginningOfDocument() are public slots in theCodeEditorwidget, and theCodeEditoris used as the central widget in aMainWindowclass, we could add the key bindings with the following code:
MainWindow::MainWindow(QWidget *parent, const char *name) : QMainWindow(parent, name)
{
editor = new CodeEditor(this); setCentralWidget(editor); goToBeginningOfLineAct =
new QAction(tr("Go to Beginning of Line"), tr("Home"), this);
connect(goToBeginningOfLineAct, SIGNAL(activated()), editor, SLOT(goToBeginningOfLine()));
goToBeginningOfDocumentAct =
new QAction(tr("Go to Beginning of Document"), tr("Ctrl+Home"), this);
connect(goToBeginningOfDocumentAct, SIGNAL(activated()), editor, SLOT(goToBeginningOfDocument()));
··· }
This makes it easy to add the commands to a menu or a toolbar, as we saw in Chapter 3. If the commands don’t appear in the user interface, theQAction
objects could be replaced with a QAccel object, the class used by QAction
internally to support key bindings.
The choice between reimplementing keyPressEvent() and using QAction (or
QAccel) is similar to that between reimplementingresizeEvent()and using aQLayoutsubclass. If we are implementing a custom widget by subclassing
QWidget, it’s straightforward to reimplement a few more event handlers and hard-code the behavior there. But if we are merely using a widget, the higher- level interfaces provided byQActionandQLayoutare more convenient.
Another common type of event is the timer event. While most types of events occur as a result of a user action, timer events allow applications to perform processing at regular time intervals. Timer events can be used to implement blinking cursors and other animations, or simply to refresh the display. To demonstrate timer events, we will implement aTickerwidget. This widget shows a text banner that scrolls left by one pixel every 30 milliseconds. If the widget is wider than the text, the text is repeated as often as necessary to fill the entire width of the widget.
Figure 7.1. TheTickerwidget
Here’s the header file:
#ifndef TICKER_H #define TICKER_H
#include <qwidget.h>
class Ticker : public QWidget {
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
public:
Ticker(QWidget *parent = 0, const char *name = 0);
void setText(const QString &newText); QString text() const { return myText; } QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent *event); void timerEvent(QTimerEvent *event); void showEvent(QShowEvent *event); void hideEvent(QHideEvent *event);
private: QString myText; int offset; int myTimerId; }; #endif
We reimplement four event handlers inTicker, three of which we have not seen before:timerEvent(),showEvent(), andhideEvent().
Now let’s review the implementation:
#include <qpainter.h>
#include "ticker.h"
Ticker::Ticker(QWidget *parent, const char *name) : QWidget(parent, name)
{
offset = 0; myTimerId = 0; }
The constructor initializes theoffsetvariable to 0. Thexcoordinate at which the text is drawn is derived from theoffsetvalue.
Reimplementing Event Handlers 167
void Ticker::setText(const QString &newText) {
myText = newText; update();
updateGeometry(); }
ThesetText() function sets the text to display. It calls update() to force a
repaint andupdateGeometry()to notify any layout manager responsible for the Tickerwidget about a size hint change.
QSize Ticker::sizeHint() const {
return fontMetrics().size(0, text()); }
ThesizeHint()function returns the space needed by the text as the widget’s ideal size. TheQWidget::fontMetrics()function returns aQFontMetricsobject that can be queried to obtain information relating to the widget’s font. In this case, we ask for the size required by the given text.
void Ticker::paintEvent(QPaintEvent *) {
QPainter painter(this);
int textWidth = fontMetrics().width(text()); if (textWidth < 1)
return; int x = -offset; while (x < width()) {
painter.drawText(x, 0, textWidth, height(),
AlignLeft | AlignVCenter, text()); x += textWidth;
} }
ThepaintEvent()function draws the text usingQPainter::drawText(). It uses
fontMetrics()to ascertain how much horizontal space the text requires, and then draws the text as many times as necessary to fill the entire width of the widget, takingoffsetinto account.
void Ticker::showEvent(QShowEvent *) {
myTimerId = startTimer(30); }
TheshowEvent() function starts a timer. The call to QObject::startTimer()
returns an ID number, which we can use later to identify the timer. QObject
supports multiple independent timers, each with its own time interval. After the call tostartTimer(), Qt will generate a timer event approximately every 30 milliseconds; the accuracy depends on the underlying operating system. We could have called startTimer() in the Ticker constructor, but we save some resources by having Qt generate timer events only when the widget is actually visible.
if (event->timerId() == myTimerId) { ++offset; if (offset >= fontMetrics().width(text())) offset = 0; scroll(-1, 0); } else { QWidget::timerEvent(event); } }
ThetimerEvent()function is called at intervals by the system. It increments
offsetby 1 to simulate movement, wrapping at the width of the text. Then it scrolls the contents of the widget one pixel to the left usingQWidget::scroll(). It would have been sufficient to callupdate()instead ofscroll(), butscroll()
is more efficient and prevents flicker, because it simply moves the existing pixels on screen and only generates a paint event for the widget’s newly revealed area (a 1-pixel-wide strip in this case).
If the timer event isn’t for the timer we are interested in, we pass it on to our base class.
void Ticker::hideEvent(QHideEvent *) {
killTimer(myTimerId); }
ThehideEvent()function callsQObject::killTimer()to stop the timer.
Timer events are low-level, and if we need multiple timers, it can become cumbersome to keep track of all the timer IDs. In such situations, it is usually easier to create a QTimer object for each timer. QTimer emits the timeout()
signal at each time interval. QTimeralso provides a convenient interface for single-shot timers (timers that time out just once).