Trao đổi với tôi

http://www.buidao.com

6/25/09

[Iczelion's Tuts] TUTORIAL 4: PAINTING WITH TEXT

TUTORIAL 4: PAINTING WITH TEXT

Trong tut này, chúng ta sẽ học paint (vẽ) một text như thế nào trong vùng client của một cửa sổ . Chúng ta sẽ học về device context (ngữ cảnh thiết bị)

Bạn có thể download source code ở đây:


LÝ THUYẾT

Text trong windows là một lọai đối tượng GUI (Graphical User Interface :giao diện người dùng đồ họa). Mỗi ký tự được sọan ra bằng nhiều pixels (dots) mà chúng được gộp lại với nhau thành một pattern (mẫu) riêng. Vì vậy tại sao nó được gọi là “painting” chứ ko phải là “writing”. Thông thường bạn paint (vẽ) một text trong vùng client của bạn ( thực tế, bạn có thể paint bên ngòai vùng client, nhưng đó là đề tài khác). Đặt text trên màn hình trong Windows khác hòan tòan trong Dos. Trong Dos, bạn có thể tưởng tượng màn hình có kích thước 80x25. Nhưng trong Windows, màn hình được chia sẽ cho những chương trình khác nhau. Vài rules (qui tắc) bắt buộc phải tuân thủ để tránh tình trạng chương trình này viết đè lên màn hình của chương trình khác .Windows đảm bảo điều này bằng cách giới hạn vùng painting của mỗi window là vùng client của chính nó (vùng client của một window là phần của cửa sổ ứng dụng trừ thanh tiêu đề, đường viền, menu, thanh công cụ , thanh trạng thái và thanh cuộn). Kích thước vùng Client của một window cũng không phải là hằng số. Người dùng có thể thay đổi kích thước bất kỳ khi nào muốn. Vì vậy bạn có thể xác định kích thước của chính vùng client của bạn một cách linh động.

Trước khi bạn có thể vẽ cái gì đó trên vùng client, bạn phải hỏi Windows có chấp thuận ko. Bạn ko thể điều khiển tuyệt đối của màn hình như bạn làm việc trong môi trường Dos. Bạn phải hỏi Windows có chấp nhận cho quyền bạn vẽ vào vùng Client của bạn ko. Windows sẽ xác định kích thước của vùng Client của bạn , font, colors và các thuộc tính GDI (Graphics Device Interface: Giao diện thiết bị đồ họa) khác, và gởi về một handle (thẻ quản) của device context cho chương trình của bạn. Bạn có thể dùng device context như một giấy thông hành để vẽ trên vùng client.

Vậy một device context là gì?. Nó chỉ là một cấu trúc dữ liệu được duy trì bên trong Windows . Một DC (device context) được liên kết với một thiết bị đặc biệt như là máy in, màn hình hiển thị. Ví dụ trong một màn hình hiển thị, một DC thường được liên kết với một cửa sổ đặc biệt trong màn hình.

Vài giá trị trong một DC là các thuộc tính đồ họa, ví dụ như : colors, font…vân vân. Chúng là những giá trị mặc định mà bạn có thể thay đổi tùy lúc. Chúng tồn tại để trợ giúp giảm bớt việc load mọi hàm GDI mà thuộc tính của chúng phải được chỉ định (tức là giảm bớt việc tải các tham số của hàm GDI).

Bạn có thể nghĩ rằng một DC giống như một môi trường mặc định được chuẩn bị sẳn cho bạn bởi Windows. Bạn có thể xài vài setting (thiết lập) mặc định nếu bạn muốn.

Khi một chương trình cần paint , nó phải nhận được một handle của một DC. Thông thường có nhiều cách khác nhau để làm chuyện này:

call BeginPaint đáp trả thông điệp WM_PAINT message.
call GetDC đáp trả các messages khác.
call CreateDC cài đặt một device context cho chính bạn

Một điều mà bạn phải nhớ là, sau khi bạn có device context handle , bạn phải phát hành” (release ) nó trong suốt tiến trình của một message đơn độc. Đừng nhận handle cho việc đáp trả một message này mà lại đi “phát hành”cho việc đáp trả cho một message khác.

Windows post thông điệp WM_PAINT đến một cửa sổ window để thông báo rằng ngay lúc này nó sẽ vẽ lại vùng client của window. Windows ko lưu nội dung của vùng client của một cửa số. Giả như, xảy ra hòan cảnh cho phép chấp thuận vẽ lại một vùng client (Như trường hợp một window được che phủ bởi những window khác và nó phải hiện lênh ko bị che phủ nữa), Windows sẽ put thông điệp WM_PAINT trong chuổi thông điệp của window. Nó chịu trách nhiệm vẽ lại chính vùng client của window. Bạn phải tập hợp tất cả các thông tin vẽ lại như thế nào trong vùng client trong section WM_PAINT của thủ tục window của bạn, vì vậy thủ tục window có thể vẽ lại vùng client khi message WM_PAINT xảy đến.

Khái niệm khác mà bạn phải tìm hiểu đến là điều khỏan hình chử nhật không hợp lệ (invalid rectangle). Windows định nghĩa một hình chử nhật không hợp lệ là một vùng hình chử nhật nhỏ nhất trong vùng client cần được vẽ lại. Khi Windows dò tìm ra một hình chử nhật không hợp lệ trong vùng client của một window , nó sẽ post thông điệp WM_PAINT đến window đó.Trong việc đáp trả lại thông điệp WM_PAINT , window có thể thu được một cấu trúc paintstruct đang chứa đựng nó, trong số những thứ thu được, có tọa độ của hình chử nhật không hợp lệ. Bạn gọi hàm BeginPaint trong việc đáp trả thông điệp WM_PAINT để “xác nhận” (validate, hay còn gọi là hợp lệ hóa) hình chử nhật không hợp lệ (invalid rectangle). Nếu bạn ko tiến hành như thế qua thông điệp WM_PAINT, thì ít nhất, bạn cũng phải gọi hàm DefWindowProc hay ValidateRect để “xác nhận” hình chử nhật ko hợp lệ (tức là cho nó hợp lệ), nếu khác Windows sẽ gởi cho bạn thông điệp WM_PAINT lặp đi lặp lại nhiều lần.

Dưới đây là từng bước bạn phải thực hiện trong việc đáp trả thông điệp WM_PAINT :

Lấy một handle của device context bằng hàm BeginPaint.
Paint vùng client.
Release (phát hành) handle của device context bằng hàm EndPaint

Chú ý rằng , bạn ko phải làm hợp lệ (xác nhận) hình chử nhật ko hợp lệ một cách dứt điểm ngay tức khắc. Nó được làm một cách tự động bởi hàm BeginPaint. Giữa cặp BeginPaint-EndPaint , bạn có thể gọi bất kỳ hàm GDI để paint vùng client của bạn. Gần như tất cả chúng đều cần handle của device context như một tham số.

Ghi chú thêm của người dịch:

Có thể các bạn mới học về lập trình Windows khó hiểu những gì đã nói ở trên, nên tôi sẽ ghi chú thêm để các bạn có cái nhìn rõ ràng hơn.

-Ví dụ: Một chương trình khác đè lên màn hình của ta , khi nó di chuyển dời đi ,Windows sẽ thông báo vẽ lại màn hình của ta bằng cách post thông điệp WM_PAINT đến thủ tục window của ta.

-Thủ tục window thường chỉ cập nhật vẽ lại một vùng hình chử nhật nhỏ (không phải tòan vùng client) , ví dụ rõ ràng nhất chính là một hộp thọai nằm lên một phần của vùng client. Việc vẽ lại (repaint) chỉ yêu cầu đối với vùng hình chử nhật bị hộp thọai che phủ phải được giải che khi hộp thọai thu hồi.Vùng đó được gọi là hình chử nhật ko hợp lệ.

-Và bạn nên có khái niệm này: khi tồn tại một vùng hình chử nhật ko hợp lệ , thì Windows sẽ post thông điệp WM_PAINT đến thủ tục window của ta.

-Windows duy trì một cấu trúc thông tin paint (paintstruct) đối với mỗi cửa sổ. Cấu trúc này chứa nhiều thông tin , trong đó có tọa độ của hình chử nhật ko hợp lệ.

-Thủ tục window có thể bất hợp lệ hóa một hình chử nhật ko hợp lệ để buộc Windows xuất thông điệp WM_PAINT bằng hàm InvalidateRect.

-Sau khi thủ tục window gọi hàm BeginPaint trong thông điệp WM_PAINT, tòan bộ vùng client sẽ được hợp lệ hóa. Một chương trình cũng có thể hợp lệ (hay còn gọi là “xác nhận” (validate)) bất kỳ vùng hình chử nhật nào bên trong vùng client bằng cách gọi hàm ValidateRect. Nếu gọi hàm này có hiệu ứng hợp lệ hóa tòan bộ vùng ko hợp lệ, thông điệp WM_PAINT nếu có nằm trong hàng đợi thông điệp đều bị thu hồi.

-Bạn muốn vẽ trong vùng client, bạn sử dụng các hàm GDI. Khi dùng hàm GDI, ta phải cần có một handle Device Context (DC, ngữ cảnh thiết bị) để hàm biết cần vẽ lên “ngữ cảnh thiết bị” nào. Vậy Handle DC là giấy thông hành của cửa sổ bạn đến các hàm GDI. DC chẳng qua là một cấu trúc dữ liệu được duy trì bên trong bởi GDI. Một DC kết hợp với một thiết bị hiển thị đặc thù, như màn hình hay máy in chẳng hạn. Đối với một màn hình, DC thường kết hợp với một cửa sổ đặc thù trên màn hình. Sau khi bạn sẽ xong trong “ngữ cảnh thiết bị” bằng các hàm GDI, bạn sẽ “phát hành” (release) nó bằng hàm EndPaint.

NỘI DUNG:

Chúng ta sẽ viết một chương trình hiển thị một text string “Win32 assembly is great and easy!” trong trung tâm của vùng client.

.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

PHÂN TÍCH:

Đa số code này giống như ví dụ trong tut 3. Tôi sẽ chỉ giải thích những thay đổi quan trọng.

LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT

Chúng là biến cục bộ được sử dụng bởi các hàm GDI trong section WM_PAINT của chúng ta.

hdc được dùng để lưu handle của device context được trả về từ hàm BeginPaint .

ps là cấu trúc PAINTSTRUCT . Thông thường bạn ko sử dụng các giá trị trong ps . Nó được chuyển đến hàm BeginPaint và Windows fill (lắp) nó với giá trị thích hợp. Rồi bạn chuyển ps đến hàm EndPaint khi bạn hòan thành paint vùng client.

rect là một cấu trúc RECT, được định nghĩa như sau:

RECT Struct
left LONG ?
top LONG ?
right LONG ?
bottom LONG ?
RECT ends

lefttop là tọa độ của góc trái cao của một hình chử nhật. rightbottom là tọa độ góc phải dưới. Một điều cần nhớ là : Trục x-y origin là tại góc trái trên của vùng client. Vì vậy điểm y=10 là nằm dưới điểm y=0

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

Trong việc đáp trả thông điệp WM_PAINT, bạn gọi hàm BeginPaint với handle của window mà bạn muốn vẽ và một cấu trúc PAINTSTRUCT ko định trị đầu cho các tham số. Sau khi hòan thành lời gọi hàm, eax chứa handle của device context . Kế đến bạn gọi GetClientRect để lấy lại kích thước của vùng client . Kích thước được trả về trong biến rect và bạn truyền nó đến hàm DrawText như một trong những tham số của hàm. Cú pháp của hàm DrawText như sau:

DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD

DrawText là một hàm API xuất ra một text ở lớp cao (high-level).Nó xử lý vài chi tiết “đẫm máu” như word wrap, centering …vân vân. Vì vậy bạn có thể định vị string mà bạn muốn paint vào giữa trung tâm (center). Anh em low-level (lớp thấp) của nó là TextOut sẽ được nghiên cứu trong tut khác. DrawText định dạng một text string vừa khớp ko quá giới hạn hình chử nhật. Nó dùng font, color và background đã được lựa chọn (trong device context) để vẽ text. Những dòng được wrap(bao phủ) ko quá giới hạn hình chử nhật. Nó trả về height (chiều cao) của output text (text xuất ra) , có giá trị là đơn vị của decive (đơn vị của thiết bị), trong case của chúng ta là pixels. Chúng ta hảy xem xét các tham số của chúng:

hdc : handle của device context

lpString : pointer của string mà bạn muốn vẽ trong hình chử nhật. String phải là chuổi kết thúc bằng Null, nếu khác bạn phải chỉ rõ chiều dài của nó trong tham số kế tiếp,nCount

nCount : số các ký tự xuất ra. Nếu string kết thúc là null thì nCount phải là -1. hay nói cách khác, nCount phải chứa số của các ký tự trong string mà bạn muốn vẽ.

lpRect : pointer của hình chử nhật ( một cấu trúc lọai RECT) mà bạn muốn vẽ string trong đó. Chú ý rằng hình chử nhật cũng là một mẫu hình chử nhật, có nghĩa là bạn ko thể vẽ string ra ngòai hình chử nhật này.

uFormat : Giá trị chỉ định vị string hiển thị trong hình chử nhật như thế nào. Chúng ta dùng 3 giá trị được kết hợp bởi tóan hạn “or” như sau:

DT_SINGLELINE : chỉ định một dòng text đơn

DT_CENTER : trung tâm của text theo phương ngang

DT_VCENTER : trung tâm của text theo phương đứng

Sau khi hòan thành painting vùng client, bạn phải gọi hàm EndPaint để phát hành handle của device context.

-Bạn gọi cặp BeginPaint-EndPaint để đáp trả tín hiệu WM_PAINT

-Làm bất cứ điều gì bạn thích với vùng client giữa các hàm BeginPaintEndPaint

-Nếu bạn muốn vẽ lại vùng client của bạn để đáp trả các thông điệp khác , bạn có 2 lựa chọn:

1/.Sử dụng cặp GetDC-ReleaseDC thực hiện việc painting của bạn giữa 2 lời gọi hàm

2/.Gọi hàm InvalidRect hay UpdateWindow để làm mất hiệu lực tòan bộ vùng client, ép Windows put thông điệp WM_PAINT vào trong chuổi thông điệp của window của bạn và thực hiện vẽ trong section WM_PAINT.

Benina (21/11/2004)

Update 29/12/2005

(Không đồng ý bất kỳ ai sử dụng tài liệu này cho mục đích thương mại nếu ko được phép của người dịch)