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循環結束。
這裡畫個簡單的示意圖: