SDK 編寫 Windows 程序需完成 3 個(gè)框架模塊:
<1> 填空賦值 WNDCLASS 定義窗口類并注冊(cè);
<2> 創(chuàng)建窗口,顯示、刷新窗口;
<3> 消息循環(huán),編寫消息處理函數(shù)。
熟悉框架后,我們的重點(diǎn)工作放在感興趣的消息處理上。
有以上分析可分模塊完成注冊(cè)窗口類和創(chuàng)建窗口工作,即分別設(shè)計(jì)功能函數(shù) InitWindowsClass 和 InitWindows ,改造后的完整程序如下,從以下程序可以更清楚的認(rèn)識(shí) windows 程序框架。
Windows 窗口應(yīng)用程序與 DOS 控制臺(tái)應(yīng)用程序是不同的,編寫一個(gè)基于 Windows API 的典型的應(yīng)用程序需要編寫處理以下四個(gè)任務(wù):
l 初始化
l 實(shí)例化
l 啟動(dòng)消息循環(huán)
l 響應(yīng)消息
前面三個(gè)任務(wù)總是發(fā)生在 WinMain 函數(shù)中, WinMain 是每一個(gè) Windows 程序的入口點(diǎn),相當(dāng) 于 C/C++ 中的 main 。第四個(gè)任務(wù)是傳統(tǒng)上稱為 WndProc 的函數(shù)來承擔(dān)的。
1 .初始化(注冊(cè)窗口類,創(chuàng)建窗口)
要定義一個(gè)窗口類 struct WNDCLASS WndClass ; 該數(shù)據(jù)結(jié)構(gòu)實(shí)際存儲(chǔ)的是一個(gè)窗口的屬性集。 調(diào)用 ATOM RegisterClass ( CONST WNDCLASSW * lpWndClass ); 函數(shù)注冊(cè) lpWndClass 指向的窗口類模板。窗口類是一個(gè)內(nèi)核對(duì)象,不同的窗口類以名稱( WNDCLASS 的 lpszClassName 字段)區(qū)分。
例如后面 MFC 中的 AfxRegisterClass 函數(shù)中,注冊(cè)窗口類前先調(diào)用 GetClassInfo 函數(shù)檢測預(yù)注冊(cè)的窗口類是否已注冊(cè)。 GetClassInfo 函數(shù)用來獲取指定名稱的窗口類信息,其原型如下:
BOOL GetClassInfo ( HINSTANCE hInstance , LPCTSTR lpClassName , LPWNDCLASSW lpWndClass );
以下為 AfxRegisterClass 函數(shù)代碼片段:
// WINCORE.CPP
BOOL AFXAPI AfxRegisterClass ( WNDCLASS * lpWndClass )
{
WNDCLASS wndcls ;
if ( GetClassInfo ( lpWndClass -> hInstance , lpWndClass -> lpszClassName , & wndcls ))
{
// class already registered
return TRUE ;
}
if (!:: RegisterClass ( lpWndClass ))
{
TRACE1 ("Can't register window class named %s/n",
lpWndClass -> lpszClassName );
return FALSE ;
}
//……
}
對(duì)于一個(gè)已注冊(cè)的窗口類,可調(diào)用 BOOL UnregisterClass ( LPCTSTR lpClassName , HINSTANCE hInstance ); 函數(shù)進(jìn)行注銷。
以下為 Win32SDK 示例程序中的注冊(cè)窗口類、創(chuàng)建窗口的代碼片段:
lpszProviderClass = __TEXT( " MyWndClass ") ;
WndClass . lpszClassName = lpszProviderClass ;
然后給定義窗口風(fēng)格以及用于該窗口類的消息處理函數(shù)。然后 CreateWindow ( lpszProviderClass ,……)將使用名稱為 lpszProviderClass 的窗口類 WndClass 作為模版創(chuàng)建類的實(shí)例。
在調(diào)用 CreateWindow(Ex) 時(shí),第一個(gè)參數(shù)也可以直接使用系統(tǒng)預(yù)定義( Predefined )的子窗口控件類,例如 "BUTTON" , "EDIT" , "COMBOBOX" 等這些是系統(tǒng)內(nèi)部已注冊(cè)的 WNDCLASS ,免去調(diào)用 RegisterClass 。
創(chuàng)建窗口后,調(diào)用 GetClassName 函數(shù)獲取指定窗口 hWnd 所使用的窗口類名稱。其函數(shù)原型如下:
int GetClassName ( HWND hWnd , LPTSTR lpClassName , int nMaxCount );
2 .實(shí)例化(顯示窗口,刷新窗口)
一旦窗口被創(chuàng)建,還需要完成若干步才能運(yùn)行你的程序。首先,當(dāng)一個(gè)窗口被創(chuàng)建時(shí),它通常是不可見的,一般要調(diào)用 BOOL ShowWindow ( HWND hWnd , int nCmdShow ); 函數(shù) 來顯示窗口,調(diào)用 BOOL UpdateWindow ( HWND hWnd ); 函數(shù)來刷新窗口。
函數(shù) UpdateWindow 向窗口 hWnd 發(fā)送第一個(gè) WM_PAINT 消息以更新它的客戶區(qū)。當(dāng) ShowWindow 使窗口顯示在屏幕上時(shí),窗口的客戶區(qū)會(huì)被 WndClass . hbrBackground 擦除,調(diào)用 UpdateWindow 函數(shù)將促使客戶區(qū)重繪,以顯示其內(nèi)容。
3 .消息循環(huán)
( 1 )消息的產(chǎn)生
無論用戶移動(dòng)鼠標(biāo)或按下某個(gè)鍵, Windows 系統(tǒng)將創(chuàng)建一個(gè)消息(對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu)為 MSG )并將之投放到對(duì)應(yīng)窗口 (MSG.hwnd 指標(biāo) ) 所在線程的消息隊(duì)列中。
( 2 )獲取消息
應(yīng)用程序調(diào)用 GetMessage 函數(shù)從調(diào)用線程消息隊(duì)列中取出 (pop) 消息。
GetMessage 函數(shù)原型如下:
BOOL GetMessage ( LPMSG lpMsg, HWND hWnd , UINT wMsgFilterMin, UINT wMsgFilterMax);
取出的消息存放至參數(shù)一 lpMsg 所指內(nèi)存(本地聲明的 MSG 結(jié)構(gòu)體變量)。
參數(shù)二 hWnd 標(biāo)識(shí)要檢查消息的窗口,如果為 NULL ,則獲取屬于調(diào)用線程消息隊(duì)列中任一(窗口)消息;如果不為 NULL ,則只獲取指定窗口的消息。
wMsgFilterMin 和 wMsgFilterMax 指定獲取消息碼的范圍。一般將兩參數(shù)都置零,表示無消息 ID 范圍限制。
注意該函數(shù)是阻塞的,直到有一個(gè)( hWnd 的)消息到來,它才返回。如果消息為 WM_QUIT ,則該函數(shù)返回 FALSE ,結(jié)束消息循環(huán);否則返回 TRUE ,繼續(xù)下一輪消息循環(huán)。
<1> 等待消息
函數(shù) BOOL WaitMessage ( VOID ); 使調(diào)用線程掛起,直到一個(gè)新的消息放到調(diào)用線程消息隊(duì)列中才返回。
<2> 取出消息
函數(shù) PeekMessage 查看調(diào)用線程消息隊(duì)列中(指定窗口 hWnd )是否有消息。如果有消息則取出放入 lpMsg 所指 MSG 結(jié)構(gòu)體中,返回 TRUE ;如果無(指定窗口 hWnd 的)消息則立即退出(即非阻塞),返回 FALSE 。其函數(shù)原型如下:
BOOL PeekMessage ( LPMSG lpMsg , HWND hWnd , UINT wMsgFilterMin , UINT wMsgFilterMax , UINT wRemoveMsg );
前四個(gè)參數(shù)同 GetMessage , wRemoveMsg 參數(shù)指定當(dāng)有消息讀取消息后,是否從消息隊(duì)列中移除該消息。其為下列值之一:
PM_NOREMOVE: 讀取后消息依然留在隊(duì)列中;
PM_REMOVE: 讀取后消息從隊(duì)列中移除;
<3> GetMessage = WaitMessage + PeekMessage ( PM_REMOVE ) 。
( 3 )翻譯消息
如果輸入列表中有一個(gè)屬于你的應(yīng)用程序的消息,并且是按鍵消息時(shí), TranslateMessage 將虛擬鍵消息轉(zhuǎn)換為字符消息( WM_KEYDOWN + WM_KEYUP = WM_CHAR 或 WM_SYSKEYDOWN + WM_SYSKEYUP = WM_SYSCHAR ),再將字符消息( WM_CHAR 或 WM_SYSCHAR )投遞到調(diào)用線程的消息隊(duì)列中。
TranslateMessage 函數(shù)內(nèi)部機(jī)制大致如下:
BOOL TranslateMessage ( CONST MSG * lpMsg )
{
if ( lpMsg -> message == WM_KEYDOWN || lpMsg -> message == WM_SYSKEYDOWN )
{
UINT message ;
if ( lpMsg -> message == WM_KEYDOWN )
{
message = WM_CHAR ;
}
else if ( lpMsg -> message == WM_SYSKEYDOWN )
{
message = WM_SYSCHAR ;
}
PostMessage ( lpMsg -> hwnd , message , lpMsg -> wParam , lpMsg -> lParam );
return TRUE ; // 消息被轉(zhuǎn)換
}
return FALSE ;
}
( 4 )路由消息
取出消息后,需要進(jìn)行處理。應(yīng)用程序調(diào)用 DispatchMessage 函數(shù) 將取得的消息發(fā)送到窗口消息處理函數(shù) WndProc 。 WndProc 采用 switch-case 分支結(jié)構(gòu)對(duì)不同的消息不同的響應(yīng)處理。
WndProc 為 WNDPROC 函數(shù)指針,其類型如下:
typedef LRESULT ( CALLBACK * WNDPROC )( HWND , UINT , WPARAM , LPARAM );
DispatchMessage 函數(shù)內(nèi)部機(jī)制大致如下:
LRESULT DispatchMessage ( CONST MSG * lpMsg )
{
// 根據(jù) lpMsg->hwnd 查找用于創(chuàng)建該窗口的 WNDCLASS ( CreateWindow 的 lpClassName 指定)的 lpfnWndProc;
CallWindowProc ( lpfnWndProc , lpMsg -> hwnd , lpMsg -> message , lpMsg -> wParam , lpMsg -> lParam );
}
4 .消息處理
Windows 編程中常見的消息有:窗口創(chuàng)建消息 WM_CREATE, 窗口繪制消息 WM_PAINT ,按鍵消息 WM_KEYDOWN, WM_CHAR, 窗口關(guān)閉消息 WM_CLOSE ,窗口銷毀消息 WM_DESTROY ,退出應(yīng)用程序消息 WM_QUIT 等。
默認(rèn)情況下,關(guān)閉窗口的處理流程如下:
點(diǎn)擊關(guān)閉按鈕 à 系統(tǒng)向指定窗口發(fā)送 WM_CLOSE 消息 à WM_CLOSE 消息響應(yīng)中調(diào)用 DestroyWindow 向窗口發(fā)送 WM_DESTROY 消息 à WM_DESTROY 消息響應(yīng)中調(diào)用 PostQuitMessage 向窗口發(fā)送 WM_QUIT 消息結(jié)束窗口所屬線程的消息循環(huán)( GetMessage 函數(shù)返回), 終止窗口所屬線程。
實(shí)際應(yīng)用軟件在響應(yīng)關(guān)閉消息時(shí)通常將窗口最小化(到托盤),并不真正關(guān)閉。應(yīng)用程序?qū)⒃跊]有窗口的條件下繼續(xù)運(yùn)行。
參考 : 窗口破 壞 過程與Windows 消息循環(huán) 、 WM_CLOSE 、WM_DESTROY 和WM_QUIT 三者的區(qū)別 。
每個(gè) Windows 程序(進(jìn)程)都至少包含一個(gè)窗口和一個(gè)應(yīng)用程序的實(shí)例,在程序中表現(xiàn)為窗口句柄 hWnd 和實(shí)例句柄 hInstance. 我們可以透過以下幾條程序語句一窺 Windows 程序運(yùn)行機(jī)制:
WndClass.hInstance= hInstance ; // 這一賦值表明要注冊(cè)的窗口類屬于當(dāng)前實(shí)例
ShowWindow( hWnd ,nCmdShow); UpdateWindow( hWnd ); // 顯示刷新該程序窗口
GetMessage // 提取消息隊(duì)列中的消息
TranslateMessage(& Msg ); // 翻譯消息,該函數(shù)負(fù)責(zé)將消息的虛擬鍵轉(zhuǎn)換成字符消息
DispatchMessage(& Msg ); // 將參數(shù) lpMSG 標(biāo)識(shí)的消息發(fā)送給窗口函數(shù) WndProc
switch( nMessage ) case: // 按接受到的消息值 nMessage 判斷是哪一種消息并處理
以下為 Windows 應(yīng)用程序執(zhí)行流程圖:
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
