Tải bản đầy đủ (.pdf) (73 trang)

Giáo trình lập trình c trên windows phần 2 nguyễn đình quyên, mai xuân hùng (đồng biên soạn)

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.25 MB, 73 trang )

Bài 4

GIAO TIẾP THIẾT BỊ ðỒ HỌA
Phần hệ thống của Microsoft Windows liên quan tới việc thể hiện ñồ họa trên thiết bị giao tiếp
màn hình, máy in ñược gọi là phần Giao tiếp Thiết bị ðồ họa (GDI – Graphics Device
Interface). ðây chính là cơ chế mà thông qua ñó các lập trình viên thao tác thể hiện các thông
tin, dữ liệu một cách trực quan trên các ứng dụng. Trong bài học này, chúng ta sẽ tìm hiểu cách
xuất văn bản, vẽ ñường thẳng, tô màu,… thông qua các hàm API về GDI tương ứng mà
Windows cung cấp.

4.1. TỔNG QUAN VỀ GDI
Chúng ta cần biết rằng các khái niệm, kỹ thuật về GDI của Microsoft Windows là rất nhiều. Vì
rằng ñây không chỉ là cơ chế ñể lập trình viên thao tác văn bản, hình ảnh, mà ngay từ ñầu ñây
cũng chính là cơ chế mà Windows phải thực hiện ñể thể hiện giao diện chung của mình: cách
trình bày menu, icon, cursor,… Tuy vậy, trong phạm vi của môn học, chúng ta không phân tích
tại sao Windows tổ chức các ñối tượng ñồ họa, mà chỉ tìm hiểu những hàm, cấu trúc dữ liệu cơ
bản nhất về ñồ họa là gì, cũng như cách thức ñể thể hiện chúng trên các ứng dụng Windowsbased là thế nào….
4.1.1. GDI và Thiết bị Ngữ cảnh
Xét dưới góc ñộ lập trình, GDI là tập hợp hàng trăm hàm (function) cũng như các kiểu dữ liệu,
macro, và cấu trúc mà Microsoft ñịnh nghĩa ñể xây dựng giao diện hệ ñiều hành Windows.
Cơ chế ñồ họa trên Windows 98 và Microsoft Windows NT ñược quản lý thông qua các hàm
liên kết từ thư viện GDI32.DLL. Trên Windows 98, thư viện GDI32.DLL sử dụng các hàm từ
thư viện GDI.EXE 16-bit ñể thực hiện các thao tác cài ñặt thật sự của các hàm. Trên Windows
NT, thư viện GDI.EXE chỉ dùng cho các chương trình dạng 16-bit.
ðiểm cần chú ý ở ñây là việc Windows sử dụng các thư viện này ñể thể hiện dữ liệu ñồ họa
lên các thiết vị vật lý cụ thể (video display, printer) như thế nào. Câu trả lời thể hiện qua mô hình
ở hình 4.1 sau:
Hình 4.1 Mô hình hoạt ñộng của GDI

Như thể hiện trên mô hình, các ứng dụng trên Windows không thật sự thao tác ñến thiết bị
xuất vật lý, mà chỉ thao tác ñến một ñối tượng logic, gọi là Thiết bị Ngữ cảnh (DC - Device


Context). Và như vậy, ở mức lập trình, chúng ta chỉ cần biết làm thế nào ñể gọi các hàm GDI lên
ñối tượng DC mà thôi – chúng ta sẽ tìm hiểu các dạng hàm GDI và cách gọi các hàm thao tác các
ñối tượng ñồ họa ở hai phần tiếp theo.
74


Cũng từ trên mô hình, bây giờ chúng ta ñã có thể hiểu tại làm sao mà ứng với mỗi thiết bị vật
lý như card màn hình, máy in,… chúng ta cần phài cài ñặt trình ñiều khiển thiết bị (driver) tương
ứng khi sử dụng trên Windows. Các driver này có nhiệm vụ chuyển các lệnh GDI thành các mã
lệnh hoặc cách truy cập mà thiết bị phần cứng tương ứng có thể hiểu và thực hiện ñúng theo yêu
cầu của ứng dụng. Và như vậy, chúng ta lập trình mà không quan tâm tới việc thiết bị máy in hay
video display ñược sử dụng là gì. Vì vậy, GDI còn ñược gọi là cơ chế ñồ họa ñộc lập thiết bị
(device-independent graphics).
Ở trên, chúng ta có nói ñến Thiết bị Ngữ cảnh, vậy Thiết bị Ngữ cảnh là gì, và cách thức
chúng ta lập trình thao tác Thiết bị Ngữ cảnh là thế nào? Ta có thể ñịnh nghĩa như sau, Thiết bị
Ngữ cảnh bao gồm tập hợp các “thuộc tính” xác ñịnh cách thức các hàm GDI thao tác trên thiết
bị. Tập thuộc tính này tương ứng các cấu trúc của các ñối tượng gọi là ñối tượng ñồ họa (xem
phần 4.1.3). Khi ta gọi các hàm GDI ñể thực hiện một thao tác ñồ họa nào ñó, ví dụ dùng hàm
LineTo ñể vẽ một ñường thẳng ñến một vị trí nào ñó trên DC, thì Windows dùng các thông tin về
bút vẽ (pen) như màu sắc, ñộ dày nét vẽ,… sẵn có trong ñối tượng ñồ họa này ñể vẽ. Trong
trường hợp chúng ta muốn vẽ với một màu sắc, kiểu vẽ,… khác, ta chỉ việc thay ñổi thông số của
ñối tượng này trước khi thực hiện thao tác vẽ. Chúng ta sẽ tìm hiểu cách thao tác một số ñối
tượng ñồ họa cơ bản là bút vẽ (pen), chổi tô (brush) và ảnh bitmap trong các phần tiếp sau.
4.1.2. Các dạng hàm GDI
Dựa vào mục ñích sử dụng, tập hợp các hàm GDI có thể ñược phân thành một số loại sau:
1

Các hàm nhận (hoặc tạo) và giải phóng (hoặc hủy) ñối tượng DC Như ñã thấy trong ví dụ
ñầu tiên, ñể xuất dòng chữ trong biến szHello ra màn hình, ta dùng hàm DrawText ñể vẽ lên
ñối tượng hdc. ðối tượng này nhận ñược qua hàm BeginPaint và giải phóng bằng hàm

EndPaint bên trong thông ñiệp WM_PAINT. Chúng ta sẽ nói rõ thêm về các hàm này trong
phần 4.1.4.

2

Các hàm lấy thông tin về DC Trong một số ứng dụng, chúng ta cần xác ñịnh một số thông tin
về DC như kích thước DC tính theo pixel và tính theo milimet, số lượng bút vẽ, chổi tô,…
trong DC thì ta dùng những hàm này. Tuy nhiên, với phạm vi môn học chúng ta sẽ không tìm
hiểu về các hàm lấy thông tin DC này.

3

Các hàm thực hiện thao tác vẽ ðể xuất một văn bản, vẽ một ñường thẳng, tô một hình
tròn,… GDI cung cấp cho chúng ta một số hàm tương ứng như TextOut, LineTo, Ellipse
Trong các phần tiếp theo chúng ta sẽ tìm hiểu về một số hàm thông qua các ví dụ tương ứng.

4

Các hàm thiết lập hoặc lấy các thuộc tính của DC Một “thuộc tính” trên DC xác ñịnh các ñặc
tính liên quan ñến dạng thức thực hiện thao tác vẽ. Thông qua một số hàm có liên quan, ta có
thể thiết lập hoặc lấy xem các thuộc tính nào ñó, chẳng hạn ta dùng hàm SetTextColor ñể
thay ñổi màu sắc của văn bản sẽ ñược vẽ trên màn hình bằng hàm DrawText.

5

Các hàm thao tác các “ñối tượng” GDI Trong phần tiếp theo, chúng ta sẽ tìm hiểu kỹ hơn
các “ñối tượng” mà thông qua chúng, chúng ta có thể thay ñổi cách thể hiện các thông tin ñồ
75



họa khác nhau lên DC. Mặc ñịnh thì trên DC luôn “có” các ñối tượng GDI với các “thông số”
nào ñó, và chúng ta có thể thay ñổi các “thông số” của chúng khi muốn thay ñổi kết quả thể
hiện thao tác vẽ. Chẳng hạn, với một DC ta luôn thể hiện ñộ dày và màu sắc nét vẽ ñường
thẳng thông qua ñối tượng bút vẽ (pen), và như thế nếu ta muốn vẽ với một nét vẽ khác thì
phải dùng ñối tượng bút vẽ mới. Các hàm mà chúng ta ñang ñề cập ở ñây sẽ giúp “thay ñổi”
ñối tượng bút vẽ hoặc các ñối tượng ñồ họa khác trên DC.
4.1.3. Các ñối tượng ñồ họa cơ bản
Các dạng ñồ họa thể hiện trên màn hình hoặc máy in ñược phân thành một số dạng, gọi là các
ñối tượng “cơ bản”, dựa trên các ñặc ñiểm riêng của chúng.
6

Các ñối tượng ñường và cung ðường (line) là thành phần nền tảng của các hệ thống ñồ họa
vector. GDI hỗ trợ các dạng ñường như ñường thẳng, hình chữ nhật, ellipse, cung ellipse,
ñường cong Bezier, polyline,… Các dạng ñường này khi ñược gọi vẽ bằng các thao tác vẽ sẽ
sử dụng thông tin bút vẽ (pen) ñang ñược chọn trong DC.

7

Các ñối tượng vùng tô Với bất cứ một vùng khép kín nào tạo bởi các ñường và cung, ta ñều
có thể tô với ñối tượng chổi tô (brush) hiện tại trong DC. Các dạng tô gồm có tô ñặc (solid),
tô theo mẫu (pattern) hoặc phủ ñầy bằng cách lặp lại một ảnh bitmap nào ñó trên toàn vùng
tô.

8

ðối tượng ảnh bitmap Một bitmap là một mảng chữ nhật các bit dữ liệu tương ứng các pixel
ảnh thể hiện trên thiết bị hiển thị. Khác với ñối tượng ñường, bitmap là thành phần nền tảng
của dạng ñồ họa ñiểm (raster graphics). Thông thường, khi nhắc ñến bitmap, chúng ta sẽ dễ
dàng nghĩ ngay ñến ñó là ñối tượng ảnh thực tế thể hiện thể hiện trên thiết bị hiển thị. Tuy
nhiên, bên cạnh ñó, các ñối tượng như icon, cursor,… cũng là các ñối tượng bitmap. GDI hỗ

trợ hai dạng bitmap, dạng thứ nhất là bitmap phụ thuộc thiết bị (device-dependent), ñây là
dạng bitmap gắn liền với phiên bản ñầu tiên của Windows, và vẫn ñược dùng trong các ứng
dụng sau này. Trong phạm vi môn học, chúng ta chỉ thao tác dạng bitmap này. Dạng thứ hai
ñược gọi là bitmap ñộc lập thiết bị (DIB – Device Independent Bitmap), ñược ñưa vào từ
phiên bản Windows 3.0. Dạng bitmap này có thể ñược lưu (theo cấu trúc riêng) và nạp vào
các ứng dụng từ tập tin. Khi lập trình, Windows cũng hỗ trợ chúng ta chuyển ñổi hai dạng
bitmap này với nhau (với một vài “mất mát” thông tin).

9

ðối tượng văn bản Không giống các ñối tượng ñồ họa ở trên, ñối tượng văn bản là một ñối
tượng khá phức tạp. ðể thể hiện văn bản dưới các dạng khác nhau, chúng ta cần sử dụng các
cấu trúc font ñược ñịnh nghĩa trước. Các cấu trúc này là tương ñối lớn. Trong phạm vi môn
học, chúng ta không thao tác các dạng font chữ, mà chỉ bàn ñến cách gọi hàm ñể thể hiện nội
dung văn bản ra thiết bị xuất.

Ngoài các ñối tượng cơ bản trên, chúng ta còn có các ñối tượng GDI khác bao gồm mapping
modes and transforms (chế ñộ ánh xạ ñiểm ảnh), metafiles, regions (vùng), paths (tập hợp
ñường), clipping (vùng cắt), palettes (bảng màu) và printing (ñối tượng in ấn). Trong phạm vi
76


môn học chúng ta không tìm hiểu thao tác với các ñối tượng này.

Hình 4.2. Quy trình thực hiện thao tác ñồ họa trên DC hiển thị

4.1.4. Quy trình thao tác các ñối tượng ñồ họa
Với mô hình ở hình 4.1, chúng ta ñã nắm ñược cách mà Windows thực hiện các thao tác ñồ họa,
nhưng ñó chỉ là mô hình lý thuyết. Trong phần này chúng ta sẽ giải quyết cách cài ñặt code của
một thao tác ñồ họa theo quy trình chung thể hiện trên mô hình ở hình 4.2.

Công việc ñầu tiên cần phải làm là làm sao có ñược DC của cửa sổ cần thể hiện dữ liệu ñồ
họa, và sau ñó tất cả các hàm ñồ họa ñều sẽ thao tác lên trên DC này.
Thật ra, quy trình mà chúng ta ñang tìm hiểu chỉ nói ñến việc thể hiện dữ liệu ra thiết bị hiển
thị video, và chúng ta chỉ thao tác DC của dạng thiết bị này. Nếu xét về DC thì Windows cung
cấp cho chúng ta bốn loại khác nhau: DC hiển thị (Display DC), DC vùng nhớ (Memory DC),
DC in ấn (Printer DC) và DC thông tin (Information DC). Tuỳ theo yêu cầu phải quản lý và thao
tác mà chúng ta cần dùng các dạng DC khác nhau thông qua các hàm khác nhau. ðối với môn
học này, chúng ta chỉ thao tác hai dạng DC liên quan tới việc hiển thị dữ liệu lên thiết bị video là
DC hiển thị và DC vùng nhớ.
Với các thao tác ñồ họa cơ bản như xuất văn bản (xem phần 4.2) và thao tác bút vẽ, chổi tô
(xem phần 4.3), chúng ta chỉ thao tác lên DC hiển thị. Thao tác nhận và giải phóng DC hiển thị
có thể ñược thực hiện theo hai cách:
1

Dùng thông ñiệp WM_PAINT Thông ñiệp WM_PAINT ñược gởi khi hệ thống hoặc một
ứng dụng yêu cầu thực hiện thao tác vẽ (paint) lại một vùng cửa sổ ứng dụng. Và vì vậy,
nếu ta muốn thể hiện kết quả ñồ họa lên cửa sổ ứng dụng thì ta chỉ việc viết code tại
thông ñiệp này. Trong trường hợp này, ñể lấy DC ta dùng hàm BeginPaint và ñể giải
phóng DC ta dùng hàm EndPaint.
HDC BeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);
BOOL EndPaint(HWND hWnd, CONST PAINTSTRUCT *lpPaint);

Với hWnd là chỉ danh của cửa sổ cần vẽ, lpPaint trỏ ñến cấu trúc PAINSTRUCT xác
ñịnh thông tin vẽ.
2

Không dùng WM_PAINT Bất kỳ ở ñâu trong ứng dụng, chỉ cần có chỉ danh hWnd của
cửa sổ, ta ñều có thể dùng hàm GetDC hoặc GetWindowDC ñể nhận DC, sau ñó dùng
hàm ReleaseDC ñể giải phóng DC.
HDC GetDC(HWND hWnd);

HDC GetWindowDC(HWND hWnd);
int ReleaseDC( HWND hWnd, HDC hDC);

77


Hàm GetDC và GetWindowDC khác nhau ở việc hàm GetDC trả về DC chỉ của vùng
client area, trong khi hàm GetWindowDC trả về DC của toàn cửa sổ, bao gồm cả thanh
tiêu ñề, menu
Mỗi DC ñều có sẵn các ñối tượng GDI bên trong nó. Khi thực hiện một thao tác vẽ nào ñó,
Windows sử dụng các ñối tượng mặc ñịnh ñang có. Tuy nhiên, ta cũng có thể lập trình tạo mới
các ñối tượng khác. Trong các phần tiếp theo, chúng ta sẽ tìm hiểu cách tạo lập một số ñối tượng
thông qua các ví dụ. Một ñối tượng GDI khi muốn ñưa vào DC, ta dùng hàm SelectObject, hàm
này thao tác chung cho tất cả các ñối tượng GDI (HGDIOBJ), và vì thế khi lập trình thao tác
cho dạng ñối tượng cụ thể nào ñó, ta cần ép kiểu (cast) ñối tượng tương ứng (xem thêm ở các ví
dụ). Sau khi sử dụng xong ñối tượng GDI, ta chỉ việc dùng hàm DeleteObject ñể huỷ bỏ.
HGDIOBJ SelectObject(HDC hDC, HGDIOBJ hGDIObj);
BOOL DeleteObject(HGDIOBJ hObject);

4.2. THAO TÁC VĂN BẢN
Chúng ta sẽ tìm hiểu thao tác về văn bản thông qua chương trình minh họa cách thể hiện dòng
chữ “Hello World!” lên màn hình ứng dụng với màu sắc và vị trí dòng chữ ñược người dùng
chọn từ menu.
4.2.1. Tạo lập project VanBan và tài nguyên menu
Với các project trước ñây, chúng ta ñã biết là khi tạo mới ứng dụng Win32 Application và chọn
dạng tạo là A typical “Hello World!” application, thì chương trình khi thực hiện sẽ thể hiện dòng
chữ “Hello World!” màu ñen nằm ở giữa và trên của vùng làm việc (client area).
Ta cũng tạo lập project VanBan theo dạng này, sau ñó vào ResourceView chọn menu
IDC_VANBAN ñể thêm một số menu item như sau:
3


Thêm popup menu Color với các item là Black (IDM_BLACK), Red (IDM_RED),
Green (IDM_GREEN) và Blue (IDM_BLUE).

4

Thêm popup menu Alignment với các item là Left (IDM_LEFT), Center
(IDM_CENTER) và Right (IDM_RIGHT).

4.2.2. Viết code xử lý cho ứng dụng
Như ñã tìm hiểu trong bài 2, ñể chương trình thực hiện một thao tác nào ñó khi người dùng chọn
các menu item, ta chỉ việc vào chặn ñịnh danh của chúng thông qua giá trị LOWORD(wParam)
của thông ñiệp WM_COMMAND và viết code thao tác tương ứng. Tuy nhiên ở ứng dụng này,
thao tác của chúng ta là thể hiện lại văn bản lên DC của ứng dụng khi người dùng chọn các menu
item về màu sắc và dạng canh lề, và trong trường hợp này thì thực hiện thao tác qua thông ñiệp
WM_PAINT. Do ñó, ñể làm ñiều này ta lợi dụng hàm InvalidateRect với mục ñích hàm này sẽ
“gởi” yêu cầu thực hiện thông ñiệp WM_PAINT của cửa sổ hWnd tương ứng.
78


BOOL InvalidateRect(HWND hWnd, CONST RECT* lpRect,

BOOL bErase);

Xét ñoạn code thực hiện thao tác xuất văn bản tại thông ñiệp WM_PAINT. ðể xuất một
ñoạn văn bản ra DC ta dùng hàm DrawText, hàm này thể hiện chuỗi lpString lên vùng hình chữ
nhật lpRect của hDC với dạng canh lề thiết lập qua uFormat.
int DrawText(HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat);

Với ứng dụng này, ta dùng ba dạng canh lề với giá trị uFormat bằng DT_LEFT,

DT_CENTER, và DT_RIGHT cho các yêu cầu thể hiện văn bản ở trái, giữa và bên phải lpRect.
Như vậy, mỗi lần ta chọn các menu item canh lề thì ta phải làm sao ñó chuyển ñược thông số
tương ứng ñến cho uFormat. Trong trường hợp này ta có thể dùng một biến phụ dạng toàn cục
(global), hoặc tĩnh (static) như biến nAlign bên trong hàm WndProc.
Về việc thay ñổi màu của văn bản, ta cũng thực hiện tương tự, sử dụng biến crColor với màu
mặc ñịnh là màu ñen RGB(0,0,0).
Hàm SetTextColor là dạng hàm thay ñổi thuộc tính về màu sắc văn bản cần xuất của DC.
COLORREF SetTextColor(HDC hdc, COLORREF crColor);

Với những thao tác như trên, ta có ñoạn code hàm WndProc của ứng dụng VanBan trong
tập tin VanBan.cpp như sau (các phần khác trong tập tin này không cần thay ñổi).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
TCHAR szHello[MAX_LOADSTRING];
LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
COLORREF crOldColor;
static COLORREF crColor = RGB(0, 0, 0);
static UINT nAlign = DT_CENTER;
switch (message)
{
case WM_COMMAND:

wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_BLACK:
crColor = RGB(0, 0, 0);
break;
case IDM_RED:
crColor = RGB(255, 0, 0);
break;
case IDM_GREEN:
crColor = RGB(0, 255, 0);
break;
case IDM_BLUE:
crColor = RGB(0, 0, 255);
break;
case IDM_LEFT:
nAlign = DT_LEFT;

79


35
36
37
38
39
40
41

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

72

break;
case IDM_CENTER:
nAlign = DT_CENTER;
break;
case IDM_RIGHT:
nAlign = DT_RIGHT;
break;
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd,
(DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam,
lParam);
}
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
crOldColor = SetTextColor(hdc, crColor);
RECT rt;
GetClientRect(hWnd, &rt);
DrawText(hdc, szHello, strlen(szHello), &rt, nAlign);
SetTextColor(hdc, crOldColor);

EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

4.3. THAO TÁC ðỐI TƯỢNG BÚT VẼ, CHỔI TÔ
So với quy trình chung thực hiện thao tác các ñối tượng ñồ họa ở hình 4.2, việc thể hiện văn bản
mà chúng ta vừa thực hiện không nói gì ñến việc tạo lập và ñưa một ñối tượng GDI (nếu có sẽ là
font chữ) vào DC mà chỉ sử dụng thông tin mặc ñịnh. Vì thế, trong phần này chúng ta sẽ tìm hiểu
thêm thao tác ñối với ñối tượng GDI, ñồng thời tìm hiểu cách xuất các ñối tượng ñường, cung và
vùng tô ra thiết bị hiển thị.

80


Hình 4.3. Kết quả sau khi người dùng chọn chức năng Ellipse

Chúng ta sẽ thực hiện ví dụ về một ứng dụng cho phép người dùng chọn menu ñể thể hiện
giữa vùng làm việc một ñường thẳng, hình chữ nhật, hoặc một ellipse với kích thước cho trước
và màu sắc cũng ñược chọn từ menu - xem hình 4.3.
4.2.1. Tạo lập project VeHinh và tài nguyên menu
Tương tự ứng dụng về văn bản, chúng ta tạo lập project Win32 Application với tên là VeHinh,
dạng A typical “Hello World!” application. Sau ñó chọn menu IDC_VEHINH trong
ResourceView và thêm các menu item như sau:

5

Thêm popup menu Shape với các item là Line (IDM_LINE), Rectangle
(IDM_RECTANGLE), và Ellipse (IDM_ELLIPSE).

6

Thêm popup menu Color với các item là Black (IDM_BLACK), Red (IDM_RED),
Green (IDM_GREEN) và Blue (IDM_BLUE).

4.3.2. Viết code xử lý cho ứng dụng
Tương tự ứng dụng VanBan, chúng ta viết code tại thông ñiệp WM_COMMAND ñể xử lý cho
các thao tác menu item. Ta cũng dùng biến tĩnh crColor kiểu COLORREF ñể lưu giá trị màu
cần vẽ; dùng biến int nShape ñể lưu dạng hình vẽ, với các giá trị ñặt là 1, 2, 3 ứng với dạng hình
vẽ là ñường thẳng, hình chữ nhật và ellipse.
Sau khi ñã thiết lập các giá trị sẽ dùng ñể vẽ hình, cũng như ứng dụng VanBan, ta dùng hàm
InvalidateRect ñể kích hoạt thông ñiệp WM_PAINT của cửa sổ chính.
Quy trình bên trong thông ñiệp WM_PAINT ñúng như ở mô hình hình 4.2. ðầu tiên ứng
dụng lấy DC bằng hàm BeginPaint, sau ñó tạo lập bút vẽ hPen và chổi tô hBrush theo màu ñã
chọn (mặc ñịnh là màu ñen) rồi ñưa vào DC bằng hàm SelectObject. Tiếp theo ứng dụng căn cứ
vào giá trị của nShape ñể thực hiện thao tác vẽ. Ở ñây ta dùng một số hàm API ñã ñược
81


