What Is the Qt Event Loop?
The Qt event loop is the heartbeat of every Qt application. It continuously waits for events (mouse clicks, timer ticks, socket data, signals) and dispatches them to the right objects.
app.exec()
↓
┌─────────────────────────────────────────┐
│ Event Queue │
│ [TimerEvent] [MouseEvent] [KeyEvent] │
└──────────────────┬──────────────────────┘
↓
QCoreApplication::notify()
↓
QObject::event(QEvent*)
↓
Specific handler:
timerEvent() / mousePressEvent() ...
QTimer — The Simplest Way to Schedule Work
Single-Shot Timer
#include <QTimer>
// Fire once after 2 seconds
QTimer::singleShot(2000, []() {
qDebug() << "2 seconds elapsed!";
});
Repeating Timer
QTimer *timer = new QTimer(this);
timer->setInterval(1000); // every 1 second
timer->setSingleShot(false);
connect(timer, &QTimer::timeout, this, &MyClass::onTick);
timer->start();
// ... later
timer->stop();
Precise Timer
QTimer *preciseTimer = new QTimer(this);
preciseTimer->setTimerType(Qt::PreciseTimer); // OS high-res timer
preciseTimer->setInterval(10); // 10 ms
preciseTimer->start();
Timer Type Comparison:
| Type | Accuracy | CPU | Use Case |
|---|---|---|---|
Qt::CoarseTimer | ±5% | Low | UI updates, polling |
Qt::PreciseTimer | ~1ms | Higher | Audio, sampling |
Qt::VeryCoarseTimer | ~1s | Lowest | Heartbeat, watchdog |
QElapsedTimer — High-Resolution Timing
#include <QElapsedTimer>
QElapsedTimer clock;
clock.start();
// ... some operation ...
heavyComputation();
qDebug() << "Elapsed:" << clock.elapsed() << "ms";
qDebug() << "Elapsed ns:" << clock.nsecsElapsed();
// Measure multiple sections
clock.restart();
step1();
qint64 t1 = clock.elapsed();
step2();
qint64 t2 = clock.elapsed();
qDebug() << "step1:" << t1 << "ms step2:" << (t2-t1) << "ms";
Event Handling — QEvent & virtual handlers
Every QWidget subclass can override event handlers:
#include <QWidget>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QPaintEvent>
#include <QTimerEvent>
class Canvas : public QWidget {
Q_OBJECT
int m_timerId = 0;
QPoint m_cursor;
protected:
// Called every frame
void timerEvent(QTimerEvent *e) override {
if (e->timerId() == m_timerId)
update(); // schedule repaint
}
void mouseMoveEvent(QMouseEvent *e) override {
m_cursor = e->pos();
update();
}
void keyPressEvent(QKeyEvent *e) override {
if (e->key() == Qt::Key_Space) {
qDebug() << "Space pressed";
e->accept();
} else {
e->ignore(); // pass to parent
}
}
void paintEvent(QPaintEvent *) override {
QPainter p(this);
p.drawEllipse(m_cursor, 10, 10);
}
void showEvent(QShowEvent *) override {
m_timerId = startTimer(16); // ~60 fps
}
void hideEvent(QHideEvent *) override {
killTimer(m_timerId);
}
};
Event Filters — Intercept Events on Any Object
class GlobalFilter : public QObject {
Q_OBJECT
protected:
bool eventFilter(QObject *watched, QEvent *event) override {
if (event->type() == QEvent::KeyPress) {
auto *key = static_cast<QKeyEvent*>(event);
qDebug() << watched->objectName()
<< "key pressed:" << key->key();
// Return true to eat the event, false to pass it on
if (key->key() == Qt::Key_F12) return true; // swallow
}
return QObject::eventFilter(watched, event); // normal processing
}
};
// Install on specific object
GlobalFilter *filter = new GlobalFilter(this);
lineEdit->installEventFilter(filter);
// Install on entire app
qApp->installEventFilter(filter);
Custom Events
// Define custom event type
const QEvent::Type DeviceDataEvent =
static_cast<QEvent::Type>(QEvent::User + 1);
// Custom event class
class DeviceEvent : public QEvent {
public:
explicit DeviceEvent(const QByteArray &data)
: QEvent(DeviceDataEvent), m_data(data) {}
QByteArray data() const { return m_data; }
private:
QByteArray m_data;
};
// Post event (thread-safe!)
QCoreApplication::postEvent(targetObject, new DeviceEvent(rawBytes));
// Handle in receiver
void MyReceiver::customEvent(QEvent *event) {
if (event->type() == DeviceDataEvent) {
auto *de = static_cast<DeviceEvent*>(event);
processData(de->data());
}
}
processEvents() — Keep UI Alive During Loops
// Long operation that must stay on main thread
void MainWindow::flashFirmware(const QByteArray &firmware) {
int total = firmware.size();
for (int i = 0; i < total; i += 256) {
writeChunk(firmware.mid(i, 256));
m_progressBar->setValue(i * 100 / total);
// Allow UI to repaint and process queued events
QCoreApplication::processEvents(QEventLoop::AllEvents, 50);
}
m_progressBar->setValue(100);
}
Prefer worker threads. Use
processEventsonly for short, bounded loops.
Visual: Event Loop Flow
┌──────────────────────────────────────────────────────────┐
│ app.exec() │
│ │
│ ┌───────────────┐ dequeue ┌────────────────────┐ │
│ │ Event Queue │ ──────────► │ QCoreApplication │ │
│ │ │ │ ::notify() │ │
│ │ [TimerEvent] │ └──────────┬─────────┘ │
│ │ [UserEvent] │ │ │
│ │ [PaintEvent] │ ┌──────────▼─────────┐ │
│ │ [InputEvent] │ │ QObject::event() │ │
│ └───────────────┘ │ (virtual dispatch)│ │
│ ▲ └──────────┬─────────┘ │
│ │ postEvent() │ │
│ other threads specific handlers │
└──────────────────────────────────────────────────────────┘
Lab 1 — Stopwatch with QTimer
class Stopwatch : public QWidget {
Q_OBJECT
QTimer *m_timer;
QElapsedTimer m_elapsed;
QLabel *m_display;
bool m_running = false;
public:
Stopwatch(QWidget *p = nullptr) : QWidget(p) {
auto *lay = new QVBoxLayout(this);
m_display = new QLabel("00:00.000");
m_display->setAlignment(Qt::AlignCenter);
m_display->setFont(QFont("Courier", 32, QFont::Bold));
auto *startBtn = new QPushButton("Start");
auto *resetBtn = new QPushButton("Reset");
lay->addWidget(m_display);
lay->addWidget(startBtn);
lay->addWidget(resetBtn);
m_timer = new QTimer(this);
m_timer->setInterval(50); // update every 50ms
connect(m_timer, &QTimer::timeout, this, &Stopwatch::tick);
connect(startBtn, &QPushButton::clicked, this, &Stopwatch::toggle);
connect(resetBtn, &QPushButton::clicked, this, &Stopwatch::reset);
}
private slots:
void toggle() {
if (m_running) {
m_timer->stop();
m_running = false;
} else {
m_elapsed.start();
m_timer->start();
m_running = true;
}
}
void reset() {
m_timer->stop();
m_running = false;
m_display->setText("00:00.000");
}
void tick() {
qint64 ms = m_elapsed.elapsed();
int min = ms / 60000;
int sec = (ms % 60000) / 1000;
int msec = ms % 1000;
m_display->setText(QString("%1:%2.%3")
.arg(min, 2, 10, QChar('0'))
.arg(sec, 2, 10, QChar('0'))
.arg(msec, 3, 10, QChar('0')));
}
};
Lab 2 — Watchdog Timer
class Watchdog : public QObject {
Q_OBJECT
QTimer *m_timer;
int m_timeoutMs;
public:
explicit Watchdog(int ms, QObject *p = nullptr)
: QObject(p), m_timeoutMs(ms)
{
m_timer = new QTimer(this);
m_timer->setSingleShot(true);
connect(m_timer, &QTimer::timeout, this, &Watchdog::onTimeout);
}
void kick() {
// Reset the watchdog
m_timer->start(m_timeoutMs);
}
void stop() { m_timer->stop(); }
signals:
void timedOut();
private slots:
void onTimeout() {
qWarning() << "Watchdog timeout! No kick for" << m_timeoutMs << "ms";
emit timedOut();
}
};
// Usage
Watchdog *wd = new Watchdog(5000, this); // 5-second watchdog
connect(wd, &Watchdog::timedOut, this, &MyApp::handleWatchdogTimeout);
wd->kick(); // start it
// In your main loop / sensor read
void onSensorData() {
wd->kick(); // reset timeout
processData();
}
Interview Questions
Q1: What is QEventLoop and when would you use it manually?
QEventLoopis the class that drives event processing. NormallyQApplication::exec()runs it. Use it manually in a worker thread when you need signals/slots to work in that thread (e.g.,QEventLoop loop; loop.exec();), or to block until an async operation completes.
Q2: What is the difference between postEvent and sendEvent?
sendEventdelivers the event synchronously — the receiver’sevent()is called before returning, in the caller’s thread.postEventenqueues the event asynchronously — it is processed in the receiver’s thread on the next event loop iteration.postEventis thread-safe;sendEventis not.
Q3: What happens when QTimer::timeout fires but the event loop is blocked?
The timeout event is queued but cannot be dispatched until the event loop regains control. Timer accuracy degrades. This is why you should never block the main thread with long operations.
Q4: How do you implement a timeout for an async operation?
Use a
QTimer::singleShot. If the operation completes first, stop the timer. If the timer fires first, handle the timeout and cancel the operation.
Q5: What is an event filter and how does it differ from overriding event()?
An event filter is installed on a specific
QObjectand intercepts events before they reach the object. Overridingevent()only applies to the class itself. Filters can be installed from outside the class (e.g., to intercept events on widgets you don’t own).
Application: Periodic Sensor Poller
class SensorPoller : public QObject {
Q_OBJECT
QTimer *m_pollTimer;
QTimer *m_watchdog;
QElapsedTimer m_uptime;
int m_readCount = 0;
public:
SensorPoller(QObject *p = nullptr) : QObject(p) {
// Poll every 500ms
m_pollTimer = new QTimer(this);
m_pollTimer->setInterval(500);
connect(m_pollTimer, &QTimer::timeout, this, &SensorPoller::poll);
// Watchdog: if no reads for 5s → alarm
m_watchdog = new QTimer(this);
m_watchdog->setSingleShot(true);
m_watchdog->setInterval(5000);
connect(m_watchdog, &QTimer::timeout, this, &SensorPoller::onWatchdog);
m_uptime.start();
}
void start() {
m_pollTimer->start();
m_watchdog->start(5000);
}
void stop() {
m_pollTimer->stop();
m_watchdog->stop();
}
signals:
void dataReady(float temp, float volt);
void watchdogAlarm();
private slots:
void poll() {
float temp = 22.0f + (rand() % 60) / 10.0f;
float volt = 3.2f + (rand() % 20) / 100.0f;
++m_readCount;
m_watchdog->start(5000); // kick
emit dataReady(temp, volt);
qDebug() << QString("[%1s] Read #%2: T=%3 V=%4")
.arg(m_uptime.elapsed() / 1000)
.arg(m_readCount)
.arg(temp, 0, 'f', 1)
.arg(volt, 0, 'f', 2);
}
void onWatchdog() {
qWarning() << "No sensor reads for 5 seconds!";
emit watchdogAlarm();
}
};
References
| Resource | Link |
|---|---|
| Qt Docs: QTimer | https://doc.qt.io/qt-6/qtimer.html |
| Qt Docs: QElapsedTimer | https://doc.qt.io/qt-6/qelapsedtimer.html |
| Qt Docs: Event System | https://doc.qt.io/qt-6/eventsandfilters.html |
| Qt Docs: QCoreApplication | https://doc.qt.io/qt-6/qcoreapplication.html |
Next tutorial → Qt Core Containers