在本課中,我們將要學習WINDOWS程序是如何處理鍵盤消息的。
理論:
因為大多數的PC只有一個鍵盤,所以所有運行中的WINDOWS程序必須共用它。WINDOWS 將負責把擊鍵消息送到具有輸入焦點的那個應用程序中去。盡管屏幕上可能同時有幾個應用程序窗口,但一個時刻僅有一個窗口有輸入焦點。有輸入焦點的那個應用程序的標題條總是高亮度顯示的。 實際上您可以從兩個角度來看鍵盤消息:一是您可以把它看成是一大堆的按鍵消息的集合,在這種情況下,當您按下一個鍵時,WINDOWS就會發送一個WM_KEYDOWN給有輸入焦點的那個應用程序,提醒它有一個鍵被按下。當您釋放鍵時,WINDOWS又會發送一個WM_KYEUP消息,告訴有一個鍵被釋放。您把每一個鍵當成是一個按鈕;另一種情況是:您可以把鍵盤看成是字符輸入設備。當您按下“a”鍵時,WINDOWS發送一個WM_CHAR消息給有輸入焦點的應用程序,告訴它“a”鍵被按下。實際上WINDOWS 內部發送WM_KEYDOWN和WWM_KEYUP消息給有輸入焦點的應用程序,而這些消息將通過調用TranslateMessage翻譯成WM_CHAR消息。WINDOWS窗口過程函數將決定是否處理所收到的消息,一般說來您不大會去處理WM_KEYDOWN、WM_KEYUP消息,在消息循環中TranslateMessage函數會把上述消息轉換成WM_CHAR消息。在我們的課程中將只處理WM_CHAR。
例子:
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
char WPARAM 20h ; the character the program receives from keyboard
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke TextOut,hdc,0,0,ADDR char,1
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
分析:
char WPARAM 20h ; the character the program receives from keyboard
這個變量將保存從鍵盤接收到的字符。因為它是在窗口過程中通過WPARAM型變量傳送的,所以我們簡單地把它定義為WPARAM型。由於我們的窗口在初次刷新時(也即剛被創建的那一次)是沒有鍵盤輸入的所以我們把他設成空格符(20h),這樣顯示時您就什麼都看不見。
.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
這一段是用來處理WM_CHAR消息的。它把接收到的字符放入變量char中,接著調用InvalidateRect,而InvalidateRect使得窗口的客戶區無效,這樣它會發出WM_PAINT消息,而WM_PAINT消息迫使WINDOWS重新繪制它的客戶區。該函數的語法如下:
InvalidateRect proto hWnd:HWND,\
lpRect:DWORD,\
bErase:DWORD
lpRect是指向客戶區我們想要其無效的一個正方形結構體的指針。如果該值等於NULL,則整個客戶區都無效;布爾值bErase告訴WINDOWS是否擦除背景,如果是TRUE,則WINDOWS在調用BeginPaint函數時把背景擦掉。 所以我們此處的做法是:我們將保存所有有關重繪客戶區的數據,然後發送WM_PAINT消息,處理該消息的程序段然後根據相關數據重新繪制客戶區。盡管這麼做事有點像走了弓背,但WINDOWS要處理那麼龐大的消息群,沒有一定的規矩可不行。實際上我們完全可以通過調用GetDC 獲得設備上下文句柄,然後繪制字符,然後再調用ReleaseDC釋放設備上下文句柄,毫無疑問這樣也能在客戶區繪制出正確的字符。但是如果這之後接收到WM_PAINT消息要處理時,客戶區會重新刷新,而我們這稍前所繪制的字符就會消失掉。所以為了讓字符一直正確地顯示,就必須把它們放到WM_PAINT的處理過程中處理。而在本消息處理中發送WM_PAINT消息即可。
invoke TextOut,hdc,0,0,ADDR char,1
在調用InvalidateRect時,WM_PAINT消息被發送到了WINDOWS窗口處理過程,程序流程轉移到處理WM_PAINT消息的程序段,然後調用BeginPaint得到設備上下文的句柄,再調用TextOut在客戶區的(0,0)處輸出保存的按鍵字符。這樣無論您按什麼鍵都能在客戶區的左上角顯示,不僅如此,無論您怎麼縮放窗口(迫使WINDOWS重新繪制它的客戶區),字符都會在正確的地方顯示,所以必須把所有重要的繪制動作都放到處理WM_PAINT消息的程序段中去。