Windows cung cấp như LineTo, Rectangle,… Trong trường hợp thực hiện các thao tác ñồ họa
khác ta có các hàm khác, vì thế cần tra cứu trong MSDN. Cuối cùng, ta chọn lại các ñối tượng
GDI ban ñầu, hủy các ñối tượng tạo lập trước ñó bằng hàm DeleteObject, và kết thúc thông ñiệp
WM_PAINT với hàm EndPaint.
Sau ñây liệt kê prototype các hàm dùng trong ứng dụng VeHinh.
Hàm CreatePen tạo lập ñối tượng bút vẽ HPEN theo kiểu vẽ fnPenStyle (PS_SOLID,

PS_DASH, PS_DOT,…), ñộ dày nét vẽ nWidth và màu vẽ crColor.
HPEN CreatePen(int fnPenStyle, int nWidth, COLORREF crColor);

Hàm CreateSolidBrush tạo lập ñối tượng chổi tô ñặc (solid) với màu tô crColor.
HBRUSH CreateSolidBrush(COLORREF crColor);

Hàm SelectObject ñưa một ñối tượng HGDIOBJ (là bút vẽ HPEN, chổi tô HBRUSH, font
chữ HFONT, …) vào DC và trả về ñối tượng cũ tương ứng trong DC.
HGDIOBJ SelectObject(HDC hdc, HGDIOBJ hgdiobj);

Hàm DeleteObject hủy một ñối tượng GDI ñã ñược tạo lập trước ñó và không cần dùng nữa.
BOOL DeleteObject(HGDIOBJ hObject);

Hàm GetClientRect nhận thông tin tọa ñô của cửa sổ hWnd và chuyển thông tin ñó vào cấu
trúc hình chữ nhật lpRect.
BOOL GetClientRect(HWND hWnd, LPRECT lpRect);

Hàm MoveToEx thiết lập lại vị trí ñiểm vẽ ñến tọa ñộ X, Y và trả về ñiểm vẽ cũ vào biến
lpPoint.
BOOL MoveToEx(HDC hdc, int X, int Y, LPPOINT lpPoint);

Hàm LineTo sử dụng các thông số của bút vẽ hiện tại ñể vẽ một ñường thẳng từ vị trí ñiểm
vẽ hiện tại ñến nXEnd, nYEnd.
BOOL LineTo(HDC hdc, int nXEnd, int nYEnd);

Cuối cùng là hàm vẽ hình chữ nhật và ellipse nội tiếp hình chữ nhật có vị trí góc trái, trên,
phải và dưới xác ñịnh bởi các tham số nLeftRect, nTopRect, nRightRect, nBottomRect.
BOOL Rectangle(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
BOOL Ellipse(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);


Và ñây là ñoạn code hàm WndProc của ứng dụng VeHinh.
1
2
3
4
5
6

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;

82


7
8
9
10
11
12
13
14
15
16
17
18

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

static int nShape = 1; // 1: Line; 2: Rectangle; 3: Ellipse
static COLORREF crColor = RGB(0, 0, 0);
HPEN hPen, hOldPen;
HBRUSH hBrush, hOldBrush;

RECT rt;
POINT
ptCenter;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_LINE:
nShape = 1;
break;
case IDM_RECTANGLE:
nShape = 2;
break;
case IDM_ELLIPSE:
nShape = 3;
break;
case IDM_BLACK:
crColor = RGB(0, 0, 0);
break;
case IDM_RED:
crColor = RGB(255, 0, 0);
break;
case IDM_GREEN:
crColor = RGB(0, 255, 0);
break;
case IDM_BLUE:

crColor = RGB(0, 0, 255);
break;
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd,
(DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam,
lParam);
}
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
hPen = CreatePen(PS_SOLID, 1, crColor);
hBrush = CreateSolidBrush(crColor);
hOldPen = (HPEN)SelectObject(hdc, hPen);
hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
GetClientRect(hWnd, &rt);
ptCenter.x = (rt.left+rt.right)/2;
ptCenter.y = (rt.top+rt.bottom)/2;
switch(nShape)
{
case 1:
MoveToEx(hdc, ptCenter.x-100, ptCenter.y-50, NULL);
LineTo(hdc, ptCenter.x+100, ptCenter.y+50);
break;


83


74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98


case 2:
Rectangle(hdc, ptCenter.x-100, ptCenter.y-50,
ptCenter.x+100, ptCenter.y+50);
break;
case 3:
Ellipse(hdc, ptCenter.x-100, ptCenter.y-50,
ptCenter.x+100, ptCenter.y+50);
break;
}
SelectObject(hdc, hOldPen);
SelectObject(hdc, hOldBrush);
DeleteObject(hPen);
DeleteObject(hBrush);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

4.4. THAO TÁC ẢNH BITMAP
Trong hai phần vừa tìm hiểu, chúng ta ñã thao tác các ñối tượng ñường và cung, vùng tô và văn
bản. ðối tượng GDI cơ bản còn lại là ảnh bitmap. Tuy nhiên, khác với các ñối tượng trên, các
thao tác ñối với ñối tượng ảnh bitmap phức tạp hơn. Ngoài ñối tượng Display DC của cửa sổ,
chúng ta cần thêm ñối tượng DC vùng nhớ (Memory DC) dùng ñể lưu ảnh cùng các thao tác liên
quan trước khi chuyển ảnh qua Display DC thật sự. Sau ñây chúng ta sẽ tìm hiểu hai quy trình

liên quan ñến ảnh bitmap là nạp - hiển thị và zoom ảnh.
4.4.1. Nạp và hiển thị ảnh bitmap
Như ñã giới thiệu ở trên, một bitmap là một mảng chữ nhật các bit dữ liệu tương ứng các pixel
ảnh thể hiện trên thiết bị hiển thị, ñược quản lý trên tập tin thông qua format riêng (bmp) và khi
cần thì nạp vào ứng dụng và hiển thị (DIB), hoặc là dữ liệu có sẵn trong ứng dụng và khi cần thì
hiển thị (DDB). Ta có quy trình nạp và hiển thị một ảnh bitmap như ở hình 4.4.
Hình 4.4. Quy trình nạp và hiển thị ảnh bitmap

Cũng giống như quy trình thao tác ñồ họa tổng quát (hình 4.2), ñể hiển thị ảnh bitmap lên
một cửa sổ, ta cần lấy DC của cửa sổ ñó – trong quy trình trên minh họa bằng hàm GetDC. Ta
cũng cần tạo lập ñối tượng GDI - ảnh bitmap HBITMAP - bằng cách nạp từ tài nguyên bằng
hàm LoadBitmap hoặc ñọc từ tập tin bất kỳ (tuy nhiên, ở ñây ta sẽ không thao tác với tập tin).
Sau ñó ta cũng ñưa ảnh vào DC thông qua hàm SelectObject.
Nếu chỉ như vậy thì quy trình hoàn toàn giống quy trình chung cho các ñối tượng ñồ họa. Thế
nhưng ở ñây chúng ta không thực hiện như vậy. Thay vì ñưa ảnh vào Display DC của cửa sổ, ta
84


ñưa ảnh vào một DC ảo khác (dạng DC vùng nhớ). ðiều này nhằm giúp việc thực hiện thao tác
chuyển ảnh vào DC vốn khá chậm sẽ không diễn ra trên màn hình (sẽ gây chớp) mà diễn ra trên
DC ảo. Và vì thế ta cần tạo ñối tượng DC này. Ta sử dụng hàm CreateCompatibleDC ñể tạo
một DC ảo có các thông số giống DC của cửa sổ ñã có rồi chọn ñối tượng ảnh HBITMAP vào
DC ảo này. Cuối cùng ta dùng hàm BitBlt ñể chuyển ảnh từ DC ảo sang Display DC ñể thể hiện
trên cửa sổ.
Prototype của các hàm LoadBitmap, CreateCompatibleDC và BitBlt như sau:
Hàm LoadBitmap nạp ảnh xác ñịnh thông qua tên lpBitmapName trong tài nguyên vào ứng
dụng hInstance. Kết quả trả về là chỉ danh ñối tượng bitmap HBITMAP.
HBITMAP LoadBitmap(HINSTANCE hInstance, LPCTSTR lpBitmapName);

Hàm CreateCompatibleDC tạo lập ñối tượng DC vùng nhớ dựa trên ñối tượng DC hdc ban

ñầu. Và hàm
HDC CreateCompatibleDC(HDC hdc);

Và hàm chuyển ảnh từ DC nguồn hdcSrc xét từ vị trí nXSrc, nYSrc sang DC ñích hdcDest tại
vị trí nXDest, nYDest với kích thước là nWidth, nHeight. Tham số cuối cùng dwRop xác ñịnh
dạng chuyển dữ liệu:
1

