理解MiniGUI消息循环和窗口过程
1.2??消息和消息循環(huán)
在Windows系列操作系統(tǒng)中,廣泛使用了消息驅(qū)動(dòng)的概念。在MiniGUI中,我們也使用了消息驅(qū)動(dòng)作為應(yīng)用程序的創(chuàng)建構(gòu)架。
?
在消息驅(qū)動(dòng)的應(yīng)用程序中,計(jì)算機(jī)外設(shè)發(fā)生的事件,例如鍵盤鍵的敲擊、鼠標(biāo)鍵的按擊等,都由支持系統(tǒng)收集,將其以事先的約定格式翻譯為特定的消息。應(yīng)用程序一般包含有自己的消息隊(duì)列,系統(tǒng)將消息發(fā)送到應(yīng)用程序的消息隊(duì)列中。應(yīng)用程序可以建立一個(gè)循環(huán),在這個(gè)循環(huán)中讀取消息并處理消息,一直處理到特定的消息傳來為止。這樣的循環(huán)稱為消息循環(huán)。一般地,消息由代表消息的一個(gè)整型數(shù)和消息的附加參數(shù)組成。例如,鼠標(biāo)左鍵的按下消息,可能由133這個(gè)數(shù)來表示,其附加參數(shù)可能包含按下時(shí)的鼠標(biāo)所在位置信息。例如,MiniGUI中如下定義消息:
typedef struct
{
????HWND?????????????hwnd;
????int??????????????message;
????WPARAM???????????wParam;
????LPARAM???????????lParam;
????...
}MSG;
message?指定了特定的消息類型,wParam?是以u(píng)nsigned int類型定義的消息的短參數(shù),lParam?是以?long?類型定義的消息長參數(shù)。
應(yīng)用程序一般要提供一個(gè)處理消息的標(biāo)準(zhǔn)函數(shù)。在消息循環(huán)中,系統(tǒng)可以調(diào)用此函數(shù),應(yīng)用程序在此函數(shù)中處理相應(yīng)消息。
圖?1.2是一個(gè)消息驅(qū)動(dòng)的應(yīng)用程序的簡單構(gòu)架示意。
圖?1.2??消息驅(qū)動(dòng)的應(yīng)用程序的簡單構(gòu)架
在?MiniGUI?中,消息分為如下幾種類型:
ü?????????系統(tǒng)消息,為系統(tǒng)內(nèi)部管理使用。
ü?????????鼠標(biāo)消息,鼠標(biāo)的點(diǎn)擊、移動(dòng)等產(chǎn)生的消息。
ü?????????鍵盤消息,鍵盤的按鍵消息。
ü?????????窗口消息,窗口管理消息。
ü?????????菜單消息,菜單管理消息。
ü?????????命令消息等。
1.3??窗口過程和窗口類
窗口過程是用來處理窗口消息的函數(shù)過程。對(duì)于同一類型的控件,其窗口過程一般是一樣的。因此,系統(tǒng)一般利用窗口的窗口類名來區(qū)分不同的窗口類并調(diào)用不同的窗口過程。由于幾乎每一個(gè)主窗口均和其他窗口有著不同的窗口過程,因此,在?MiniGUI?中,窗口類的概念只存在于控件和窗片中。對(duì)于主窗口來說,其窗口過程在建立主窗口時(shí)指定,而對(duì)控件和窗片來說,則在注冊(cè)窗口類時(shí)指定,而在建立窗片或控件時(shí)指定所屬窗口類。
1.4??句柄
句柄是?MiniGUI?用來標(biāo)識(shí)對(duì)象的標(biāo)識(shí)符。句柄和指針概念類似,但它不一定是指針值。利用句柄,MiniGUI?將系統(tǒng)變量從應(yīng)用項(xiàng)目中分離了出來,因?yàn)槌绦騿T只能通過句柄訪問對(duì)象,因而就沒有利用指針是可能發(fā)生的因非法訪問而導(dǎo)致的數(shù)據(jù)不一致問題。
在?MiniGUI?中,窗口、控件、設(shè)備環(huán)境、菜單、圖標(biāo)等均使用句柄訪問。
二、窗口
2.1??應(yīng)用程序和主窗口
我們將基于?MiniGUI?的一個(gè)會(huì)話(session)稱為一個(gè)應(yīng)用項(xiàng)目,而其中每個(gè)單獨(dú)的線程或線程組稱為應(yīng)用。每個(gè)應(yīng)用項(xiàng)目可建立多個(gè)應(yīng)用。主窗口是建立在?MiniGUI?基礎(chǔ)上的應(yīng)用的主界面。MiniGUI?為每個(gè)主窗口建立單獨(dú)的消息隊(duì)列,在該主窗口基礎(chǔ)上派生出的窗片、對(duì)話框及其控件均使用同一消息隊(duì)列。在?MiniGUI?中,每個(gè)應(yīng)用對(duì)應(yīng)于一個(gè)線程。理論上講,每個(gè)應(yīng)用可以具備多個(gè)主窗口,但在?MiniGUI?中,主窗口均以單獨(dú)的線程實(shí)現(xiàn)。但多個(gè)主窗口對(duì)應(yīng)單一線程的情況也是可以在?MiniGUI?中實(shí)現(xiàn)的。
每個(gè)應(yīng)用項(xiàng)目有一個(gè)?MiniGUIMain?函數(shù),在這個(gè)函數(shù)中,可建立初始的應(yīng)用線程。在調(diào)用?MiniGUIMain?之前,MiniGUI?啟動(dòng)自己的桌面窗口(Desktop)。桌面窗口作為?MiniGUI?的窗口管理器而存在。下面的代碼段在?MiniGUIMain?中啟動(dòng)了三個(gè)主窗口線程:
int MiniGUIMain(int args, char* arg[])
{
?????pthread_t thread, thread2, thread3;
?????CreateThreadForMainWindow(&thread, NULL, TestWindowMain, 0);
?????CreateThreadForMainWindow(&thread2, NULL, TestWindowMain2, 0);
?????CreateThreadForMainWindow(&thread3, NULL, TestWindowMain3, 0);
?????return 0;
}
CreateThreadForMainWindow?函數(shù)為主窗口建立線程,并返回線程標(biāo)識(shí)符。
其中的第三個(gè)參數(shù)是線程的入口函數(shù)地址。如下的代碼段定義了上述代碼中第一個(gè)主窗口線程的入口函數(shù):
void InitCreateInfo(PMAINWINCREATE pCreateInfo)
{
????pCreateInfo->dwStyle = WS_THICKFRAME;
????pCreateInfo->spCaption = "The first main window" ;
????pCreateInfo->hMenu = 0;
????pCreateInfo->hCursor = GetSystemCursor(2);
????pCreateInfo->hIcon = LoadIconFromFile("res/table.ico");
????pCreateInfo->MainWindowProc = TestMainWinProc;
????pCreateInfo->lx = 50;?
????pCreateInfo->ty = 50;
????pCreateInfo->rx = 300;
????pCreateInfo->by = 480;
????pCreateInfo->iBkColor = COLOR_lightwhite;?
????pCreateInfo->dwAddData1 = 0;
????pCreateInfo->dwAddData2 = 0;
}
void* TestWindowMain(void* data)
{
????MSG Msg;
????MAINWINCREATE CreateInfo;
????HWND hMainWnd;
????InitCreateInfo(&CreateInfo);
????if( !(hMainWnd = CreateMainWindow(&CreateInfo)) )
????????return NULL;
????ShowWindow(hMainWnd, SW_SHOWNORMAL);
????while( GetMessage(&Msg, hMainWnd) ) {
????????DispatchMessage(&Msg);
????}
????MainWindowThreadCleanup(hMainWnd);
????return NULL;
}
在上面的代碼段中,該線程首先調(diào)用?CreateMainWindow建立了主窗口,然后調(diào)用?ShowWindow顯示了主窗口,最后啟動(dòng)了消息循環(huán)。當(dāng)消息循環(huán)因?yàn)榻邮盏?MSG_QUIT?消息而終止時(shí),該函數(shù)調(diào)用了?MainWindowThreadCleanup?清除了相關(guān)的線程數(shù)據(jù)。
從上述代碼中可看出主函數(shù)不支持窗口類,在調(diào)用?CreateMainWindow?函數(shù)時(shí)直接指定主窗口的窗口過程地址。我們也可以從中看到主窗口所支持的其他屬性:
1.窗口風(fēng)格。表?2.1?給出了所支持的窗口風(fēng)格
表?2.1??MiniGUI?支持的主窗口風(fēng)格
| 風(fēng)格 | 描述 |
| WS_BORDER | 創(chuàng)建一個(gè)具有單線邊框的窗口 |
| WS_THICKFRAME | 創(chuàng)建一個(gè)具有寬邊框的窗口 |
| WS_THINFRAME | 創(chuàng)建一個(gè)具有細(xì)邊框的窗口 |
| WS_CAPTION | 創(chuàng)建一個(gè)具有標(biāo)題欄的窗口 |
| WS_HSCROLL | 創(chuàng)建一個(gè)具有水平滾動(dòng)條的窗口 |
| WS_MAXMIZEBOX | 創(chuàng)建一個(gè)具有最大化框的窗口 |
| WS_MINIMIZEBOX | 創(chuàng)建一個(gè)具有最小化框的窗口 |
| WS_SYSMENU | 創(chuàng)建一個(gè)具有系統(tǒng)菜單的窗口 |
| WS_VSCROLL | 創(chuàng)建一個(gè)具有垂直滾動(dòng)條的窗口 |
| WS_DISABLED | 創(chuàng)建一個(gè)初始為禁止的窗口 |
| WS_MAXIMIZE | 創(chuàng)建一個(gè)初始最大化的窗口 |
| WS_MINIMIZE | 創(chuàng)建一個(gè)初始最小化的窗口 |
| WS_VISIBLE | 創(chuàng)建一個(gè)初始可見的窗口 |
| WS_EX_TOPMOST | 創(chuàng)建一個(gè)頂層窗口,這是一個(gè)?Win32?的擴(kuò)展風(fēng)格 |
2.窗口標(biāo)題。
3.窗口菜單。
4.窗口圖標(biāo)。
5.窗口背景色。
?
理解消息循環(huán)和窗口過程?
在利用 MiniGUI 開發(fā)應(yīng)用程序之前,首先要理解的兩個(gè)概念就是消息循環(huán)和窗口過程。消息循環(huán)是事件驅(qū)動(dòng)的 GUI 編程之基礎(chǔ)。而窗口則是圖形用戶界面的最基本交互元素。本文描述了 MiniGUI 中與消息相關(guān)的幾個(gè)重要函數(shù),也描述了 MiniGUI-Threads 和 MiniGUI-Lite 在消息循環(huán)實(shí)現(xiàn)上的幾個(gè)不同。本文還講述了在 MiniGUI 中的窗口建立和銷毀過程,并解釋了窗口過程的概念以及對(duì)一些重要消息的處理。
引言
我們知道,流行的 GUI 編程都有一個(gè)重要的概念與之相關(guān),即"事件驅(qū)動(dòng)編程"。事件驅(qū)動(dòng)的含義就是,程序的流程不再是只有一個(gè)入口和若干個(gè)出口的串行執(zhí)行線路;相反,程序會(huì)一直處于一個(gè)循環(huán)狀態(tài),在這個(gè)循環(huán)當(dāng)中,程序從外部輸入設(shè)備獲取某些事件,比如用戶的按鍵或者鼠標(biāo)的移動(dòng),然后根據(jù)這些事件作出某種的響應(yīng),并完成一定的功能,這個(gè)循環(huán)直到程序接受到某個(gè)消息為止。"事件驅(qū)動(dòng)"的底層設(shè)施,就是常說的"消息隊(duì)列"和"消息循環(huán)"。本文將具體描述 MiniGUI 中用來處理消息的幾個(gè)重要函數(shù),并描述 MiniGUI-Threads 和 MiniGUI-Lite 在消息循環(huán)實(shí)現(xiàn)上的一些不同。
窗口是 MiniGUI 當(dāng)中最基本的 GUI 元素,一旦窗口建立之后,窗口就會(huì)從消息隊(duì)列當(dāng)中獲取屬于自己的消息,然后交由它的窗口過程進(jìn)行處理。這些消息當(dāng)中,有一些是基本的輸入設(shè)備事件,而有一些則是與窗口管理相關(guān)的邏輯消息。本文將講述 MiniGUI 中的窗口建立和銷毀過程,并解釋了窗口過程的概念以及對(duì)一些重要消息的處理。
2 消息和消息循環(huán)
在 MiniGUI 中,消息被如下定義(include/window.h):
352 typedef struct _MSG
353 {
354 HWND hwnd;
355 int message;
356 WPARAM wParam;
357 LPARAM lParam;
358 #ifdef _LITE_VERSION
359 unsigned int time;
360 #else
361 struct timeval time;
362 #endif
363 POINT pt;
364 #ifndef _LITE_VERSION
365 void* pAdd;
366 #endif
367 }MSG;
368 typedef MSG* PMSG;
一個(gè)消息由該消息所屬的窗口(hwnd)、消息編號(hào)(message)、消息的 WPARAM 型參數(shù)(wParam)以及消息的 LPARAM 型參數(shù)(lParam)組成。消息的兩個(gè)參數(shù)中包含了重要的內(nèi)容。比如,對(duì)鼠標(biāo)消息而言,lParam 中一般包含鼠標(biāo)的位置信息,而 wParam 參數(shù)中則包含發(fā)生該消息時(shí),對(duì)應(yīng)的 SHIFT 鍵的狀態(tài)信息等。對(duì)其他不同的消息類型來講,wParam 和 lParam 也具有明確的定義。當(dāng)然,用戶也可以自定義消息,并定義消息的 wParam 和 lParam 意義。為了用戶能夠自定義消息,MiniGUI 定義了 MSG_USER 宏,可如下定義自己的消息:
#define MSG_MYMESSAGE1 (MSG_USER + 1)
#define MSG_MYMESSAGE2 (MSG_USER + 2)
用戶可以在自己的程序中使用自定義消息,并利用自定義消息傳遞數(shù)據(jù)。
在理解消息之后,我們看消息循環(huán)。簡而言之,消息循環(huán)就是一個(gè)循環(huán)體,在這個(gè)循環(huán)體中,程序利用 GetMessage 函數(shù)不停地從消息隊(duì)列中獲得消息,然后利用 DispatchMessage 函數(shù)將消息發(fā)送到指定的窗口,也就是調(diào)用指定窗口的窗口過程,并傳遞消息及其參數(shù)。典型的消息循環(huán)如下所示:
while (GetMessage (&Msg, hMainWnd)) {
TranslateMessage (&Msg);
DispatchMessage (&Msg);
}
如上所示,GetMessage 函數(shù)從 hMainWnd 窗口所屬的消息隊(duì)列當(dāng)中獲得消息,然后調(diào)用 TranslateMessage 函數(shù)將 MSG_KEYDOWN 和 MSG_KEYUP 消息翻譯成 MSG_CHAR 消息,最后調(diào)用 DispatchMessage 函數(shù)將消息發(fā)送到指定的窗口。
在 MiniGUI-Threads 版本中,每個(gè)建立有窗口的 GUI 線程有自己的消息隊(duì)列,而且,所有屬于同一線程的窗口共享同一個(gè)消息隊(duì)列。因此,GetMessage 函數(shù)將獲得所有與 hMainWnd 窗口在同一線程中的窗口的消息。
而在 MiniGUI-Lite 版本中,只有一個(gè)消息隊(duì)列,GetMessage 將從該消息隊(duì)列當(dāng)中獲得所有的消息,而忽略 hMainWnd 參數(shù)。
3 幾個(gè)重要的消息處理函數(shù)
除了上面提到的 GetMessage 和 TranslateMessage、DispatchMessage 函數(shù)以外,MiniGUI 支持如下幾個(gè)消息處理函數(shù)。
PostMessage:該函數(shù)將消息放到指定窗口的消息隊(duì)列后立即返回。這種發(fā)送方式稱為"郵寄"消息。如果消息隊(duì)列中的郵寄消息緩沖區(qū)已滿,則該函數(shù)返回錯(cuò)誤值。在下一個(gè)消息循環(huán)中,由 GetMessage 函數(shù)獲得這個(gè)消息之后,窗口才會(huì)處理該消息。PostMessage 一般用于發(fā)送一些非關(guān)鍵性的消息。比如在 MiniGUI 中,鼠標(biāo)和鍵盤消息就是通過 PostMessage 函數(shù)發(fā)送的。
SendMessage:該函數(shù)和 PostMessage 函數(shù)不同,它在發(fā)送一條消息給指定窗口時(shí),將等待該消息被處理之后才會(huì)返回。當(dāng)需要知道某個(gè)消息的處理結(jié)果時(shí),使用該函數(shù)發(fā)送消息,然后根據(jù)其返回值進(jìn)行處理。在 MiniGUI-Threads 當(dāng)中,如果發(fā)送消息的線程和接收消息的線程不是同一個(gè)線程,發(fā)送消息的線程將阻塞并等待另一個(gè)線程的處理結(jié)果,然后繼續(xù)運(yùn)行;否則,SendMessage 函數(shù)將直接調(diào)用接收消息窗口的窗口過程函數(shù)。MiniGUI-Lite 則和上面的第二種情況一樣,直接調(diào)用接收消息窗口的窗口過程函數(shù)。
SendNotifyMessage:該函數(shù)和 PostMessage 消息類似,也是不等待消息被處理即返回。但和 PostMessage 消息不同,通過該函數(shù)發(fā)送的消息不會(huì)因?yàn)榫彌_區(qū)滿而丟失,因?yàn)橄到y(tǒng)采用鏈表的形式處理這種消息。通過該函數(shù)發(fā)送的消息一般稱為"通知消息",一般用來從控件向其父窗口發(fā)送通知消息。
PostQuitMessage:該消息在消息隊(duì)列中設(shè)置一個(gè) QS_QUIT 標(biāo)志。GetMessage 在從指定消息隊(duì)列中獲取消息時(shí),會(huì)檢查該標(biāo)志,如果有 QS_QUIT 標(biāo)志,GetMessage 消息將返回 FALSE,從而可以利用該返回值終止消息循環(huán)。
4 MiniGUI-Threads 和 MiniGUI-Lite 在消息處理上的不同
表 1 總結(jié)了 MiniGUI-Threads 和 MiniGUI-Lite 在消息處理上的不同
表 1 MiniGUI-Threads 和 MiniGUI-Lite 在消息處理上的不同
MiniGUI-Threads MiniGUI-Lite?
多消息隊(duì)列 每個(gè)創(chuàng)建窗口的線程擁有獨(dú)立的消息隊(duì)列 只有一個(gè)消息隊(duì)列。所有窗口共享一個(gè)消息隊(duì)列。除非嵌套消息循環(huán),否則一個(gè)程序中只有一個(gè)消息循環(huán)。?
內(nèi)建多線程處理 是??梢宰詣?dòng)處理跨線程的消息傳遞 不能。從一個(gè)線程向另外一個(gè)線程發(fā)送或者郵寄消息時(shí),必須通過互斥處理保護(hù)消息隊(duì)列。?
其他 可以利用 PostSyncMessage 函數(shù)跨線程發(fā)送消息,并等待消息的處理結(jié)果 不能使用 PostSyncMessage、SendAsynMessage 等消息。?
5 窗口的建立和銷毀
5.1 窗口的建立
我們知道,MiniGUI 的 API 類似 Win32 的 API。因此,窗口的建立過程和 Windows 程序基本類似。不過也有一些差別。首先我們回顧一下 Windows 應(yīng)用程序的框架:
在 WinMain () 中創(chuàng)建窗口,使用以下步驟:創(chuàng)建窗口類、登記窗口類、創(chuàng)建并顯示窗口、啟動(dòng)消息循環(huán)。?
在 WndProc () 中,負(fù)責(zé)對(duì)發(fā)到窗口中的各種消息進(jìn)行響應(yīng)。?
在 MiniGUI 中也同樣要有這兩個(gè)函數(shù)。不過稍微有點(diǎn)不同。程序的入口函數(shù)名字叫MiniGUIMain (),它負(fù)責(zé)創(chuàng)建程序的主窗口。在建立主窗口之后,程序進(jìn)入消息循環(huán)。
在 Win32 程序中,在建立一個(gè)主窗口之前,程序首先要注冊(cè)一個(gè)窗口類,然后創(chuàng)建一個(gè)屬于該窗口類的主窗口。MiniGUI 卻沒有在主窗口中使用窗口類的概念。在 MiniGUI 程序中,首先初始化一個(gè) MAINWINCREATE 結(jié)構(gòu),該結(jié)構(gòu)中元素的含義是:
CreateInfo.dwStyle: 窗口風(fēng)格?
CreateInfo.spCaption: 窗口的標(biāo)題?
CreateInfo.dwExStyle : 窗口的附加風(fēng)格?
CreateInfo.hMenu: 附加在窗口上的菜單句柄?
CreateInfo.hCursor: 在窗口中所使用的鼠標(biāo)光標(biāo)句柄?
CreateInfo.hIcon: 程序的圖標(biāo)?
CreateInfo.MainWindowProc: 該窗口的消息處理函數(shù)指針?
CreateInfo.lx: 窗口左上角相對(duì)屏幕的絕對(duì)橫坐標(biāo),以象素點(diǎn)表示?
CreateInfo.ty: 窗口左上角相對(duì)屏幕的絕對(duì)縱坐標(biāo),以象素點(diǎn)表示?
CreateInfo.rx: 窗口的長,以象素點(diǎn)表示?
CreateInfo.by: 窗口的高,以象素點(diǎn)表示?
CreateInfo.iBkColor: 窗口背景顏色?
CreateInfo.dwAddData: 附帶給窗口的一個(gè) 32 位值?
CreateInfo.hHosting: 窗口消息隊(duì)列所屬?
其中有如下幾點(diǎn)要特別說明:
CreateInfo.dwAddData:在程序編制過程中,應(yīng)該盡量減少靜態(tài)變量,但是如何不使用靜態(tài)變量而給窗口傳遞參數(shù)呢?這時(shí)可以使用這個(gè)域。該域是一個(gè) 32 位的值,因此可以把所有需要傳遞給窗口的參數(shù)編制成一個(gè)結(jié)構(gòu),而將結(jié)構(gòu)的指針賦予該域。在窗口過程中,可以使用 GetWindowAdditionalData 函數(shù)獲取該指針,從而獲得所需要傳遞的參數(shù)。?
CreateInfo.hHosting:該域表示的是將要建立的主窗口使用哪個(gè)主窗口的消息隊(duì)列。使用其他主窗口消息隊(duì)列的主窗口,我們稱為"被托管"的主窗口。當(dāng)然,這只在 MiniGUI-Threads 版本中有效。?
MainWinProc 函數(shù)負(fù)責(zé)處理窗口消息。這個(gè)函數(shù)就是主窗口的"窗口過程"。窗口過程一般有四個(gè)入口參數(shù),第一個(gè)是窗口句柄,第二個(gè)是消息類型,第三個(gè)和第四個(gè)是消息的兩個(gè)參數(shù)。?
在準(zhǔn)備好MAINWINCREATE 結(jié)構(gòu)之
總結(jié)
以上是生活随笔為你收集整理的理解MiniGUI消息循环和窗口过程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2017云栖大会门票转让_2017云栖大
- 下一篇: 电路笔记(二)--稳压电路(一文读懂TL