1.4 消息循环和回调函数
生活随笔
收集整理的這篇文章主要介紹了
1.4 消息循环和回调函数
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
**************************************************
* 本文由小鳥飛飛整理發表 <samboy@sohu.com> *
* 首發網站:藍麗網 *
* 其他網站轉載請保留以上信息,謝謝! *
**************************************************
上 一節的程序可以運行了吧。現在介紹一下消息循環和回調函數
消息循環
通常的消息循環代碼如下:
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
? TranslateMessage(&msg);
? DispatchMessage(&msg);
}
GetMessage 函數從應用程序消息隊列中取走一條消息,該函數的原型如下:
BOOL GetMessage(
LPMSG lpMsg, ? ?? ?// address of structure with message
HWND hWnd, ? ?? ?? // handle of window
UINT wMsgFilterMin, // first message
UINT wMsgFilterMax ? // last message
);
參數lpMsg是接受消息的變量的指針。
參數hWnd指定了接收屬于哪個窗口的消息。
參 數wMsgFilterMin,wMsgFilterMax指定了接受某一范圍內的消息。
如果隊列中沒有滿足條件的消息,該函數將一 直等待,不會返回。除了WM_QUIT消息外,該函數返回非零值,對WM_QUIT消息,該函數返回零。也就是說,只有收到WM_QUIT消息,上述代碼 才能退出while循環,程序才有可能結束運行。
TranslateMessage函數對取道的消息進行轉換。用戶按動一下某個鍵, 系統將發出WM_KEYDOWN,WM_KEYUP,并且參數中提供的是該鍵的虛擬掃描碼。但有時用戶按動一下某個鍵,我們想得到一條表示用戶輸入了某個 字符的消息,并在消息補充參數中提供字符的編碼。TranslateMessage能將可行的WM_KEYDOWN, WM_KEYUP消息對轉換成一條WM_CHAR消息,將可行的WM_SYSKEYDOWN, or WM_SYSKEYUP消息對轉換成一條WM_SYSCHAR消息,并將轉換后得到的新消息投遞到程序的消息隊列中。轉換過程不會影響原來的消息,只在消 息隊列中增加新消息。
DispatchMessage函數將取道的消息傳遞到窗口的回調函數中去處理。可以理解成該函數通知操作系 統,讓操作系統去調用窗口的回調函數來處理收到的消息。
所有Windows程序的消息處理代碼基本上都是相同的,如沒特殊需要,可以 照搬照抄上述代碼。
順便提示:從隊列中取消息還有PeekMessage函數,PeekMessage函數有兩種取消息的方式,第一 種與GetMessage一樣,從隊列中直接取走消息,第二種是只取消息的一個副本,并不將消息從隊列中取走。無論哪種方式,PeekMessage都不 會因隊列中沒有滿足條件的消息而阻塞,當取到滿足條件的消息,該函數返回非零值反之,返回零。向隊列中發送消息有PostMessage和 SendMessage,PostMessage函數發送消息后立即返回,而SendMessage需等到發送的消息被處理完后才能返回。還有一個 PostThreadMessage用于向線程發送消息,關于線程,請在以后的章節再學,但我們也因此想到消息不一定總是與窗口相關的,也就是說,對于某 些消息,其對應的MSG結構中的hwnd可以為NULL。
回調函數
回調函數的原型為:
LRESULT CALLBACK WindowProc(
HWND hwnd, ? ?// handle to window
UINT uMsg, ? ?// message identifier
WPARAM wParam, // first message parameter
LPARAM lParam ? // second message parameter
);
我們可以將函數名WindowProc改 為我們喜歡的名稱,如MyProc,該函數的四個參數對應消息的窗口句柄,消息碼,消息碼的兩個補充參數。
在該函數內部是一個龐大的 switch語句,用于對各種感興趣的消息進行處理。我們分析程序中的代碼。
LRESULT CALLBACK MyProc( HWND hWnd,UINT Msg,WPARAM wParam, LPARAM lParam)
{
HDC hDC;
switch(Msg)
{
case WM_CHAR:
char str[20];
sprintf(str,"the char code is %d",wParam);
MessageBox(hWnd,str,"Message",MB_OKCANCEL);
break;
case WM_LBUTTONDOWN:
MessageBox(hWnd,"mouse click","Message",MB_OK);
hDC=GetDC(hWnd);
TextOut(hDC,LOWORD(lParam),HIWORD(lParam),
? ?"Mouse Click",strlen("Mouse Click"));
ReleaseDC(hWnd,hDC);
break;
case WM_CLOSE:
if(IDOK==MessageBox(NULL,"真的要要退出嗎?",
"Message",MB_OKCANCEL|MB_ICONQUESTION))
{
DestroyWindow(hWnd);
}
break;
case WM_PAINT:
PAINTSTRUCT ps;
hDC=BeginPaint(hWnd,&ps);//在 WM_PAINT里必須用這個函數
TextOut(hDC,0,0,"Output message",strlen("Output message"));
EndPaint(hWnd,&ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return(DefWindowProc(hWnd,Msg, wParam,lParam ));
}
return (0);
}
當用戶在窗口上按下一個字符鍵,程序將得 到一條WM_CHAR消息,參看msdn,在其wParam參數中含有字符的碼值。使用程序中的代碼,我們可以隨時獲得某個字符的碼值,不用為此去專門查 找書籍。MessageBox函數可以彈出一個顯示信息的對話框,如果我們按下BackSpace鍵,在這里將彈出"the char code is 8"消息。
當用戶在窗口上按下鼠標左按鈕時,程序彈出一條消息框對收到的WM_LBUTTONDOWN進行響應,以證明按下鼠標左按 鈕動作與WM_LBUTTONDOWN消息的這種對應關系,另外程序還將在窗口上鼠標所按下的位置寫上一串文字。當我們要在窗口上寫字、繪圖,并不直接在 這些設備上操作,而是在一個稱為設備描述表(Device Contexts,簡稱DC)的資源上操作的。使用DC,程序不用為圖形的顯示與打印輸出分別單獨處理了。無論是打印還是顯示,我們都是在DC上操作,然 后由DC映射到這些設備上。使用DC,我們不用擔心程序在不同的硬件上執行的差異性,DC能夠為我們裝載合適的設備驅動程序,實現程序的圖形操作與底層設 備無關。
GetDC函數能夠返回同窗口相關連的DC的句柄。
TextOut函數在當前DC上指定的位置輸出一個 字符串,當按下鼠標左按鈕,消息WM_LBUTTONDOWN的補充參數lParam中含有鼠標位置的x,y坐標,其中的低16位含有x坐標,高16位含 有y坐標,可以分別通過LOWORD,HIWORD宏獲得。
對于每次成功調用GetDC返回的DC句柄,在執行完所有的圖形操作后, 必須調用ReleaseDC釋放該句柄所占用的資源。否則,會造成內存泄露。我曾經幫助一個叫王健的學員調試他的On-job項目程序,他發現他的程序所 占用的內存總是以4k的增量向上增長,每運行幾小時后,程序便因內存不足而崩潰了。后來發現就是因為程序在定時器中反復使用GetDC而沒有使用 ReleaseDC釋放造成的。讀者可以將例子程序中的ReleaseDC一句注釋掉,編譯后運行,在NT4.0/win2000下啟動任務管理器,切換 到進程標簽,查看你的程序所使用的內存量,在程序中不斷點擊鼠標左按鈕,程序將不斷調用GetDC函數,你將發現你的程序占用的內存量不斷向上增長,我們 通常使用這樣的方式來檢測程序的內存泄露的。
當用戶點擊窗口上的關閉按鈕時,系統將給應用程序發送一條WM_CLOSE消息,如果程 序不做進一步處理,窗口是不會關閉的。我們在程序中利用一個選擇對話框,如果用戶確認真的要退出,程序調用DestroyWindow函數將窗口關閉。
窗口關閉后,系統將給應用程序發送一條WM_DESTROY消息,需要注意的是,主窗口的關閉,不代表應用程序結束,在WinMain函數中的消息循環 代碼中,GetMessage函數必須取到一條WM_QUIT,消息循環才能結束。要讓程序正常退出,必須在WM_DESTROY消息響應代碼中,調用 PostQuitMessage函數向程序的消息隊列中發送一條WM_QUIT消息,PostQuitMessage函數的參數值傳遞給WM_QUIT消 息的wParam參數,通常用作WinMain函數的返回值。
當窗口第一次產生,移動,改變大小,從其他窗口后面切換到前面等情況都 會導致窗口的重畫。重畫時將使用設計窗口類時指定的刷子粉刷窗口的背景,窗口上原有的文字和圖形都將被擦除掉。要想讓圖形和文字總顯示在窗口的表面,只能 是在這些圖形和文字被擦除后,立即又將它們畫上去。這個過程對用戶來說,是感覺不到的,他們只能感覺到這些圖形和文字永遠都和窗口一并存在。當系統粉刷完 窗口的背景后,都會發送一條WM_PAINT消息,以便通知應用程序原有的圖形和文字已被擦除,如果還想保留哪些圖形和文字,請在此處加入處理代碼。也就 是說,我們在WM_PAINT消息響應中作出的圖形和文字是“永遠”存在的。對于WM_PAINT消息響應代碼中要獲得窗口的DC,只能使用 BeginPaint函數,除此之外的消息響應代碼中必須用GetDC獲得窗口的DC,BeginPaint獲得的DC最后必須用EndPaint釋放。
提醒:水平或垂直改變窗口的大小時,窗口是否重畫,取決于WNDCLASS結構中style成員的設置中是否包含CS_VREDRAW與 CS_HREDRAW。
DefWindowProc函數提供了對所有消息的缺省處理方式,對于大多數不想特殊處理的消息,程序都可以 調用這個函數來處理,所以程序在switch的default語句中調用此函數進行處理。
* 本文由小鳥飛飛整理發表 <samboy@sohu.com> *
* 首發網站:藍麗網 *
* 其他網站轉載請保留以上信息,謝謝! *
**************************************************
上 一節的程序可以運行了吧。現在介紹一下消息循環和回調函數
消息循環
通常的消息循環代碼如下:
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
? TranslateMessage(&msg);
? DispatchMessage(&msg);
}
GetMessage 函數從應用程序消息隊列中取走一條消息,該函數的原型如下:
BOOL GetMessage(
LPMSG lpMsg, ? ?? ?// address of structure with message
HWND hWnd, ? ?? ?? // handle of window
UINT wMsgFilterMin, // first message
UINT wMsgFilterMax ? // last message
);
參數lpMsg是接受消息的變量的指針。
參數hWnd指定了接收屬于哪個窗口的消息。
參 數wMsgFilterMin,wMsgFilterMax指定了接受某一范圍內的消息。
如果隊列中沒有滿足條件的消息,該函數將一 直等待,不會返回。除了WM_QUIT消息外,該函數返回非零值,對WM_QUIT消息,該函數返回零。也就是說,只有收到WM_QUIT消息,上述代碼 才能退出while循環,程序才有可能結束運行。
TranslateMessage函數對取道的消息進行轉換。用戶按動一下某個鍵, 系統將發出WM_KEYDOWN,WM_KEYUP,并且參數中提供的是該鍵的虛擬掃描碼。但有時用戶按動一下某個鍵,我們想得到一條表示用戶輸入了某個 字符的消息,并在消息補充參數中提供字符的編碼。TranslateMessage能將可行的WM_KEYDOWN, WM_KEYUP消息對轉換成一條WM_CHAR消息,將可行的WM_SYSKEYDOWN, or WM_SYSKEYUP消息對轉換成一條WM_SYSCHAR消息,并將轉換后得到的新消息投遞到程序的消息隊列中。轉換過程不會影響原來的消息,只在消 息隊列中增加新消息。
DispatchMessage函數將取道的消息傳遞到窗口的回調函數中去處理。可以理解成該函數通知操作系 統,讓操作系統去調用窗口的回調函數來處理收到的消息。
所有Windows程序的消息處理代碼基本上都是相同的,如沒特殊需要,可以 照搬照抄上述代碼。
順便提示:從隊列中取消息還有PeekMessage函數,PeekMessage函數有兩種取消息的方式,第一 種與GetMessage一樣,從隊列中直接取走消息,第二種是只取消息的一個副本,并不將消息從隊列中取走。無論哪種方式,PeekMessage都不 會因隊列中沒有滿足條件的消息而阻塞,當取到滿足條件的消息,該函數返回非零值反之,返回零。向隊列中發送消息有PostMessage和 SendMessage,PostMessage函數發送消息后立即返回,而SendMessage需等到發送的消息被處理完后才能返回。還有一個 PostThreadMessage用于向線程發送消息,關于線程,請在以后的章節再學,但我們也因此想到消息不一定總是與窗口相關的,也就是說,對于某 些消息,其對應的MSG結構中的hwnd可以為NULL。
回調函數
回調函數的原型為:
LRESULT CALLBACK WindowProc(
HWND hwnd, ? ?// handle to window
UINT uMsg, ? ?// message identifier
WPARAM wParam, // first message parameter
LPARAM lParam ? // second message parameter
);
我們可以將函數名WindowProc改 為我們喜歡的名稱,如MyProc,該函數的四個參數對應消息的窗口句柄,消息碼,消息碼的兩個補充參數。
在該函數內部是一個龐大的 switch語句,用于對各種感興趣的消息進行處理。我們分析程序中的代碼。
LRESULT CALLBACK MyProc( HWND hWnd,UINT Msg,WPARAM wParam, LPARAM lParam)
{
HDC hDC;
switch(Msg)
{
case WM_CHAR:
char str[20];
sprintf(str,"the char code is %d",wParam);
MessageBox(hWnd,str,"Message",MB_OKCANCEL);
break;
case WM_LBUTTONDOWN:
MessageBox(hWnd,"mouse click","Message",MB_OK);
hDC=GetDC(hWnd);
TextOut(hDC,LOWORD(lParam),HIWORD(lParam),
? ?"Mouse Click",strlen("Mouse Click"));
ReleaseDC(hWnd,hDC);
break;
case WM_CLOSE:
if(IDOK==MessageBox(NULL,"真的要要退出嗎?",
"Message",MB_OKCANCEL|MB_ICONQUESTION))
{
DestroyWindow(hWnd);
}
break;
case WM_PAINT:
PAINTSTRUCT ps;
hDC=BeginPaint(hWnd,&ps);//在 WM_PAINT里必須用這個函數
TextOut(hDC,0,0,"Output message",strlen("Output message"));
EndPaint(hWnd,&ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return(DefWindowProc(hWnd,Msg, wParam,lParam ));
}
return (0);
}
當用戶在窗口上按下一個字符鍵,程序將得 到一條WM_CHAR消息,參看msdn,在其wParam參數中含有字符的碼值。使用程序中的代碼,我們可以隨時獲得某個字符的碼值,不用為此去專門查 找書籍。MessageBox函數可以彈出一個顯示信息的對話框,如果我們按下BackSpace鍵,在這里將彈出"the char code is 8"消息。
當用戶在窗口上按下鼠標左按鈕時,程序彈出一條消息框對收到的WM_LBUTTONDOWN進行響應,以證明按下鼠標左按 鈕動作與WM_LBUTTONDOWN消息的這種對應關系,另外程序還將在窗口上鼠標所按下的位置寫上一串文字。當我們要在窗口上寫字、繪圖,并不直接在 這些設備上操作,而是在一個稱為設備描述表(Device Contexts,簡稱DC)的資源上操作的。使用DC,程序不用為圖形的顯示與打印輸出分別單獨處理了。無論是打印還是顯示,我們都是在DC上操作,然 后由DC映射到這些設備上。使用DC,我們不用擔心程序在不同的硬件上執行的差異性,DC能夠為我們裝載合適的設備驅動程序,實現程序的圖形操作與底層設 備無關。
GetDC函數能夠返回同窗口相關連的DC的句柄。
TextOut函數在當前DC上指定的位置輸出一個 字符串,當按下鼠標左按鈕,消息WM_LBUTTONDOWN的補充參數lParam中含有鼠標位置的x,y坐標,其中的低16位含有x坐標,高16位含 有y坐標,可以分別通過LOWORD,HIWORD宏獲得。
對于每次成功調用GetDC返回的DC句柄,在執行完所有的圖形操作后, 必須調用ReleaseDC釋放該句柄所占用的資源。否則,會造成內存泄露。我曾經幫助一個叫王健的學員調試他的On-job項目程序,他發現他的程序所 占用的內存總是以4k的增量向上增長,每運行幾小時后,程序便因內存不足而崩潰了。后來發現就是因為程序在定時器中反復使用GetDC而沒有使用 ReleaseDC釋放造成的。讀者可以將例子程序中的ReleaseDC一句注釋掉,編譯后運行,在NT4.0/win2000下啟動任務管理器,切換 到進程標簽,查看你的程序所使用的內存量,在程序中不斷點擊鼠標左按鈕,程序將不斷調用GetDC函數,你將發現你的程序占用的內存量不斷向上增長,我們 通常使用這樣的方式來檢測程序的內存泄露的。
當用戶點擊窗口上的關閉按鈕時,系統將給應用程序發送一條WM_CLOSE消息,如果程 序不做進一步處理,窗口是不會關閉的。我們在程序中利用一個選擇對話框,如果用戶確認真的要退出,程序調用DestroyWindow函數將窗口關閉。
窗口關閉后,系統將給應用程序發送一條WM_DESTROY消息,需要注意的是,主窗口的關閉,不代表應用程序結束,在WinMain函數中的消息循環 代碼中,GetMessage函數必須取到一條WM_QUIT,消息循環才能結束。要讓程序正常退出,必須在WM_DESTROY消息響應代碼中,調用 PostQuitMessage函數向程序的消息隊列中發送一條WM_QUIT消息,PostQuitMessage函數的參數值傳遞給WM_QUIT消 息的wParam參數,通常用作WinMain函數的返回值。
當窗口第一次產生,移動,改變大小,從其他窗口后面切換到前面等情況都 會導致窗口的重畫。重畫時將使用設計窗口類時指定的刷子粉刷窗口的背景,窗口上原有的文字和圖形都將被擦除掉。要想讓圖形和文字總顯示在窗口的表面,只能 是在這些圖形和文字被擦除后,立即又將它們畫上去。這個過程對用戶來說,是感覺不到的,他們只能感覺到這些圖形和文字永遠都和窗口一并存在。當系統粉刷完 窗口的背景后,都會發送一條WM_PAINT消息,以便通知應用程序原有的圖形和文字已被擦除,如果還想保留哪些圖形和文字,請在此處加入處理代碼。也就 是說,我們在WM_PAINT消息響應中作出的圖形和文字是“永遠”存在的。對于WM_PAINT消息響應代碼中要獲得窗口的DC,只能使用 BeginPaint函數,除此之外的消息響應代碼中必須用GetDC獲得窗口的DC,BeginPaint獲得的DC最后必須用EndPaint釋放。
提醒:水平或垂直改變窗口的大小時,窗口是否重畫,取決于WNDCLASS結構中style成員的設置中是否包含CS_VREDRAW與 CS_HREDRAW。
DefWindowProc函數提供了對所有消息的缺省處理方式,對于大多數不想特殊處理的消息,程序都可以 調用這個函數來處理,所以程序在switch的default語句中調用此函數進行處理。
總結
以上是生活随笔為你收集整理的1.4 消息循环和回调函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1.2句柄及 WinMain函数
- 下一篇: 1.5 MFC封装思想