SRCCOPY: copy vùng chữ nhật ảnh nguồn thẳng sang ảnh ñích.

2

SRCAND: kết hợp giá trị màu từng ñiểm ảnh nguồn và ñích bằng toán tử AND.

3

SRCPAINT: kết hợp bằng toán tử OR.

4

Các giá trị khác của dwRop xin xem trong MSDN…

BOOL BitBlt(HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int

nXSrc, int nYSrc, DWORD dwRop);

Sau ñây là một ví dụ minh họa thao tác xuất ảnh bitmap trên cửa sổ chính của ứng dụng. Ứng
dụng này cho phép người dùng chọn thể hiện một vài hình ảnh có sẵn trong tài nguyên ra chính
giữa màn hình.
ðầu tiên ta cũng tạo lập project Win32 Application dạng A typical “Hello World!”

application có tên là XuatAnh, sau ñó xây dựng popup menu Bitmap với các menu item ñại
diện cho các ảnh sẽ chọn thể hiện. Ở ñây tôi minh họa với ba ảnh bitmap, và tôi tạo ba menu
item

Elephant
(IDM_ELEPHANT),
Pine
(IDM_PINE)

Mushroom
(IDM_MUSHROOM).
Song song với việc tạo lập menu, ta cũng tạo lập các ảnh trong resource. Chúng ta có thể
Insert Bitmap hoặc Import các tập tin ảnh bitmap vào tài nguyên project. Ba ñối tượng ảnh
bitmap ñược ñặt tên tương ứng là IDB_ELEPHANT, IDB_PINE, và IDB_MUSHROOM - xem
hình 4.5.
85


Sau khi ñã tạo lập tài nguyên, ta sẽ viết code thực hiện các thao tác hiển thị các ảnh bitmap
trên khi người dùng chọn menu tương ứng của ứng dụng.

Hình 4.5. Tài nguyên ảnh bitmap IDB_ELEPHANT

Cũng như các project trên, khi người dùng chọn các menu item, ta có thể dùng một biến tĩnh
ñể lưu lại giá trị xác ñịnh ảnh tương ứng. Nhận thấy rằng ñịnh danh của ảnh là một hằng số ñược
ñịnh nghĩa trước (IDB_ELEPHANT, …) do ñó ta ñặt biến nhớ là nIDB, và thiết lập với các giá
trị tương ứng, rồi dùng hàm InvalidateRect ñể “gởi” thông ñiệp WM_PAINT thể hiện ảnh lên
màn hình.
Bên trong thông ñiệp WM_PAINT, ta viết code theo ñúng quy trình hiển thị một ảnh bitmap
(nạp từ resource) lên cửa sổ chính của ứng dụng. Ngoài các hàm và kiểu dữ liệu ñã liệt kê ở trên,

ñể có kích thước ảnh nhằm thể hiện ñược ảnh chính giữa màn hình, ta dùng thêm cấu trúc
BITMAP và lấy thông tin ảnh DDB qua hàm GetObject.
typedef struct tagBITMAP {
LONG bmType;

// Chiều rộng của ảnh
// Chiều cao của ảnh

LONG bmWidth;
LONG bmHeight;
LONG bmWidthBytes;
WORD bmPlanes;
WORD bmBitsPixel;
LPVOID bmBits;
} BITMAP, *PBITMAP;
int GetObject(
HGDIOBJ hgdiobj,

// Trỏ ñến ñối tượng GDI
86


int cbBuffer,

// Kích thước ñối tượng

LPVOID lpvObject

// Vùng ñệm thông tin ñối tượng


);

Và ñây là ñoạn code hàm WndProc của ứng dụng XuatAnh.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

58

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
static int nIDB = IDB_ELEPHANT; // ID cua bitmap trong resource
HDC
hdc, hdcMem;
HBITMAP hBitmap;
BITMAP bitmap;
RECT
rt;
POINT
ptOrgBmp;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ELEPHANT:
nIDB = IDB_ELEPHANT;
break;
case IDM_PINE:
nIDB = IDB_PINE;
break;

case IDM_MUSHROOM:
nIDB = IDB_MUSHROOM;
break;
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd,
(DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam,
lParam);
}
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
hdcMem = CreateCompatibleDC(hdc);
hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(nIDB));
GetObject(hBitmap, sizeof(BITMAP), &bitmap);
SelectObject(hdcMem, hBitmap);
GetClientRect(hWnd, &rt);
ptOrgBmp.x = (rt.left + rt.right - bitmap.bmWidth)/2;
ptOrgBmp.y = (rt.top + rt.bottom - bitmap.bmHeight)/2;
BitBlt(hdc, ptOrgBmp.x, ptOrgBmp.y, bitmap.bmWidth,
bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);
DeleteDC(hdcMem);

87



59
60
61
62
63
64
65
66
67
68

EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

4.4.2. Thao tác zoom ảnh bitmap
ðối với các ứng dụng về ảnh, ngoài việc thể hiện ảnh với ñúng kích thước, ñôi lúc chúng ta cũng
cần phóng lớn hay thu nhỏ ảnh ñể quan sát tốt hơn. ðể làm ñiều này, chúng ta thực hiện một quy
trình cũng tương tự như quy trình hiển thị ảnh. Hình 4.6 trình bày mô hình quy trình này.

Hình 4.6. Quy trình zoom ảnh bitmap


ðể zoom (phóng lớn hoặc thu nhỏ) một ñối tượng ảnh lên một cửa sổ, ta cũng nạp ảnh từ tài
nguyên (hoặc tạo lập) và lấy DC của cửa sổ như ñã làm trong quy trình hiển thị ảnh. Sau ñó tạo
lập thêm hai DC vùng nhớ (thay vì một như trong quy trình hiển thị), ñặt là DC nguồn và DC
ñích. DC nguồn sẽ chứa ảnh HBITMAP nạp từ tài nguyên ở trên và ñưa vào bằng hàm
SelectObject. DC ñích chứa một ảnh “trắng” ñược tạo lập bằng hàm CreateCompatibleBitmap
dựa trên ñịnh dạng của ảnh trong Display DC của cửa sổ, và cũng nạp vào tương tự bằng hàm
SelectObject. Sau khi có hai DC vùng nhớ chứa ảnh bitmap, ta dùng hàm StretchBlt ñể zoom
ảnh từ DC nguồn sang DC ñích. Cuối cùng ta chỉ việc chuyển ảnh ñã ñược zoom về Display DC
của cửa sổ bằng hàm BitBlt như ñã biết.
Hàm CreateCompatibleBitmap tạo lập một bitmap có kích thước nWidth x nHeight (pixels)
tương thích với ñối tượng xác ñịnh hdc.
HBITMAP CreateCompatibleBitmap(HDC hdc, int nWidth, int nHeight);

Tương tự hàm BitBlt, nhưng thay vì chuyển ảnh từ DC nguồn hdcSrc sang DC ñích hdcDest
với cùng một kích thước, hàm StretchBlt chuyển ảnh với kích thước nguồn nWidthSrc ,
nHeightSrc sang kích thước ñích nWidthDest, nHeightDest.
BOOL StretchBlt(HDC hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int

nHeightDest, HDC hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc,
DWORD dwRop);

Chúng ta vừa tìm hiểu lý thuyết quy trình zoom ảnh bitmap, ví dụ cụ thể sẽ ñược minh họa ở
bài học tiếp theo.

4.5. TÓM TẮT

88



Thông qua quy trình hiển thị văn bản – phần 4.2, quy trình thực hiện thao tác vẽ cơ bản với bút
vẽ và chổi tô – phần 4.3, và quy trình hiển thị và zoom ảnh bitmap – phần 4.4, chúng ta ñã hiểu
rõ cách thức lập trình ñể xuất dữ liệu ñồ họa ra ñối tượng thiết bị ngữ cảnh.
Tuy nhiên chúng ta mới chỉ thao tác các ñối tượng ñồ họa khá ñơn giản. Vì thế, ñể thực hiện
các thao tác ñồ họa ñối với bất kỳ ñối tượng nào theo quy trình chung ñã ñược học – phần 4.1.4 –
ta cần kết hợp tra cứu toàn diện về Windows GDI, Platform SDK Documentation trên MSDN.
4.6. CÂU HỎI ÔN TẬP – BÀI TẬP
4.6.1. Trình bày tổng quát về cơ chế GDI của Windows? Mô hình hoạt ñộng GDI? Sự khác biệt
khi thể hiện hình ảnh trên ứng dụng của Windows so với trên MS-DOS?
4.6.2. Thiết bị Ngữ cảnh (Device Context) là gì (xét ở mức lập trình)? Giới thiệu ngắn gọn về các
ñối tượng GDI? Các nhóm hàm liên quan về ñồ họa GDI?
4.6.3. Trình bày quy trình thực hiện thao tác ñồ họa trên thiết bị hiển thị? Áp dụng quy trình này
vào việc thể hiện văn bản, vẽ hình và xuất ảnh lên cửa sổ ứng dụng?
4.6.4*. Tìm hiểu một trong các ñối tượng GDI phức tạp như Color (Palette), Font,… trên MSDN.
Kiến thức tổng quan? Các cấu trúc liên quan? Các nhóm hàm và thông ñiệp Windows hỗ trợ?
Việc áp dụng khi lập trình cho các ứng dụng?
4.6.5. Viết chương trình thể hiện dòng chữ “This is a sample text!” nằm giữa của sổ chính với
màu sắc do người dùng chọn qua các nút nhấn (mặc ñịnh là màu ñỏ) – xem hình 4.7.

Hình 4.7. Chương trình xuất chữ với màu chọn qua nút nhấn.

Hình 4.7 Chương trình xuất chữvới màu chọn qua nút nhấn

Hướng dẫn: Ta có thể dùng hàm CreateWindow ñể tạo 3 nút nhấn static (lớp “BUTTON”)
trong thông ñiệp WM_CREATE. Khi người dùng chọn nút nhấn nào, Windows sẽ gởi thông
ñiệp WM_COMMAND với (HWND)lParam là ñịnh danh của nút nhấn tương ứng, ta viết code
ñể xuất lại dòng chữ với màu ñã chọn (có thể trong thông ñiệp WM_PAINT).
89



4.6.6. Viết chương trình thể hiện dòng chữ giữa của sổ chính – hình 4.8a, với nội dung dòng chữ
và màu sắc do người dùng nhập vào từ hộp thoại – hình 4.8b.
Hướng dẫn: Ta tạo lập tài nguyên menu và hộp thoại như ở hình trên. Khi người dùng chọn
menu GetText, ta dùng hàm DialogBox ñể hiển thị hộp thoại và chuyển “quyền ñiều khiển”
thông ñiệp cho hộp thoại. Khi người dùng click chọn các radio button trên hộp thoại, ta có thể
dùng một biến toàn cục ñể lưu lại thông tin này. Khi người dùng chọn nút OK, ta gởi dữ liệu text
vào một biến toàn cục nào ñó sử dụng hàm GetDlgItemText, rồi ñóng hộp thoại bằng hàm
EndDialog. Nhận thấy rằng khi chọn OK hoặc Cancel ta ñều ñóng hộp thoại, vì thế khi
EndDialog, ta có thể gởi giá trị này vào tham số thứ hai, ñó cũng chính là giá trị trả về của hàm
DialogBox, và ta sử dụng giá trị này ñể quyết ñịnh có thực hiện thao tác thể hiện chuỗi dữ liệu
text (toàn cục) với màu sắc (biến toàn cục ñã thiết lập) lên màn hình hay không (dùng hàm
DrawText).

Hình 4.8. Chương trình xuất chữ ñược nhập từ hộp thoại.

4.6.7. Viết chương trình thể hiện dòng chữ sau khi nhận từ hộp thoại – hình 4.9b – lên màn hình
– hình 4.9a, với nội dung và dạng canh lề do người dùng chọn.

90


Hình 4.9. Chương trình xuất chữ theo dạng canh lề.

Hướng dẫn: Trong bài tập 4.6.6, chúng ta tạo các radio button trên hộp thoại với các thông số
mặc ñịnh, trong ñó có chúc năng tự ñộng check khi người dùng chọn một button (trong cả
nhóm), tuy nhiên ở bài này thì khác, chúng ta có hai nhóm radio button khác nhau, vì thế khi tạo
lập trong tài nguyên, ta bỏ chọn kiểu tự ñộng (Style Auto) ñi. Khi ñó, việc viết code xử lý tương
tự bài tập trên, nhưng ta phải viết hàm check radio CheckRadioButton ứng mỗi thao tác chọn
trên hộp thoại. Ngoài ra, ñối với chương trình này, nhóm radio Top, Vertical Center, và
Bottom chỉ có hiệu lực khi check box Single Line ñược chọn, vì thế ta cần dùng các hàm

CheckDlgButton, IsDlgButtonChecked ñể check và kiểm tra check box có ñược chọn hay
không; ñồng thời dùng hàm EnableWindow ñể enable hay disable radio button. Cuối cùng, khi
người dùng chọn OK thì ta căn cứ trên trạng thái (enable, check) của các radio mà thiết lập giá trị
dạng canh lề (dùng thêm hàm IsWindowEnabled) theo các cờ sẽ dùng cho hàm DrawText và
lấy text gởi về cho cử sổ chính và thực hiện giống như bài 4.6.6.
4.6.8. Viết chương trình vẽ lên chính giữa màn hình một hình tròn – hình 4.10a – với bán kính và
màu sắc ñược chọn từ hộp thoại – hình 4.10b.

Hình 4.10. Chương trình xuất hình tròn giữa màn hình.

Hướng dẫn: ðối với bài này, nên chọn edit text nhận dữ liệu có kiểu Number, và như vậy ta
dùng hàm GetDlgItemInt ñể lấy giá trị bán kính hình tròn (Circle Radius). Rồi khi thể hiện
hình tròn, ta tạo bút vẽ và chổi tô theo màu ñã chọn và dùng hàm Ellipse ñể vẽ hình (sau khi xác
ñịnh ñược vị trí cần vẽ).

91


Bài 5

BÀN PHÍM, THIẾT BỊ CHUỘT
VÀ BỘ ðỊNH THỜI GIAN
Trong các bài học trước chúng ta ñã tìm hiểu cách thức xử lý các tác ñộng của người dùng lên
ứng dụng thông qua các ñối tượng như menu, control với thông ñiệp WM_COMMAND, cách
thức Windows vẽ lại cửa sổ với thông ñiệp WM_PAINT, gởi thông ñiệp WM_SIZE khi thay
ñổi kích thước cửa sổ. Bài học này tiếp tục cung cấp cho chúng ta một số thông ñiệp quan trọng
khác về cơ chế Windows xử lý tác ñộng từ bàn phím (keyboard), thiết bị chuột (mouse) và bộ
ñịnh thời gian (timer) nhằm giúp chúng ta có thể xây dựng các ứng dụng dạng Win32
Application hiệu quả hơn.


5.1. THÔNG ðIỆP VÀ XỬ LÝ THÔNG ðIỆP
Với phần giới thiệu chung ở bài 1, cũng như xuyên suốt quá trình học, ñiều quan trọng nhất khi
lập trình trên Windows là hiểu ñược vấn ñề thông ñiệp. Chúng ta có thể tóm lược lại vấn ñề này
thông qua mô hình ở hình 5.1.
Khi người dùng hoặc một ứng dụng tác ñộng lên một ñối tượng cửa sổ, thao tác ñó sẽ ñược
chuyển thành dạng thông tin gọi là thông ñiệp (message) và ñược chuyển vào hàng ñợi của hệ
thống (Windows message queue). Mỗi dạng tác ñộng sẽ tương ứng với một mã thông ñiệp riêng
(thông ñiệp liên quan ñến menu, control là WM_COMMAND, liên quan ñến thao tác thay ñổi
kích thước cửa sổ là WM_SIZE,…). Mỗi thông ñiệp ñược Windows ñịnh nghĩa là một cấu trúc
thông tin với những giá trị gởi kèm như ñịnh danh của cửa sổ (window handle), hai word gởi
kèm (WPARAM và LPARAM) – xem lại cấu trúc thông ñiệp ở phần 1.3.1. – bài 1. Khi ñó, với
thông tin về ñịnh danh của cửa sổ có ñược, hệ thống chuyển thông ñiệp tương ứng vào hàng ñợi
thông ñiệp ứng dụng (application message queue) cho ứng dụng quản lý cửa sổ này – ñây chính
là vòng lặp GetMessage trong hàm WinMain của một ứng dụng Win32 Application.

Hình 5.1. Mô hình thông ñiệp trên Windows

