事件循环
事件循环(Event Loop)是 Qt 框架的核心机制,主要负责管理应用程序的事件分发、信号槽调用、定时器、网络通信等异步操作。
事件循环是一个无限循环,不断检查是否有新的事件(如鼠标点击、键盘输入、网络数据到达)需要处理,并分发给对应的对象,主要组成部分:
- 事件队列(Event Queue):存储所有待处理的事件,按照先进先出的原则处理
- 事件分发器:(Event Dispatcher):负责从队列中取出事件,将事件发送到目标对象
- 事件处理器:(Event Handler):对象中处理特定事件的方法(如 mousePressEvent())
在 Qt 应用程序中,Qt 的主事件循环由 QCoreApplication::exec() 或 QApplication::exec() 启动,一般位于 main() 函数的末尾。
c++
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec(); // 进入主事件循环
}
如果没有 app.exec(),程序会立即退出,UI 无法显示,信号槽也无法正常工作。
对于非 GUI 程序(如后台服务),使用 QCoreApplication::exec()。
事件循环的应用场景
保持 UI 响应
事件循环允许 GUI 程序在等待用户输入时不会卡死(例如一个窗口调用show方法以模态框的方式显示后不会阻塞(这一点跟winform、c++builder的form不太一样));如果没有事件循环,UI 会冻结,无法响应用户操作。
异步网络通信
QTcpSocket、QUdpSocket 依赖事件循环来接收数据。
c++
QTcpSocket socket;
socket.connectToHost("127.0.0.1", 1800);
QObject::connect(&socket, &QTcpSocket::readyRead, [&]() {
qDebug() << "Data received:" << socket.readAll();
});
readyRead 信号只能在事件循环运行时触发。
定时器
QTimer 依赖事件循环
c++
QTimer timer;
timer.setInterval(1000); // 1秒触发一次
QObject::connect(&timer, &QTimer::timeout, []() {
qDebug() << "timer triggered!";
});
timer.start(); // 需要事件循环才能运行
跨线程通信
如果对象在不同线程,信号槽调用会通过目标线程的事件循环进行调度
c++
Worker worker;
worker.moveToThread(&workerThread); // 将 worker 移到子线程
QObject::connect(this, &MainWindow::startWork, &worker, &Worker::doWork);
workerThread.start();
当 emit startWork() 时,doWork() 会在 workerThread 的事件循环中被调用。
手动控制事件循环
局部事件循环
有时需要临时进入事件循环(如等待用户输入)
c++
QEventLoop loop;
QTimer::singleShot(3000, &loop, &QEventLoop::quit); // 3秒后退出
loop.exec(); // 阻塞,直到 quit() 被调用
多线程与事件循环
每个线程可以有独立的事件循环
c++
class WorkerThread : public QThread {
protected:
void run() override
{
QTimer timer;
connect(&timer, &QTimer::timeout, []() { qDebug() << "Thread tick"; });
timer.start(1000);
exec(); // 启动子线程的事件循环
}
};
主线程的事件循环由 QApplication::exec() 启动。
子线程的事件循环由 QThread::exec() 启动。
跨线程信号槽依赖目标线程的事件循环。
扩展:关于qt线程的一些总结
- QThread 的run函数相当于程序main函数,只有在run函数中调用的函数才算是在子线程中执行;
- 建议使用moveToThread,这样简单且安全,不推荐继承QThread
事件
Qt 的事件系统是一个事件驱动架构 ,它让程序能够响应用户操作(鼠标、键盘等)、系统消息(窗口调整、定时器)、以及自定义事件。所有事件都被封装为 QEvent 对象,通过事件循环分发给对应的 QObject(尤其是 QWidget)处理。
Qt 的事件驱动实际上是面向对象的事件传递系统(继承 + 虚函数),与.net框架的事件驱动有很大区别(.net的事件驱动类似与Qt的信号、槽机制,属于发布/订阅模式)。
QEvent
- 所有事件的基类。
- 定义了
Type枚举,表示事件类型,比如鼠标事件、键盘事件、定时器事件、绘图事件等。 - 支持事件"接受/忽略"机制,控制事件是否继续传递。
c++
void QEvent::accept(); // 设置事件已处理
void QEvent::ignore(); // 设置事件未处理
bool QEvent::isAccepted() const;
QObject::event(QEvent *event)
- 事件处理的统一入口。
- 默认实现根据事件类型调用对应的虚函数,如
mousePressEvent(),keyPressEvent()。 - 可重写此函数实现自定义事件处理逻辑。
c++
bool MyWidget::event(QEvent *event) override {
if (event->type() == QEvent::KeyPress) {
// 自定义键盘事件处理
return true;
}
return QWidget::event(event); // 调用父类默认处理
}
事件过滤器(Event Filter)
- 允许一个对象监听和拦截另一个对象的事件。
- 通过
installEventFilter()安装。 - 通过重写
eventFilter()实现事件拦截与处理。
c++
class MainWindow : public QMainWindow {
public:
MainWindow();
protected:
bool eventFilter(QObject *obj, QEvent *event);
private:
QTextEdit *textEdit;
};
MainWindow::MainWindow() {
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->installEventFilter(this);//MainWindow中有一个QTextEdit控件,拦截它的键盘按下的事件。
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
if (obj == textEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug() << "you press" << keyEvent->key();
//事件不再进行传播,拦截
return true;
} else {
return false;//继续传播
}
}
else {
//当不确定是否继续传播时,按照父类的方法来处理
//即调用父类的evenFilter函数
return QMainWindow::eventFilter(obj, event);
}
}