intTypePromotion=1

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)

Chia sẻ: Hoa La Hoa | Ngày: | Loại File: PDF | Số trang:73

0
114
lượt xem
45
download

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)

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Phần 2 Giáo trình Lập trình C trên Windows tiếp tục giới thiệu đến bạn đọc nội dung từ bài 4 đến bài 6 về các vấn đề như: Giao tiếp thiết bị đồ họa, bàn phím, thiết bị chuột và bộ định thời gian, cơ bản về lập trình với MFC. Giáo trình cung cấp cho bạn đọc những kiến thức tổng quan cũng như về kỹ thuật lâp trình C trên Windows. Hi vọng giáo trình là tài liệu tham khảo thiết thực và bổ ích cho bạn đọc trong việc nghiên cứu lập trình.

Chủ đề:
Lưu

Nội dung Text: 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)

  1. 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 Windows- based 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, 2 LPARAM lParam) 3 { 4 int wmId, wmEvent; 5 PAINTSTRUCT ps; 6 HDC hdc; 7 TCHAR szHello[MAX_LOADSTRING]; 8 LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING); 9 COLORREF crOldColor; 10 static COLORREF crColor = RGB(0, 0, 0); 11 static UINT nAlign = DT_CENTER; 12 13 switch (message) 14 { 15 case WM_COMMAND: 16 wmId = LOWORD(wParam); 17 wmEvent = HIWORD(wParam); 18 // Parse the menu selections: 19 switch (wmId) 20 { 21 case IDM_BLACK: 22 crColor = RGB(0, 0, 0); 23 break; 24 case IDM_RED: 25 crColor = RGB(255, 0, 0); 26 break; 27 case IDM_GREEN: 28 crColor = RGB(0, 255, 0); 29 break; 30 case IDM_BLUE: 31 crColor = RGB(0, 0, 255); 32 break; 33 case IDM_LEFT: 34 nAlign = DT_LEFT; 79
  7. 35 break; 36 case IDM_CENTER: 37 nAlign = DT_CENTER; 38 break; 39 case IDM_RIGHT: 40 nAlign = DT_RIGHT; 41 break; 42 case IDM_ABOUT: 43 DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, 44 (DLGPROC)About); 45 break; 46 case IDM_EXIT: 47 DestroyWindow(hWnd); 48 break; 49 default: 50 return DefWindowProc(hWnd, message, wParam, 51 lParam); 52 } 53 InvalidateRect(hWnd, NULL, TRUE); 54 break; 55 case WM_PAINT: 56 hdc = BeginPaint(hWnd, &ps); 57 // TODO: Add any drawing code here... 58 crOldColor = SetTextColor(hdc, crColor); 59 RECT rt; 60 GetClientRect(hWnd, &rt); 61 DrawText(hdc, szHello, strlen(szHello), &rt, nAlign); 62 SetTextColor(hdc, crOldColor); 63 EndPaint(hWnd, &ps); 64 break; 65 case WM_DESTROY: 66 PostQuitMessage(0); 67 break; 68 default: 69 return DefWindowProc(hWnd, message, wParam, lParam); 70 } 71 return 0; 72 } 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
  8. 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
  9. 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 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, 2 LPARAM lParam) 3 { 4 int wmId, wmEvent; 5 PAINTSTRUCT ps; 6 HDC hdc; 82
  10. 7 static int nShape = 1; // 1: Line; 2: Rectangle; 3: Ellipse 8 static COLORREF crColor = RGB(0, 0, 0); 9 HPEN hPen, hOldPen; 10 HBRUSH hBrush, hOldBrush; 11 RECT rt; 12 POINT ptCenter; 13 14 switch (message) 15 { 16 case WM_COMMAND: 17 wmId = LOWORD(wParam); 18 wmEvent = HIWORD(wParam); 19 // Parse the menu selections: 20 switch (wmId) 21 { 22 case IDM_LINE: 23 nShape = 1; 24 break; 25 case IDM_RECTANGLE: 26 nShape = 2; 27 break; 28 case IDM_ELLIPSE: 29 nShape = 3; 30 break; 31 case IDM_BLACK: 32 crColor = RGB(0, 0, 0); 33 break; 34 case IDM_RED: 35 crColor = RGB(255, 0, 0); 36 break; 37 case IDM_GREEN: 38 crColor = RGB(0, 255, 0); 39 break; 40 case IDM_BLUE: 41 crColor = RGB(0, 0, 255); 42 break; 43 case IDM_ABOUT: 44 DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, 45 (DLGPROC)About); 46 break; 47 case IDM_EXIT: 48 DestroyWindow(hWnd); 49 break; 50 default: 51 return DefWindowProc(hWnd, message, wParam, 52 lParam); 53 } 54 InvalidateRect(hWnd, NULL, TRUE); 55 break; 56 case WM_PAINT: 57 hdc = BeginPaint(hWnd, &ps); 58 59 hPen = CreatePen(PS_SOLID, 1, crColor); 60 hBrush = CreateSolidBrush(crColor); 61 hOldPen = (HPEN)SelectObject(hdc, hPen); 62 hOldBrush = (HBRUSH)SelectObject(hdc, hBrush); 63 64 GetClientRect(hWnd, &rt); 65 ptCenter.x = (rt.left+rt.right)/2; 66 ptCenter.y = (rt.top+rt.bottom)/2; 67 68 switch(nShape) 69 { 70 case 1: 71 MoveToEx(hdc, ptCenter.x-100, ptCenter.y-50, NULL); 72 LineTo(hdc, ptCenter.x+100, ptCenter.y+50); 73 break; 83
  11. 74 case 2: 75 Rectangle(hdc, ptCenter.x-100, ptCenter.y-50, 76 ptCenter.x+100, ptCenter.y+50); 77 break; 78 case 3: 79 Ellipse(hdc, ptCenter.x-100, ptCenter.y-50, 80 ptCenter.x+100, ptCenter.y+50); 81 break; 82 } 83 84 SelectObject(hdc, hOldPen); 85 SelectObject(hdc, hOldBrush); 86 DeleteObject(hPen); 87 DeleteObject(hBrush); 88 89 EndPaint(hWnd, &ps); 90 break; 91 case WM_DESTROY: 92 PostQuitMessage(0); 93 break; 94 default: 95 return DefWindowProc(hWnd, message, wParam, lParam); 96 } 97 return 0; 98 } 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
  12. ñư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 là Elephant (IDM_ELEPHANT), Pine (IDM_PINE) và 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
  13. 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; LONG bmWidth; // Chiều rộng của ảnh LONG bmHeight; // Chiều cao của ảnh LONG bmWidthBytes; WORD bmPlanes; WORD bmBitsPixel; LPVOID bmBits; } BITMAP, *PBITMAP; int GetObject( HGDIOBJ hgdiobj, // Trỏ ñến ñối tượng GDI 86
  14. 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 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, 2 LPARAM lParam) 3 { 4 int wmId, wmEvent; 5 PAINTSTRUCT ps; 6 static int nIDB = IDB_ELEPHANT; // ID cua bitmap trong resource 7 HDC hdc, hdcMem; 8 HBITMAP hBitmap; 9 BITMAP bitmap; 10 RECT rt; 11 POINT ptOrgBmp; 12 13 switch (message) 14 { 15 case WM_COMMAND: 16 wmId = LOWORD(wParam); 17 wmEvent = HIWORD(wParam); 18 // Parse the menu selections: 19 switch (wmId) 20 { 21 case IDM_ELEPHANT: 22 nIDB = IDB_ELEPHANT; 23 break; 24 case IDM_PINE: 25 nIDB = IDB_PINE; 26 break; 27 case IDM_MUSHROOM: 28 nIDB = IDB_MUSHROOM; 29 break; 30 case IDM_ABOUT: 31 DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, 32 (DLGPROC)About); 33 break; 34 case IDM_EXIT: 35 DestroyWindow(hWnd); 36 break; 37 default: 38 return DefWindowProc(hWnd, message, wParam, 39 lParam); 40 } 41 InvalidateRect(hWnd, NULL, TRUE); 42 break; 43 case WM_PAINT: 44 hdc = BeginPaint(hWnd, &ps); 45 hdcMem = CreateCompatibleDC(hdc); 46 47 hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(nIDB)); 48 GetObject(hBitmap, sizeof(BITMAP), &bitmap); 49 SelectObject(hdcMem, hBitmap); 50 51 GetClientRect(hWnd, &rt); 52 ptOrgBmp.x = (rt.left + rt.right - bitmap.bmWidth)/2; 53 ptOrgBmp.y = (rt.top + rt.bottom - bitmap.bmHeight)/2; 54 55 BitBlt(hdc, ptOrgBmp.x, ptOrgBmp.y, bitmap.bmWidth, 56 bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY); 57 58 DeleteDC(hdcMem); 87
  15. 59 EndPaint(hWnd, &ps); 60 break; 61 case WM_DESTROY: 62 PostQuitMessage(0); 63 break; 64 default: 65 return DefWindowProc(hWnd, message, wParam, lParam); 66 } 67 return 0; 68 } 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
ADSENSE
ADSENSE

CÓ THỂ BẠN MUỐN DOWNLOAD

 

Đồng bộ tài khoản
2=>2