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 when
using
a widget, whereas events are useful when
implementing
a widget. For example, when we are using
QPushButton
, we are more interested in its
clicked()
signal than in the low-level mouse or key events that caused the signal to be
emitted
. But if we are implementing a class like
QPushButton
, we need to write code to handle mouse and key events and emit the
clicked()
signal when necessary.
Events are notified to objects through their
event()
function, inherited from
QObject
. The
event()
implementation in
QWidget
forwards the most common types of events to specific event handlers, such as
mousePressEvent()
,
keyPressEvent()
, and
paintEvent()
, and ignores other kinds of events.
We have already seen many event handlers when implementing
MainWindow
,
IconEditor
,
Plotter
,
ImageEditor
, and
Editor
in the previous chapters. There are many other types of events, listed in the
QEvent
reference 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 reimplementing
keyPressEvent()
and
keyReleaseEvent()
. The
Plotter
widget reimplements
keyPressEvent()
. Normally, we only need to reimplement
keyPressEvent()
since the only keys for which release is important are the modifier keys
Ctrl
,
Shift
, and
Alt
, and these can be checked for in a
keyPressEvent()
using
state()
. For example, if we were implementing a
CodeEditor
widget, its stripped-down
keyPressEvent()
that distinguishes between
Home
and
Ctrl+Home
would 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);
}
}
The
Tab
and
Backtab
(
Shift+Tab
) keys are special cases. They are handled by
QWidget::event()
before it calls
keyPressEvent()
, 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 a
CodeEditor
widget, we might prefer to make
Tab
indent a line. The
event()
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;
}
}
return QWidget::event(event);
}
If the event is a key press, we cast the
QEvent
object to a
QKeyEvent
and check which key was pressed. If the key is
Tab
, we do some processing and return
true
to tell Qt that we have handled the event. If we returned
false
, Qt would propagate the event to the parent widget.
A higher-level approach for implementing key bindings is to use a
QAction
. For example, if
goToBeginningOfLine()
and
goToBeginningOfDocument()
are public slots in the
CodeEditor
widget, and the
CodeEditor
is used as the central widget in a
MainWindow
class, 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, the
QAction
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 reimplementing
resizeEvent()
and using a
QLayout
subclass. 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 higherlevel interfaces provided by
QAction
and
QLayout
are 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 a
Ticker
widget. 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.
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 in
Ticker
, three of which we have not seen before:
timerEvent()
,
showEvent()
, and
hideEvent()
.
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 the
offset
variable to 0. The
x
coordinate at which the text is drawn is derived from the
offset
value.
void Ticker::setText(const QString &newText)
{
myText = newText;
update();
updateGeometry();
}
The
setText()
function sets the text to display. It calls
update()
to force a repaint and
updateGeometry()
to notify any layout manager responsible for the
Ticker
widget about a size hint change.
QSize Ticker::sizeHint() const
{
return fontMetrics().size(0, text());
}
The
sizeHint()
function returns the space needed by the text as the widget's ideal size. The
QWidget::fontMetrics()
function returns a
QFontMetrics
object 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;
}
}
The
paintEvent()
function draws the text using
QPainter::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, taking
offset
into account.
void Ticker::showEvent(QShowEvent *)
{
myTimerId = startTimer(30);
}
The
showEvent()
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 to
startTimer()
, 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.
void Ticker::timerEvent(QTimerEvent *event)
{
if (event->timerId() == myTimerId) {
++offset;
if (offset >= fontMetrics().width(text()))
offset = 0;
scroll(-1, 0);
} else {
QWidget::timerEvent(event);
}
}
The
timerEvent()
function is called at intervals by the system. It
increments
offset
by 1 to simulate movement, wrapping at the width of the text. Then it scrolls the contents of the widget one pixel to the left using
QWidget::scroll()
. It would have been sufficient to call
update()
instead of
scroll()
, but
scroll()
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);
}
The
hideEvent()
function calls
QObject::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.
QTimer
also provides a convenient interface for single-shot timers (timers that time out just once).
|