Thông qua hàm DispatchMessage, Windows chuyển thông ñiệp tương ứng ñến hàm xử lý
của cửa sổ (ñược xác ñịnh bởi ñịnh danh của nó trong thông ñiệp) và ta chỉ việc nhận thông ñiệp
này trong hàm xử lý của cửa sổ tương ứng và viết code theo yêu cầu của ứng dụng.
Chúng ta ñã quen thuộc thao tác này khi viết code cho các thông ñiệp WM_COMMAND,
WM_PAINT,… trong hàm xử lý cửa sổ chính WndProc của một ứng dụng Win32 Application;
hoặc tương tự là hàm xử lý hộp thoại (ví dụ hàm About quen thuộc) với thông ñiệp
WM_INITDIALOG và WM_COMMAND.
92


Ngoài ra, chúng ta cũng ñã biết cách gởi thông ñiệp cho một ứng dụng bằng hàm
SendMessage, PostMessage hoặc bằng các cơ chế hỗ trợ riêng của từng dạng thông ñiệp (ví dụ
dùng hàm InvalidateRect tương tự việc “kích hoạt” thông ñiệp WM_PAINT).

Tương tự như vậy, trong các phần tiếp theo chúng ta sẽ lần lượt tìm hiểu các thông ñiệp về
bàn phím, thiết bị chuột và bộ ñịnh thời gian với những kiến thức cơ bản nhất thông qua các ví
dụ minh họa.
5.2. THIẾT BỊ BÀN PHÍM
Thiết bị nhập liệu cơ bản nhất luôn phải có khi sử dụng máy tính là bàn phím (keyboard). Cũng
tương tự như các dạng thao thác khác, khi chúng ta thao tác bàn phím, Windows nhận và xử lý
chúng dưới dạng thông tin thông ñiệp.
Trong phần này, chúng ta sẽ tìm hiểu khái niệm cơ bản về mô hình nhập liệu bàn phím –
phần 5.2.1, các dạng thông ñiệp cơ bản về phím (key) và ký tự (character) – phần 5.2.2 – và các
giá trị WPARAM và LPARAM gởi kèm – phần 5.2.3. Cuối cùng là một ví dụ minh họa thao tác
nhận phím + và - ñể phóng to và thu nhỏ ảnh bitmap thể hiện trên cửa sổ chính của ứng dụng.

5.2.1. Nền tảng cơ sở về bàn phím
Về cơ bản, tương tự cơ chế xuất dữ liệu ñộc lập thiết bị GDI ñã học, Windows cũng cung cấp cơ
chế nhập liệu ñộc lập thiết bị (device-independent keyboard). Thông qua trình ñiều khiển thiết bị
bàn phím ñã ñược cài ñặt, một thao tác phím sẽ ñược chuyển thành một dạng mã, gọi là mã quét
(scan code) ñể gởi ñến cho ứng dụng xử lý.
Trình ñiều khiển bàn phím thông dịch một mã quét và ánh xạ thành một dạng mã khác gọi là
mã phím ảo (virtual-key code), giá trị mã phím ảo này ñược hệ thống ñịnh nghĩa trước theo mục
ñích sử dụng của phím. Trong phần tìm hiểu các thông tin gởi kèm các thông ñiệp bàn phím
(phần 5.2.2), chúng ta sẽ tìm hiểu chi tiết hơn.
Sau khi dịch mã quét, hệ thống keyboard tạo lập thông ñiệp chứa mã quét, mã phím ảo, cùng
kiểu gõ và ñưa vào hàng ñợi của hệ thống ñể hệ thống chuyển ñến cho ứng dụng theo mô hình
thông ñiệp ở hình 5.1.
Một vấn ñề quan trọng khi nói ñến thao tác bàn phím trên Windows cũng cần tìm hiểu là
focus. Ai trong chúng ta cũng ñều biết là mặc dù Windows xử lý ñồng thời nhiều tiến trình ứng
dụng (process), tuy nhiên ở một thời ñiểm thì chỉ có một cửa sổ ứng dụng ñược xem là ñang ở
trạng thái hoạt ñộng (active), còn các cửa sổ khác ñược xem là không hoạt ñộng (inactive). Nếu
một ứng dụng ñang active (cụ thể hơn là một cửa sổ trên một ứng dụng active) thì tất cả thao tác
bàn phím thông thường ñều ñược gởi cho cửa sổ này. Khi ñó ta nói cửa sổ này ñang nhận focus.

Chúng ta ñã thao tác về vấn ñề này thông qua thông ñiệp WM_SETFOCUS ở bài 2 khi thiết lập
cho edit text nhận tất cả các thao tác phím từ người dùng.
Thông ñiệp WM_SETFOCUS ñược Windows gởi cho một cửa sổ nào ñó sau khi cửa sổ này
93


nhận ñược focus bàn phím. Tương tự, thông ñiệp WM_KILLFOCUS ñược gởi cho cửa sổ ngay
trước khi cửa sổ này bị mất focus. Ta cũng có thể dùng hàm GetFocus và SetFocus ñể nhận biết
cửa sổ ñang giữ focus hoặc thiết lập focus cho một cửa sổ nào ñó.
HWND GetFocus(VOID);
HWND SetFocus(HWND hWnd);

5.2.2. Các dạng thông ñiệp bàn phím
Thao tác căn bản của nhập liệu từ bàn phím là thao tác nhấn và nhả một phím. Khi thao tác nhấn
một phím trên bàn phím ñược thực hiện thì Windows sẽ phát sinh thông ñiệp WM_KEYDOWN
hay WM_SYSKEYDOWN và ñưa vào hàng ñợi thông ñiệp của ứng dụng hay cửa sổ giữ focus.
Tương tự, khi ta nhả phím thì thông ñiệp WM_KEYUP hay WM_SYSKEYUP sẽ ñược hệ ñiều
hành phân phát tới hàng ñợi hệ thống.
Thông ñiệp có tiếp ñầu ngữ là “SYS” thường ñược phát sinh khi người dùng nhấn các phím
gõ hệ thống. Chẳng hạn, khi người dùng nhấn phím Alt kết hợp với phím khác, thường dùng
thao tác một menu item, control,… thì hệ thống phát sinh thông ñiệp WM_SYSKEYDOWN và
WM_SYSKEYUP. Với các ứng dụng thông thường, chúng ta không quan tâm ñến các thông ñiệp
này mà chỉ cần dùng hàm DefWindowProc cuối mỗi xử lý cửa sổ ñể Windows xử lý mặc ñịnh.
Sau ñây là bảng mô tả các thông ñiệp phát sinh từ bàn phím (theo thứ tự Alphabet).
Bảng 5.1. Mô tả thông ñiệp phát sinh từ bàn phím
Thông ñiệp

WM_ACTIVATE

WM_APPCOMMAND


Nguyên nhân phát sinh

Thông ñiệp này cùng ñược gởi ñến các
cửa sổ bị kích hoạt và cửa sổ không bị
kích hoạt. Nếu các cửa sổ này cùng một
hàng ñợi nhập liệu, các thông ñiệp này sẽ
ñược truyền một cách ñồng bộ, ñầu tiên
thủ tục Windows của cửa sổ trên cùng bị
mất kích hoạt, sau ñó ñến thủ tục của cửa
sổ trên cùng ñược kích hoạt. Nếu các cửa
sổ này không nằm trong cùng một hàng
ñợi thì thông ñiệp sẽ ñược gởi một cách
không ñồng bộ, do ñó cửa sổ sẽ ñược
kích hoạt ngay lập tức.
Thông báo ñến cửa sổ rằng người dùng
ñã tạo một sự kiện lệnh ứng dụng, ví dụ
khi người dùng kích vào button sử dụng

94


chuột hay ñánh vào một kí tự kích hoạt
một lệnh của ứng dụng.

WM_CHAR

WM_DEADCHAR

WM_GETHOTKEY


Thông ñiệp này ñược gởi tới cửa sổ có
sự quan tâm khi thông ñiệp
WM_KEYDOWN ñã ñược dịch từ hàm
TranslateMessage.
Thông
ñiệp
WM_CHAR có chứa mã kí tự của phím
ñược nhấn.
Thông ñiệp này ñược gởi tới cửa sổ có
sự quan tâm khi thông ñiệp
WM_KEYUP ñã ñược xử lý từ hàm
TranslateMessage. Thông ñiệp này
xác nhận mã kí tự khi một phím dead
key ñược nhấn. Phím dead key là phím
kết hợp ñể tạo ra kí tự ngôn ngữ không
có trong tiếng Anh (xuất hiện trong bàn
phím hỗ trợ ngôn ngữ khác tiếng Anh).
Ứng dụng gởi thông ñiệp này ñể xác
ñịnh một phím nóng liên quan ñến một
cửa sổ. ðể gởi thông ñiệp này thì dùng
hàm SendMessage.

WM_HOTKEY

Thông ñiệp này ñược gởi khi người dùng
nhấn một phím nóng ñược ñăng kí trong
RegisterHotKey.

WM_KEYDOWN


Thông ñiệp này ñược gởi cho cửa sổ
nhận ñược sự quan tâm khi người dùng
nhấn một phím trên bàn phím. Phím này
không phải phím hệ thống (Phím không
có nhấn phím Alt).

WM_KEYUP

Thông ñiệp này ñược gởi cho cửa sổ
nhận ñược sự quan tâm khi người dùng
nhả một phím ñã ñược nhấn trước
ñó.Phím này không phải phím hệ thống
(Phím không có nhấn phím Alt).

95


WM_KILLFOCUS

Thông ñiệp này ñược gởi tới cửa sổ ñang
nhận ñược sự quan tâm trước khi nó mất
quyền này.

WM_SETFOCUS

Thông ñiệp này ñược gởi tới cửa sổ sau
khi cửa sổ nhận ñược sự quan tâm của
Windows


WM_SETHOTKEY

Ứng dụng sẽ gởi thông ñiệp này ñến cửa
sổ liên quan ñến phím nóng, khi người
dùng nhấn một phím nóng thì cửa sổ
tương ứng liên quan tới phím nóng này
sẽ ñược kích hoạt.

WM_SYSCHAR

Thông ñiệp này sẽ ñược gởi tới cửa sổ
nhận ñược sự quan tâm khi hàm
TranslateMesage xử lý xong thông
ñiệp WM_SYSKEYDOWN. Thông
ñiệp WM_SYSCHAR chứa mã cửa
phím hệ thống. Phím hệ thống là phím có
chứa phím Alt và tổ hợp phím khác.

WM_SYSDEADCHAR

Thông ñiệp này ñược gởi tới cửa sổ nhận
ñược sự quan tâm khi một thông ñiệp
WM_SYSKEYDOWN ñược biên dịch
trong hàm TranslateMessage. Thông
ñiệp này xác nhận mã kí tự của phím hệ
thống deadkey ñược nhấn.

WM_SYSKEYDOWN

Thông ñiệp này ñược gởi tới cửa sổ nhận

ñược sự quan tâm khi người dùng nhấn
phím F10 hay nhấn Alt trước khi nhấn
phím khác. Thông ñiệp này cũng ñược
gởi khi không có cửa sổ nào nhận ñược
sự quan tâm và lúc này thì cửa sổ nhận
ñược là cửa sổ ñang ñược kích hoạt
(Active).

WM_SYSKEYUP

Thông ñiệp này ñược gởi tới cửa sổ nhận
ñược sự quan tâm khi người dùng nhấn
một phím mà trước ñó ñã giữ phím Alt.

96


Cũng tương tự nếu không có cửa sổ nào
nhận ñược sự quan tâm thì thông ñiệp
này sẽ ñược gởi cho cửa sổ ñang ñược
kích hoạt.

5.2.3. Tham số wParam và lPram của thông ñiệp bàn phím
Trong phạm vi môn học chúng ta chỉ tìm hiểu thông tin của các thông ñiệp về phím (key) và ký
tự (character).
Như ñã giới thiệu ở trên, mỗi khi chúng ta nhấn phím thì hệ thống gởi cho ứng dụng ñang giữ
focus các thông ñiệp WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, và
WM_SYSKEYUP. Khi ñó, tham số wParam sẽ nhận giá trị mã phím ảo, và tham số lParam
chứa thông tin chi tiết về phím ñược gõ vào.
Thông qua hàm TranslateMessage, thông ñiệp WM_KEYDOWN ñược thông dịch sang

thêm thông ñiệp WM_CHAR. Tương tự ta có các thông ñiệp WM_DEADCHAR,
WM_SYSCHAR, và WM_SYSDEADCHAR từ các thông ñiệp WM_KEYUP,
WM_SYSKEYDOWN, và WM_SYSKEYUP.
Giống như các thông ñiệp về phím, giá trị lParam của các thông ñiệp về ký tự xác ñịnh
thông tin chi tiết của ký tự phím nhận ñược; còn wParam thì chứa mã ký tự (thay vì mã phím
ảo).
Khái niệm phím ảo trên Windows gắn liền với ñặc trưng ñộc lập thiết bị (device
independent) của bàn phím. Như ñã ñề cập ở trên, khi một phím ñược nhấn thì phần cứng vật lý
phát sinh ra một mã quét (scan code). Trên các bàn phím tương thích IBM các phím ñược gán
với các mã cụ thể, ví dụ phím W là 17, phím E là 18, và phím R là 19... Cách sắp xếp này thuần
túy dựa trên vị trí vật lý của phím trên bàn phím. Tuy nhiên, những người xây dựng nên
Windows nhận thấy rằng nếu dùng trực tiếp mã quét thì sẽ không thích hợp (các bàn phím luôn
phải ñược sản xuất dựa trên các thông số ñã có). Do ñó họ xử lý bàn phím bằng cách ñộc lập
thiết bị hơn, bằng cách tạo ra một bảng ñịnh nghĩa tập giá trị phím tổng quát mà sau này ñược
gọi là mã phím ảo. Một số giá trị bàn phím ảo mà ta không thấy xuất hiện trên bàn phím IBM
tương thích nhưng có thể tìm thấy chúng trong bàn phím của các nhà sản xuất khác hay chúng
ñược ñể dành cho bàn phím trong tương lai. Các giá trị của phím ảo này ñược ñịnh nghĩa trong
tập tin tiêu ñề WINUSER.H và ñược bắt ñầu với các tiếp ñầu ngữ VK_xxxxx.
Sau ñây là các bảng mô tả các phím ảo thông dụng trong Windows giao tiếp với bàn phím
IBM tương thích.
Bảng 5.2. Mô tả các phím ảo
Thập
phân

Thập
lục
phân

Hằng phím ñịnh
nghĩa trong

WINUSER.H

Windows
dùng

Bàn phím
tương thích

97

IBM


1

01

VK_LBUTTON

Nút chuột trái

2

02

VK_RBUTTON

Nút chuột phải

3


03

VK_CANCEL

4

04

VK_MBUTTON

X

Ctrl –Break
Nút chuột giữa

Các thông ñiệp trên chỉ ñược nhận khi dùng chuột, ta không bao giờ nhận ñược thông ñiệp
trên nếu gõ từ bàn phím. Không nên dùng phím Ctrl–Break trong ứng dụng Windows, phím này
thường ñược ngắt các ứng dụng trong DOS.
Những giá trị phím ảo tiếp sai dành cho các phím Backspace, Tab, Enter, Escape, và
Spacebar ñược dùng nhiều trong chương trình Windows.
Bảng 5.3. Mô tả các phím ảo (tiếp theo)
Thập
phân

Thập
lục
phân

Hằng phím ñịnh

nghĩa trong
WINUSER.H

Windows
dùng

Bàn phím IBM
tương thích

8

08

VK_BACK

X

Backspace

9

09

VK_TAB

X

Tab

12


0C

VK_CLEAR

X

Phím số 5 trong
NumPad với ñèn
Numlock tắt.

13

0D

VK_RETURN

X

Enter (hai phím)

16

10

VK_SHIFT

X

Shift (hai phím)


17

11

VK_CONTROL

X

Ctrl (hai phím)

18

12

VK_MENU

X

Alt (hai phím)

19

13

VK_PAUSE

X

Pause


20

14

VK_CAPITAL

X

Caps Lock

27

1B

VK_ESCAPE

X

Esc

32

20

VK_SPACE

X

Spacebar


Bảng mô tả phím ảo tiếp sau ñây là các phím thường ñược sử dụng nhiều trong Windows.
Bảng 5.4. Mô tả các phím ảo (tiếp theo)
Thập
phân

Thập
lục
phân

Hằng phím ñịnh
nghĩa trong
WINUSER.H

Windows
dùng

Bàn phím IBM
tương thích

33

21

VK_PRIOR

X

Page Up


34

22

VK_NEXT

X

Page Down

35

23

VK_END

X

End

98


×