本課中,我們將學習如何在窗口的客戶區“繪制”字符串。我們還將學習關於“設備環境”的概念。
理論:
Windows 中的文本是一個GUI(圖形用戶界面)對象。每一個字符實際上是由許多的像素點組成,這些點在有筆畫的地方顯示出來,這樣就會出現字符。這也是為什麼我說“繪制”字符,而不是寫字符。通常您都是在您應用程序的客戶區“繪制”字符串(盡管您也可以在客戶區外“繪制”)。Windows 下的“繪制”字符串方法和 Dos 下的截然不同,在 Dos 下,您可以把屏幕想象成 85 x 25 的一個平面,而 Windows 下由於屏幕上同時有幾個應用程序的畫面,所以您必須嚴格遵從規范。Windows 通過把每一個應用程序限制在他的客戶區來做到這一點。當然客戶區的大小是可變的,您隨時可以調整。
在您在客戶區“繪制”字符串前,您必須從 Windows 那裡得到您客戶區的大小,確實您無法像在 DOS 下那樣隨心所欲地在屏幕上任何地方“繪制”,繪制前您必須得到 Windows 的允許,然後 Windows 會告訴您客戶區的大小,字體,顏色和其它 GUI 對象的屬性。您可以用這些來在客戶區“繪制”。
什麼是“設備環境”(DC)呢? 它其實是由 Windows 內部維護的一個數據結構。一個“設備環境”和一個特定的設備相連。像打印機和顯示器。對於顯示器來說,“設備環境”和一個個特定的窗口相連。
“設備環境”中的有些屬性和繪圖有關,像:顏色,字體等。您可以隨時改動那些缺省值,之所以保存缺省值是為了方便。您可以把“設備環境”想象成是Windows 為您准備的一個繪圖環境,而您可以隨時根據需要改變某些缺省屬性。
當應用程序需要繪制時,您必須得到一個“設備環境”的句柄。通常有幾種方法。
在 WM_PAINT 消息中使用 call BeginPaint
在其他消息中使用 call GetDC
call CreateDC 建立你自己的 DC
您必須牢記的是,在處理單個消息後你必須釋放“設備環境”句柄。不要在一個消息處理中獲得 “設備環境”句柄,而在另一個消息處理中在釋放它。
我們在Windows 發送 WM_PAINT 消息時處理繪制客戶區,Windows 不會保存客戶區的內容,它用的是方法是“重繪”機制(譬如當客戶區剛被另一個應用程序的客戶區覆蓋),Windows 會把 WM_PAINT 消息放入該應用程序的消息隊列。重繪窗口的客戶區是各個窗口自己的責任,您要做的是在窗口過程處理 WM_PAINT 的部分知道繪制什麼和何如繪制。
您必須了解的另一個概念是“無效區域”。Windows 把一個最小的需要重繪的正方形區域叫做“無效區域”。當 Windows 發現了一個”無效區域“後,它就會向該應用程序發送一個 WM_PAINT 消息,在 WM_PAINT 的處理過程中,窗口首先得到一個有關繪圖的結構體,裡面包括無效區的坐標位置等。您可以通過調用BeginPaint 讓“無效區”有效,如果您不處理 WM_PAINT 消息,至少要調用缺省的窗口處理函數 DefWindowProc ,或者調用 ValidateRect 讓“無效區”有效。否則您的應用程序將會收到無窮無盡的 WM_PAINT 消息。
下面是響應該消息的步驟:
取得“設備環境”句柄
繪制客戶區
釋放“設備環境”句柄
注意,您無須顯式地讓“無效區”有效,這個動作由 BeginPaint 自動完成。您可以在 BeginPaint 和 Endpaint 之間,調用所有的繪制函數。幾乎所有的GDI 函數都需要“設備環境”的句柄作為參數。
內容:
我們將寫一個應用程序,它會在客戶區的中心顯示一行 "Win32 assembly is great and easy!"
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
OurText db "Win32 assembly is great and easy!",0
.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
LOCAL rect:RECT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax, eax
ret
WndProc endp
end start
分析:
這裡的大多數代碼和第三課中的一樣。我只解釋其中一些不相同的地方。
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT
這些局部變量由處理 WM_PAINT 消息中的 GDI 函數調用。hdc 用來存放調用 BeginPaint 返回的“設備環境”句柄。ps 是一個 PAINTSTRUCT 數據類型的變量。通常您不會用到其中的許多值,它由 Windows 傳遞給 BeginPaint,在結束繪制後再原封不動的傳遞給 EndPaint。rect 是一個 RECT 結構體類型參數,它的定義如下:
RECT Struct left LONG ?
top LONG ?
right LONG ?
bottom LONG ?
RECT ends
left 和 top 是正方形左上角的坐標。right 和 bottom 是正方形右下角的坐標。客戶區的左上角的坐標是 x=0,y=0,這樣對於 x=0,y=10 的坐標點就在它的下面。
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps
在處理 WM_PAINT 消息時,您調用BeginPaint函數,傳給它一個窗口句柄和未初始化的 PAINTSTRUCT 型參數。調用成功後在 eax 中返回“設備環境”的句柄。下一次,調用 GetClientRect 以得到客戶區的大小,大小放在 rect 中,然後把它傳給 DrawText。DrawText 的語法如下:
DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD
DrawText是一個高層的調用函數。它能自動處理像換行、把文本放到客戶區中間等這些雜事。所以您只管集中精力“繪制”字符串就可以了。我們會在下一課中講解低一層的函數 TextOut,該函數在一個正方形區域中格式化一個文本串。它用當前選擇的字體、顏色和背景色。它處理換行以適應正方形區域。它會返回以設備邏輯單位度量的文本的高度,我們這裡的度量單位是像素點。讓我們來看一看該函數的參數:
hdc: “設備環境”的句柄。
lpString:要顯示的文本串,該文本串要麼以NULL結尾,要麼在nCount中指出它的長短。
nCount:要輸出的文本的長度。若以NULL結尾,該參數必須是-1。
lpRect: 指向要輸出文本串的正方形區域的指針,該方形必須是一個裁剪區,也就是說超過該區域的字符將不能顯示。
uFormat:指定如何顯示。我們可以用 or 把以下標志或到一塊:
DT_SINGLELINE:是否單行顯示。
DT_CENTER:是否水平居中。
DT_VCENTER :是否垂直居中。
結束繪制後,必須調用 EndPaint 釋放“設備環境”的句柄。 好了,現在我們把“繪制”文本串的要點總結如下:
必須在開始和結束處分別調用 BeginPaint 和 EndPaint;
在 BeginPaint 和 EndPaint 之間調用所有的繪制函數;
如果在其它的消息處理中重新繪制客戶區,您可以有兩種選擇:
(1)用GetDC和ReleaseDC代替BeginPaint和EndPaint;
(2)調用InvalidateRect或UpdateWindow讓客戶區無效,這將迫使WINDOWS把WM_PAINT放入應用程序消息隊列,從而使得客戶區重繪。