Qt实用技能3-理解事件循环
目录
(放个目录方便预览。这个目录是从博客复制过来的,点击会跳转到博客)
- 简介
- 事件与事件循环
- Hello World
- 循环处理
- 类比事件循环的概念
- 不同操作系统的事件循环
- Windows
- Linux X11窗口
- MacOS Cocoa Application
- Qt的事件循环
- QEventLoop类
- QCoreApplication 主事件循环
- Qt的事件分发和事件处理
- 重载事件
- QEvent
- 事件过滤器
- 事件循环的运用
- processEvents不阻塞
- QEventLoop模拟同步调用
简介
本文是《Qt实用技能》系列文章的第三篇,涛哥在这里讨论事件循环相关的知识点。
一些常见的问题,诸如:
为什么Qt程序的main函数都有一个QApplication?
执行比较耗时的任务时,怎样能不卡界面?
同步、非同步、阻塞、非阻塞到底是怎么回事?
等等,都可以在本文中找到答案。
注:文章主要发布在涛哥的博客 和 涛哥的知乎专栏-Qt进阶之路
事件与事件循环
Hello World
从Hello World说起吧
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello World");
return 0;
}
这是一段大家都很熟悉的命令行程序,运行起来会在终端输出」Hello World」,之后程序就退出了。
循环处理
我们稍微加点需求: 程序能够一直运行,每次用户输入一些信息并按下回车时,列印出用户的输入。直到输入的内容为「quit」时才退出。
按照这个需求,代码实现如下:
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[])
{
char input[1024]; //假设输入长度不超过1024
const char quitStr[] = "quit";
bool quit = false;
while (false == quit) {
scanf_s("%s", input, sizeof input);
printf("user input: %s
", input);
if (0 == memcmp(input, quitStr, sizeof quitStr)) {
quit = true;
}
}
return 0;
}
我们使用了一个while循环。在这个循环体内,不停地处理用户的输入。当输入的内容为」quit」时,循环终止条件被设置为true,循环将终止。
类比事件循环的概念
在上面这个例子中,「用户输入并按下回车」这件事情,我们可以称作一个「事件」或者「用户输入事件」,不停的去处理「事件」的这段代码,
我们可以称作「事件循环」, 也可以叫做」消息循环」,是一回事。
一般对于带UI窗口的程序来说,「事件」是由操作系统或程序框架在不同的时刻发出的。
当用户按下滑鼠、敲下键盘,或者是窗口需要重新绘制的时候,计时器触发的时候,都会发出一个相应的事件。
我们把「事件循环」的代码 提炼/抽象 如下:
function loop() {
initialize();
bool shouldQuit = false;
while(false == shouldQuit)
{
var message = get_next_message();
process_message(message);
if (message == QUIT)
{
shouldQuit = true;
}
}
}
在事件循环中, 不停地去获取下一个事件,然后做出处理。直到quit事件发生,循环结束。
有「取事件」的过程,那么自然有「存储事件」的地方,要么是操作系统存储,要么是软体框架存储。
存储事件的地方,我们称作 「事件队列」 Event Queue
处理事件,我们也称作 「事件分发」 Event Dispatch
不同操作系统的事件循环
Windows
先来看一个Windows系统的事件循环示例(win32 API):
MSG msg = { 0 };
bool done = false;
bool result = false;
while (!done)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (msg.message == WM_QUIT)
{
done = true;
}
}
思路和前面介绍的一致
Linux X11窗口
有些linux系统使用X11窗口系统,看看其窗口事件循环
Atom wmDeleteMessage = XInternAtom(mDisplay, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display, window, &wmDeleteMessage, 1);
XEvent event;
bool running = true;
while (running)
{
XNextEvent(display, &event);
switch (event.type)
{
case Expose:
printf("Expose
");
break;
case ClientMessage:
if (event.xclient.data.l[0] == wmDeleteMessage)
running = false;
break;
default:
break;
}
}
思路也是和前面一致的
MacOS Cocoa Application
在Cocoa Application中, 有一种获取事件的机制,叫做runloop(一个NSRunLoop对象,它允许进程接收窗口服务的各种事件)
一般的Cocoa Application运行流程是,从runloop的事件队列中获取一个事件(NSEvent)
派发事件(NSEvent)到合适的对象(Object)
事件被处理完成后,再取下一个事件(NSEvent),直到应用退出.
思路也是和前面一致的。
Qt的事件循环
Qt作为一个跨平台的UI框架,其事件循环实现原理, 就是把不同平台的事件循环进行了封装,并提供统一的抽象介面。
和Qt做了类似工作的,还有glfw、SDL等等很多开源库。
QEventLoop类
QEventLoop即Qt中的事件循环类,主要介面如下:
int exec(QEventLoop::ProcessEventsFlags flags = AllEvents)
void exit(int returnCode = 0)
bool isRunning() const
bool processEvents(QEventLoop::ProcessEventsFlags flags = AllEvents)
void processEvents(QEventLoop::ProcessEventsFlags flags, int maxTime)
void wakeUp()
其中exec是启动事件循环,调用exec以后,调用exec的函数就会被「阻塞」,直到EventLoop里面的while循环结束。
这里画个简单的示意图: