K2A-B3-5
Qt Event System
Created 2018-03-04Updated 2026-05-18programming-language-stack / c_or_cpp / qt
- Description: How Qt delivers input and system events —
QEventhierarchy, virtual handler overrides (keyPressEvent,mousePressEvent,wheelEvent,dragEnterEvent,dropEvent,paintEvent,resizeEvent,closeEvent),accept/ignorepropagation, event filters (installEventFilter,eventFilter),QTimer/QTimerEvent, and custom events - My Notion Note ID: K2A-B3-5
- Created: 2018-03-04
- Updated: 2026-05-18
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
- 1. Event Loop Overview
- 2.
QEventTypes - 3. Key Events
- 4. Mouse and Wheel Events
- 5. Drag and Drop
- 6. Lifecycle Events: paint, resize, show, hide, close
- 7. Accept / Ignore and Propagation
- 8. Event Filters
- 9. Timers
- 10. Custom Events
- 11. References
1. Event Loop Overview
QCoreApplication::exec()runs the event loop: pulls events out of a per-thread queue, dispatches each to its targetQObjectby callingtarget->event(QEvent *).QObject::eventis the central dispatcher. Default implementation switches one->type()and calls the matching virtual handler (keyPressEvent,paintEvent, etc.).- Two ways to react:
- Override the per-type virtual (
void mousePressEvent(QMouseEvent *) override) — the idiomatic way; you only see events you care about. - Override
event(QEvent *e)itself — needed for event types Qt didn't break out into a virtual (e.g.,QEvent::ToolTip,QEvent::WhatsThis, custom events).
- Override the per-type virtual (
- Events are different from signals. Events are pushed into an object's queue by the framework or other code; signals are emitted out of an object after some change. A
QPushButton's click event gets translated to aclickedsignal via the button's event handler.
2. QEvent Types
- Every event has a
QEvent::Type(an enum) and a concrete subclass. Common ones:
| Type | Class | Triggered by |
|---|---|---|
KeyPress, KeyRelease |
QKeyEvent |
Key down/up |
MouseButtonPress, MouseButtonRelease, MouseButtonDblClick, MouseMove |
QMouseEvent |
Mouse buttons + movement |
Wheel |
QWheelEvent |
Mouse wheel / trackpad scroll |
Enter, Leave |
QEnterEvent, QEvent |
Mouse cursor enters/leaves widget |
FocusIn, FocusOut |
QFocusEvent |
Keyboard focus changes |
Paint |
QPaintEvent |
Region needs repainting |
Resize |
QResizeEvent |
Widget resized |
Show, Hide |
QShowEvent, QHideEvent |
Visibility toggled |
Close |
QCloseEvent |
User clicked the window close button |
DragEnter, DragMove, DragLeave, Drop |
QDragEnterEvent, QDragMoveEvent, QDragLeaveEvent, QDropEvent |
Drag-and-drop pipeline |
Timer |
QTimerEvent |
Posted by startTimer(ms) |
ContextMenu |
QContextMenuEvent |
Right-click / context-menu key |
Shortcut |
QShortcutEvent |
Key sequence matched a QShortcut |
User ... MaxUser |
QEvent subclass you define |
QCoreApplication::postEvent |
- Full list:
QEvent::Typeenum has 150+ entries — touch, gestures, native gestures, palette/font changes, IME composition, accessibility, etc.
3. Key Events
void MyWidget::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_Escape) {
close();
return;
}
if (event->modifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_S) {
save();
return;
}
QWidget::keyPressEvent(event); // chain to base for default behavior
}
event->key()is aQt::Keyenum value —Qt::Key_Return,Qt::Key_F1,Qt::Key_Home, etc. Letter keys are uppercase:Qt::Key_M, notQt::Key_m.event->modifiers()is aQt::KeyboardModifiersflag set:ShiftModifier,ControlModifier,AltModifier,MetaModifier(Cmd on macOS, Win key on Windows). Test with&ortestFlag.event->text()is the printable character string — already respects Shift / IME / dead-key composition. Use it for "what should I insert into a text buffer"; usekey()for "which key, regardless of layout".event->isAutoRepeat()—truewhen the OS is generating repeat events from a held-down key. Skip if you only want one action per physical press.- For a widget to receive key events at all, it needs keyboard focus:
setFocusPolicy(Qt::StrongFocus)(orClickFocus/TabFocus). DefaultQt::NoFocusswallows the events.
4. Mouse and Wheel Events
void MyWidget::mousePressEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
startPos_ = event->position().toPoint();
}
}
void MyWidget::mouseMoveEvent(QMouseEvent *event) {
if (event->buttons() & Qt::LeftButton) {
QPoint delta = event->position().toPoint() - startPos_;
scrollBy(delta);
}
}
void MyWidget::wheelEvent(QWheelEvent *event) {
int dy = event->angleDelta().y(); // 120 per notch on most mice
zoom(dy / 120);
}
- Qt 6 coords are
QPointFviaevent->position()/event->scenePosition()/event->globalPosition(). The Qt 5pos()/globalPos()returningQPointwas deprecated; mixing them across Qt 5 and 6 is a common porting gotcha.position().toPoint()rounds back to integer coords when needed. button()is the one button that triggered this specific event (only meaningful in press/release).buttons()is the current set of all pressed buttons (useful in move events: "is left still held?").- By default a widget only receives
mouseMoveEventwhile a button is held. To track hovers,setMouseTracking(true). setOverrideCursor(QCursor(Qt::WaitCursor))/restoreOverrideCursor()— app-wide cursor change. They're a stack; eachsetmust be paired with arestore. For local cursor change, preferwidget->setCursor(QCursor(Qt::ArrowCursor)).
// "what widget is under the cursor inside me right now?"
QWidget *child = childAt(event->position().toPoint());
if (auto *label = qobject_cast<QLabel *>(child)) {
QPixmap pm = label->pixmap(); // Qt 6: returns by value (used to be pointer)
// ...
}
childAt(QPoint)ignores transparent overlays — handy for picking the "real" child under a click in a complex layout.
5. Drag and Drop
- Receiving widget opts in by calling
setAcceptDrops(true), then handles the 4-stage pipeline:
class DropZone : public QWidget {
public:
DropZone(QWidget *parent = nullptr) : QWidget(parent) {
setAcceptDrops(true);
}
protected:
void dragEnterEvent(QDragEnterEvent *event) override {
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction(); // tell OS "yes, I want this drop"
}
}
void dropEvent(QDropEvent *event) override {
for (const QUrl &url : event->mimeData()->urls()) {
openFile(url.toLocalFile());
}
event->acceptProposedAction();
}
};
- Stages:
dragEnterEvent— first time a drag enters the widget. Inspectevent->mimeData(); if you can handle it,accept/acceptProposedAction, else ignore.dragMoveEvent— fires repeatedly while dragging over you. Override only if drop-validity depends on cursor position (e.g., a tree where you can drop on folders but not leaves).dragLeaveEvent— drag left without dropping. Used to clear visual feedback.dropEvent— drop happened on you.
- Initiating a drag (the source side):
auto *drag = new QDrag(this);
auto *mime = new QMimeData;
mime->setText(label_->text());
drag->setMimeData(mime);
drag->setPixmap(label_->grab()); // visual feedback during drag
Qt::DropAction result = drag->exec(Qt::CopyAction | Qt::MoveAction);
6. Lifecycle Events: paint, resize, show, hide, close
- These are events Qt synthesizes from the window system or from user code.
void MyWidget::paintEvent(QPaintEvent *event) {
QPainter p(this);
// paint stuff
}
void MyWidget::resizeEvent(QResizeEvent *event) {
QSize newSize = event->size();
QSize oldSize = event->oldSize(); // may be QSize(-1, -1) on first show
// re-layout custom-drawn content
}
void MainWindow::closeEvent(QCloseEvent *event) {
if (hasUnsavedChanges()) {
auto reply = QMessageBox::question(this, tr("Unsaved"),
tr("Close anyway?"),
QMessageBox::Yes | QMessageBox::No);
if (reply != QMessageBox::Yes) {
event->ignore(); // veto the close
return;
}
}
saveSettings();
event->accept();
}
event->ignore()incloseEventkeeps the window open.event->accept()(default) lets it close. Same pattern fordragEnterEvent—ignorerejects the drop.
7. Accept / Ignore and Propagation
- For input events (key, mouse, wheel), the rule is:
- If you handle it, call
event->accept()(which most handlers do implicitly). - If you don't handle it, call
event->ignore()(or just callBaseClass::keyPressEvent(event)) — Qt then walks up the parent chain looking for a handler.
- If you handle it, call
- A common bug: overriding
keyPressEventto handle only Escape, but forgetting to chain unhandled keys to the base class. Result: arrow keys, Tab navigation, etc. all stop working.
void MyWidget::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_Escape) {
close();
return;
}
QWidget::keyPressEvent(event); // <-- crucial
}
8. Event Filters
- Lets one
QObjectspy on (and optionally intercept) events being delivered to another. Useful when you can't subclass the target — e.g., adding a behavior to a 3rd-party widget.
class ClickLogger : public QObject {
public:
bool eventFilter(QObject *watched, QEvent *event) override {
if (event->type() == QEvent::MouseButtonPress) {
qDebug() << "click on" << watched->objectName();
}
return QObject::eventFilter(watched, event); // false = let it through
}
};
auto *logger = new ClickLogger(this);
someButton->installEventFilter(logger);
anotherButton->installEventFilter(logger); // same filter, multiple targets
eventFilterreturnstrueto swallow the event (no further processing, no virtual handler called),falseto let it continue normally.- Multiple filters chain in last-installed-first order. Removing:
target->removeEventFilter(filter), or just delete the filter. qApp->installEventFilter(filter)installs an application-wide filter that sees every event going to every object — powerful but expensive; only for debugging or low-level features like global key hooks.
9. Timers
- Two ways: high-level
QTimer, low-levelstartTimer/QTimerEvent.
// High-level — preferred
auto *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MyWidget::onTick);
timer->start(1000); // every 1000 ms
QTimer::singleShot(500, this, &MyWidget::deferred); // one-shot, fires once after 500 ms
// Low-level — for tight loops or thousands of timers per object
class MyWidget : public QWidget {
int timerId_ = 0;
public:
void start() { timerId_ = startTimer(16); } // ~60 FPS
protected:
void timerEvent(QTimerEvent *event) override {
if (event->timerId() == timerId_) {
tick();
}
}
};
QTimeris aQObject, has signals/slots overhead, and creates one timer-id per instance. Fine for a handful per window.startTimeris cheaper if you need many —QGraphicsItemanimations historically used it. The default timer type is coarse (~5% accuracy on most OSes); for ~1 ms accuracy, opt in:
auto *timer = new QTimer(this);
timer->setTimerType(Qt::PreciseTimer); // ~1 ms accuracy; CoarseTimer is the default
timer->start(16);
Qt::PreciseTimeris millisecond-level, not sub-millisecond — for nanosecond-precision needs, seeQChronoTimer.
10. Custom Events
- For posting your own events into the loop — useful for cross-thread "do this on the GUI thread" without writing a slot.
class FileLoadedEvent : public QEvent {
public:
static constexpr Type kType = static_cast<Type>(QEvent::User + 1);
explicit FileLoadedEvent(QByteArray data)
: QEvent(kType), data_(std::move(data)) {}
QByteArray data_;
};
// Sender (could be a worker thread):
QCoreApplication::postEvent(receiver, new FileLoadedEvent(bytes));
// Receiver — override event():
bool MyReceiver::event(QEvent *e) {
if (e->type() == FileLoadedEvent::kType) {
const auto *fe = static_cast<FileLoadedEvent *>(e);
onLoaded(fe->data_);
return true;
}
return QObject::event(e);
}
postEventis asynchronous — the event is queued, dispatched when the receiver's thread runs its event loop. The receiver takes ownership of the event and must not delete it.sendEventis synchronous — handler runs immediately in the caller's thread. Use it for testing or when you want a synchronous round-trip; don't use it for cross-thread dispatch.- For most "do this in the GUI thread" cases,
QMetaObject::invokeMethod(receiver, fn, Qt::QueuedConnection)is shorter than rolling a custom event.
11. References
- Qt 6 — The Event System — overview, event filters, custom events.
- Qt 6 —
QEvent::Typeenum — full list of event types. - Qt 6 — Drag and Drop — sender + receiver halves, mime data.
- Qt 6 —
QTimer— timer types, precision. - Qt 6 — Threads and
QObject— how events interact with thread affinity.