Bài giảng Lập trình Windows - Bài 5: Bàn phím, thiết bị chuột và bộ định thời gian

doc 40 trang phuongnguyen 6180
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình Windows - Bài 5: Bàn phím, thiết bị chuột và bộ định thời gian", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • docbai_giang_lap_trinh_windows_bai_5_ban_phim_thiet_bi_chuot_va.doc

Nội dung text: Bài giảng Lập trình Windows - Bài 5: Bàn phím, thiết bị chuột và bộ định thời gian

  1. 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
  2. Lập trình Windows đ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. Thao tác từ người Chuyển thành Hàng đợi thông dùng hoặc ứng thông điệp điệp của hệ thống dụng khác Nếu có thao tác Chuyển vào Hàng đợi thông Hàm xử lý thông điệp của ứng điệp của cửa sổ dụng DispatchMessage GetMessage Vòng lặp thông điệp 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 2
  3. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian đ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. 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 3
  4. Lập trình Windows 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ổ 4
  5. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian 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 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 5
  6. Lập trình Windows đ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). Thông điệp 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 WM_ACTIVATE 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ụ WM_APPCOMMAND khi người dùng kích vào button sử dụng chuột hay đánh vào một kí tự kích hoạt một lệnh của ứng dụng. Thông điệp này được gởi tới cửa sổ có WM_CHAR sự quan tâm khi thông điệp WM_KEYDOWN đã được dịch từ hàm TranslateMessage. Thông điệp 6
  7. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian 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 WM_DEADCHAR 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 WM_GETHOTKEY đị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. Thông điệp này được gởi khi người dùng WM_HOTKEY nhấn một phím nóng được đăng kí trong RegisterHotKey. 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 WM_KEYDOWN 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). Thông điệp này được gởi cho cửa sổ WM_KEYUP nhận được sự quan tâm khi người dùng 7
  8. Lập trình Windows 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). Thông điệp này được gởi tới cửa sổ đang WM_KILLFOCUS nhận được sự quan tâm trước khi nó mất quyền này. Thông điệp này được gởi tới cửa sổ sau WM_SETFOCUS khi cửa sổ nhận được sự quan tâm của Windows Ứ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 WM_SETHOTKEY 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. 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 WM_SYSCHAR đ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. 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_SYSDEADCHAR 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ệ 8
  9. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian thống deadkey được nhấn. 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 WM_SYSKEYDOWN 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). 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. WM_SYSKEYUP 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. Bảng 5.1. Mô tả thông điệp phát sinh từ bàn phím 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. 9
  10. Lập trình Windows 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. 10
  11. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian 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. Thập Thập Hằng phím định Windows Bàn phím IBM phân lục nghĩa trong dùng tương thích phân WINUSER.H 1 01 VK_LBUTTON Nút chuột trái 2 02 VK_RBUTTON Nút chuột phải 3 03 VK_CANCEL X Ctrl –Break 4 04 VK_MBUTTON Nút chuột giữa Bảng 5.2. Mô tả các phím ảo 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. Thập Thập Hằng phím định Windows Bàn phím IBM phân lục nghĩa trong dùng tương thích phân WINUSER.H 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) 11
  12. Lập trình Windows 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 5.3. Mô tả các phím ảo (tiếp theo) 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. Thập Thập Hằng phím định Windows Bàn phím IBM phân lục nghĩa trong dùng tương thích phân WINUSER.H 33 21 VK_PRIOR X Page Up 34 22 VK_NEXT X Page Down 35 23 VK_END X End 36 24 VK_HOME X Home 37 25 VK_LEFT X Phím trái 38 26 VK_UP X Phím mũi tên lên 39 27 VK_RIGHT X Phím phải 40 28 VK_DOWN X Phím xuống 41 29 VK_SELECT - 42 2A VK_PRINT - 43 2B VK_EXECUTE - 12
  13. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian 44 2C VK_SNAPHOT Print Screen 45 2D VK_INSERT X Insert 46 2E VK_DELETE X Delete 47 2F VK_HELP Bảng 5.4. Mô tả các phím ảo (tiếp theo) Một số các phím ảo như VK_SELECT, VK_PRINT, VK_EXECUTE, hay VK_HELP thường chỉ xuất hiện trong các bàn phím giả lập mà chúng ta ít khi gặp. Tiếp sau là mã phím ảo của các phím số và phím chữ trên bàn phím chính. Trong bàn phím bổ sung phím Num Pad được qui định riêng. Thập Thập Hằng phím định Windows Bàn phím IBM phân lục nghĩa trong dùng tương thích phân WINUSER.H 48- 30- Không X Từ phím 0 đến 57 39 phím 9 trên bàn phím chính 65- 41- Không X Từ phím A đến 90 5A phím Z. Bảng 5.5. Mô tả các phím chữ và số trên bàn phím chính Mã phím ảo trong bảng trên bằng với mã ASCII của kí tự mà phím thể hiện. Trong các ứng dụng Windows thường không dùng mã phím ảo này mà thay vào đó ứng dụng chỉ xử lý thông điệp kí tự dành cho kí tự có mã ASCII. Cuối cùng là mã phím ảo của bàn phím mở rộng Num Pad (bên phải của bàn phím) và các phím chức năng (F1, F2, F3 ). 13
  14. Lập trình Windows Thập Thập Hằng phím định Windows Bàn phím IBM phân lục nghĩa trong dùng tương thích phân WINUSER.H 96- 60- VK_NUMPAD0 Phím 0 – 9 khi 105 69 đến đèn Num Lock VK_NUMPAD9 được bật 106 6A VK_MULTIPLY Phím * 107 6B VK_ADD Phím + 108 6C VK_SEPARATOR 109 6D VK_SUBTRACT Phím - 110 6E VK_DECIMAL Phím . 111 6F VK_DIVIDE Phím / 112- 70- VK_F1 đến X Phím F1 đến F10 121 79 VK_F10 122- 7A- VK_F11 đến Phím F11 đến 135 87 VK_F24 F24 144 90 VK_NUMLOCK Num Lock 145 91 VK_SCROLL Scroll Lock Bảng 5.6. Mô tả các phím bổ sung Một số các phím ảo như F13- F24 thì được Windows tạo ra dự phòng cho các bàn phím sau này. Thường thì ứng dụng Windows chỉ dùng phím F1 – F10 mà thôi. Đó là các bảng mã phím ảo của các thông điệp về key. Trong trường hợp thao tác ký tự, ta sử dụng luôn mã ký tự có được (ví dụ 0x0D là ký tự xuống dòng) hoặc có thể lấy biến ký tự bằng (TCHAR)wParam. 14
  15. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian Cuối cùng, thông tin chứa trong 32 bit của tham số lParam của các thông điệp trên được mô tả trong bảng sau: Bit Nội dung nhận dạng 0-15 Chứa số lần lặp của thông điệp đang được gởi (WM_KEYDOWN, WM_CHAR, ). Nếu chúng ta chỉ nhấn rồi nhả ra thì giá trị này bằng 1, nếu chúng ta giữ luôn phím thì số lần này sẽ tăng theo. 16-23 Chứa mã quét OEM (Original Equipment Manufacturer) do nhà sản xuất phần cứng quy định, ta hầu như không quan tâm đến thông tin này. 24 Cờ này có giá trị 1 khi phím được nhấn là phím thuộc nhóm phím mở rộng trên bàn phím IBM tương thích hay phím Alt, Ctrl bên phải bàn phím được nhấn. Windows ít quan tâm đến giá trị của cờ này. 25-28 Để dành, không sử dụng. 29 Mã ngữ cảnh của phím (Context code), nếu cờ này có giá trị bằng 1 thì phím Alt được nhấn, điều này tương ứng với thông điệp WM_SYSKEYDOWN hay WM_SYSKEYUP được phát sinh 30 Trạng thái của phím trước đó, bit này bằng 0 nếu phím ở trạng thái nhả trước đó, và bằng 1 nếu phím ở trạng thái nhấn. 31 Trạng thái dịch chuyển của phím, cờ này bằng 0 nếu phím đang được nhấn, và bằng 1 nếu phím nhả. Bảng 5.7. Mô tả thông tin chứa trong tham số lParam 15
  16. Lập trình Windows 5.2.4. Ví dụ ZoomAnh sử dụng thao tác bàn phím Trong bài học này, chúng ta chỉ minh họa thao tác đơn giản về phím nhấn để thể hiện thao tác zoom ảnh bitmap (theo quy trình đã được học ở bài 4). Ứng dụng thể hiện một ảnh bitmap có trong resource ra giữa vùng làm việc của cửa sổ ứng dụng. Khi người dùng nhấn phím + thì phóng lớn gấp đôi nếu ảnh vẫn có thể thể hiện trong phạm vi vùng làm việc, và thu nhỏ một nửa khi chọn phím -. Đầu tiên, ta cũng tạo project dạng A typical “Hello World!” application có tên là ZoomAnh. Sau đó tạo một đối tượng ảnh bitmap sẽ được dùng để hiển thị, định danh là IDB_ANH, và vẽ một hình ảnh bất kỳ (ở đây là hình chữ A) – xem hình 5.2. Hình 5.2. Tạo lập resource IDB_ANH 16
  17. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian Để hiển thị ảnh ra chính giữa vùng làm việc của ứng dụng, ta cần xác định vị trí tâm màn hình. Ta dùng một biến tĩnh static POINT ptCenter trong hàm WndProc để lưu vị trí tâm vùng làm việc và biến tĩnh static LPARAM dwSize để lưu thông số kích thước vùng làm việc. Giá trị này được cập nhật trong thông điệp WM_SIZE mỗi khi kích thước cửa sổ bị thay đổi. Thông qua vị trí tâm màn hình, kết hợp với kích thước ảnh cần thể hiện ta dễ dàng thể hiện ảnh bằng các hàm zoom ảnh trong thông điệp WM_PAINT. Chiều rộng và chiều cao ảnh được lưu trong cấu trúc biến tĩnh static BITMAP bmpInfo được khởi gán tại thông điệp WM_CREATE. Ngoài ra, ta dùng thêm hai biến static int nBmpWidth, nBmpHeight thể hiện kích thước đang zoom và được cập nhật mỗi khi thực hiện thao tác nhấn phím + hoặc - trong thông điệp WM_KEYDOWN trước khi gọi hàm InvalidateRect để kích hoạt thông điệp WM_PAINT xuất lại ảnh. Ta có đoạn code hàm WndProc như sau: 1 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, 2 LPARAM lParam) 3 { 4 int wmId, wmEvent; 5 PAINTSTRUCT ps; 6 HDC hDC, hSrcMemDC, hDestMemDC; 7 HBITMAP hOldBmp, hBlankBmp, hOldBlankBmp; 8 static HBITMAP hBmp; 9 static BITMAP bmpInfo; 10 static int nBmpWidth, nBmpHeight; 11 static LPARAM dwSize; 12 static POINT ptCenter; 13 POINT ptOrgBmp; 14 17
  18. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian 15 switch (message) 16 { 17 case WM_CREATE: 18 hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_ANH)); 19 GetObject(hBmp, sizeof(BITMAP), &bmpInfo); 20 nBmpWidth = bmpInfo.bmWidth; 21 nBmpHeight = bmpInfo.bmHeight; 22 break; 23 case WM_SIZE: 24 dwSize = lParam; 25 ptCenter.x = LOWORD(lParam)/2; 26 ptCenter.y = HIWORD(lParam)/2; 27 break; 28 case WM_COMMAND: 29 wmId = LOWORD(wParam); 30 wmEvent = HIWORD(wParam); 31 // Parse the menu selections: 32 switch (wmId) 33 { 34 case IDM_ABOUT: 35 DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, 36 (DLGPROC)About); 37 break; 38 case IDM_EXIT: 39 DestroyWindow(hWnd); 40 break; 41 default: 42 return DefWindowProc(hWnd, message, wParam, 43 lParam); 44 } 45 break; 46 case WM_PAINT: 47 hDC = BeginPaint(hWnd, &ps); 48 49 hSrcMemDC = CreateCompatibleDC(hDC); 50 hDestMemDC = CreateCompatibleDC(hDC); 51 52 hBlankBmp = CreateCompatibleBitmap(hDC, LOWORD(dwSize), 53 HIWORD(dwSize)); 54 55 hOldBmp = (HBITMAP)SelectObject(hSrcMemDC, hBmp); 56 hOldBlankBmp = (HBITMAP)SelectObject(hDestMemDC, 57 hBlankBmp); 58 59 ptOrgBmp.x = ptCenter.x - nBmpWidth/2; 60 ptOrgBmp.y = ptCenter.y - nBmpHeight/2; 61 62 StretchBlt(hDestMemDC, ptOrgBmp.x, ptOrgBmp.y, nBmpWidth, 63 nBmpHeight, hSrcMemDC, 0, 0, bmpInfo.bmWidth, 18
  19. Lập trình Windows 64 bmpInfo.bmHeight, SRCCOPY); 65 BitBlt(hDC, ptOrgBmp.x, ptOrgBmp.y, nBmpWidth, nBmpHeight, 66 hDestMemDC, ptOrgBmp.x, ptOrgBmp.y, SRCCOPY); 67 68 SelectObject(hSrcMemDC, hOldBmp); 69 SelectObject(hDestMemDC, hOldBlankBmp); 70 DeleteObject(hBlankBmp); 71 DeleteDC(hSrcMemDC); 72 DeleteDC(hDestMemDC); 73 74 EndPaint(hWnd, &ps); 75 break; 76 case WM_KEYDOWN: 77 switch(wParam) 78 { 79 case VK_ADD: // Ma ao ky tu + 80 if(nBmpWidth*2 1 && nBmpHeight>1) 90 { 91 nBmpWidth/=2; 92 nBmpHeight/=2; 93 InvalidateRect(hWnd, NULL, TRUE); 94 } 95 break; 96 } 97 break; 98 case WM_DESTROY: 99 PostQuitMessage(0); 100 break; 101 default: 102 return DefWindowProc(hWnd, message, wParam, lParam); 103 } 104 return 0; 105 } 5.3. THIẾT BỊ CHUỘT Trong môi trường giao tiếp đồ họa như Windows, việc sử dụng thiết bị định vị chuột là hết sức cần thiết. Nhờ thiết bị chuột ta 19
  20. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian có thể di chuyển đến một vị trí bất kỳ trên màn hình và chỉ cần thực hiện thao tác click nút chuột để xử lý một công việc nào đó. 5.3.1. Cơ bản về thiết bị chuột trên Windows Về cơ bản Windows hỗ trợ các loại thiết bị chuột có một nút, hai và ba nút, ngoài ra Windows còn có thể dùng thiết bị khác như joystick hay bút vẽ để bắt chước thiết bị chuột. Các ứng dụng trong Windows thường né việc dùng các nút thứ hai và nút thứ ba, để có thể sử dụng tốt trên các thiết bị chuột chỉ có một nút. Thông thường nút thứ hai của thiết bị chuột phải được dùng cho chức năng gọi thực đơn ngữ cảnh (context menu). Theo nguyên tắc, ta có thể kiểm tra xem có tồn tại thiết bị chuột hay không bằng cách dùng hàm GetSystemMetrics. int GetSystemMetrics(int nIndex); Ta dùng giá trị nIndex là SM_MOUSEPRESENT để kiểm tra có tồn tại thiết bị chuột trong hệ thống hay không – Giá trị trả về là TRUE (1) nếu có và FALSE (0) nếu không. Và dùng SM_CMOUSEBUTTONS cho nIndex để kiểm tra số nút chuột của thiết bị chuột có trong hệ thống; giá trị trả về là số nút nhấn và bằng 0 nếu không có thiết bị chuột được cài đặt. Khi người dùng di chuyển thiết bị chuột thì Windows cũng di chuyển một ảnh bitmap nhỏ trên màn hình, ảnh này được gọi là con trỏ chuột (mouse cursor). Con trỏ chuột này có một điểm gọi là điểm nóng (hot spot), điểm nóng này nằm trong hình bitmap, và do người tạo con trỏ chuột quyết định. Khi chúng ta tham chiếu tới một vị trí của con trỏ chuột thì chính là chúng ta đang dùng tọa độ của điểm nóng này. 20
  21. Lập trình Windows Để đưa một con trỏ chuột vào ứng dụng hInstance, cách đơn giản nhất là dùng hàm LoadCursor để nạp một đối tượng con trỏ chuột lpCursorName từ tài nguyên. HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName); Thông thường ta sử dụng các đối tượng con trỏ chuột chuẩn được Windows định nghĩa sẵn khi gọi hàm này, với các giá trị của lpCursorName tương ứng là IDC_ARROW (hình mũi tên), IDC_HELP (hình dấu hỏi đi kèm mũi tên), IDC_WAIT (hình đồng hồ cát), IDC_CROSS (hình dấu cộng), . Chúng ta có thể thử thay đổi các giá trị này cho đối tượng con trỏ chuột của lớp cửa sổ ứng dụng (xem phần thiết lập thông số lớp cửa sổ trong hàm MyRegisterClass của các ứng dụng đã làm) và xem kết quả thực thi của ứng dụng. 5.3.2. Xử lý các thông điệp từ thiết bị chuột Với thiết bị chuột ta có thể thực hiện các thao tác như click chuột (nhấn và thả nút nhấn), double click (nhấn và thả nhanh 2 lần liền) hoặc kéo rê (move) con trỏ chuột trên màn hình, và tất cả các thao tác này đều được chuyển thành các thông điệp gởi cho cửa sổ nhận tác động tương ứng. Như vậy, bất cứ lúc nào cũng có một cửa sổ nhận được một thông điệp về thiết bị chuột. Windows định nghĩa 21 thông điệp được phát sinh từ thiết bị chuột, phân thành hai nhóm chính. Nhóm các thông điệp trong vùng làm việc (client area messages) gởi cho cửa sổ khi thiết bị chuột nằm ở trong vùng làm việc (11 thông điệp). Nhóm còn lại là các thông điệp không thuộc vùng làm việc (nonclient-area messages), nhóm này 21
  22. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian thường được các ứng dụng bỏ qua không xử lý. Trong các ví dụ minh họa, chúng ta cũng không thao tác các thông điệp này. 5.3.2.1. Thông điệp chuột trong vùng làm việc Khi con trỏ chuột di chuyển vào vùng làm việc của một cửa sổ, Windows phát sinh thông điệp có tên là WM_MOUSEMOVE. Khi người dùng click chuột, tuỳ theo cách thao tác mà chúng ta có các thông điệp được liệt kê trong bảng 5.1. sau: Nút Nhấn Thả Nhấn đúp Trái WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK Giữa WM_MBUTTONDOWN WM_MBUTTONUP WM_MBUTTONDBLCLK Phải WM_RBUTTONDOWN WM_MBUTTONUP WM_RBUTTONDBLCLK Bảng 5.8. Mô tả thông điệp click chuột trong vùng làm việc Thủ tục cửa sổ của ứng dụng sẽ nhận được thông điệp của nút chuột giữa nếu máy tính cài thiết bị chuột có 3 nút. Tương tự như vậy với thông điệp thiết bị chuột phải, chúng ta cần có thiết bị chuột dùng 2 nút. Để nhận được thông điệp kích đúp thiết bị chuột thủ tục cửa sổ phải khai báo nhận thông điệp này. Wndclass.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS; Trong thông điệp phát sinh từ thiết bị chuột thì tham số lParam sẽ chứa vị trí của thiết bị chuột, 16 byte thấp sẽ chứa giá trị tọa độ x, còn 16 byte cao sẽ chứa giá trị tọa độ của y. Để lấy ra hai giá trị này ta có thể dùng macro là LOWORD và HIWORD. 22
  23. Lập trình Windows MK_LBUTTON Nút chuột trái nhấn MK_MBUTTON Nút chuột giữa nhấn MK_RBUTTON Nút chuột phải nhấn MK_SHIFT Phím Shift được nhấn MK_CONTROL Phím Ctrl được nhấn Bảng 5.9. Mô tả trạng thái của chuột và phím nhấn Giá trị wParam cho biết trạng thái của nút nhấn, phím Shift, và phím Ctrl. Chúng ta có thể kiểm tra các trạng thái này bằng cách dùng bit mặt nạ được định nghĩa trước trong WINUSER.H. Các mặt nạ này được bắt đầu bằng tiền tố MK_xxx (Mouse Key). Bảng 5.2 liệt kê một số giá trị trạng thái nút nhấn thường gặp. Như vậy, khi lập trình chúng ta chỉ việc chặn thông điệp tương ứng cần xử lý trong hàm xử lý của cửa sổ, và căn cứ vào các thông số vị trí (lParam), trạng thái phím và nút nhấn (wParam) gởi kèm để viết các xử lý khác nhau. 5.3.2.2. Thông điệp của thiết bị chuột ngoài vùng làm việc Các thông điệp của thiết bị chuột vừa tìm hiểu trong phần trước đều được phát sinh khi thiết bị chuột nằm trong vùng làm việc của cửa sổ. Khi di chuyển con trỏ chuột ra khỏi vùng làm việc của cửa sổ nhưng vẫn ở trong phạm vi của cửa sổ (trên thanh tiêu đề, menu, thanh cuộn, ) thì Windows cũng phát sinh các thông điệp tương tự, với tên gọi có thêm tiếp đầu ngữ NC (nonclient) vào sau ký tự “_” như WM_NCMOUSEMOVE, WM_NCLBUTTONDOWN, WM_NCMBUTTONUP, WM_NCRBUTTONBDLCLK, . 23
  24. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian Các tham số lParam và wParam cũng hơi khác so với các thông điệp thiết bị chuột phát sinh trong vùng làm việc. Tham số lParam của thông điệp phát sinh từ ngoài vùng làm việc sẽ chỉ ra vị trí ngoài vùng làm việc nơi mà thiết bị chuột di chuyển hay kéo tới. Vị trí này được xác định bởi các giá trị định nghĩa trong WINUSER.H được bắt đầu với HT (viết tắt cho hit-test) như HTBORDER (trên border), HTCLOSE (trên nút Close). Ta có thể nhận các giá trị HT này qua thông điệp WM_NCHITTEST, tuy nhiên với phạm vi môn học chúng ta sẽ không tìm hiểu kỹ về vấn đề này. Hình 5.3. Minh họa tọa độ màn hình và tọa độ vùng làm việc Tham số lParam sẽ chứa tọa độ x ở 16 byte thấp và tọa độ y ở 16 byte cao. Tuy nhiên, đây là tọa độ màn hình, không phải 24
  25. Lập trình Windows là tọa độ vùng làm việc giống như thông điệp phát sinh từ vùng làm việc. Ta sử dụng các macro GET_X_LPARAM và GET_Y_LPARAM để lấy các giá trị này tương ứng. Nếu muốn chuyển về tọa độ vùng làm việc để xử lý khi cần thiết ta dùng các hàm Windows đã cung cấp. BOOL ScreenToClient(HWND hWnd, LPPOINT lpPoint); BOOL ClientToScreen(HWND hWnd, LPPOINT lpPoint); Hàm ScreenToClient chuyển tọa độ màn hình sang tọa độ vùng làm việc của cửa sổ hWnd và hàm ClientToScreen thực hiện ngược lại. Giá trị của điểm lpPoint sẽ thay đổi nếu thao tác chuyển đổi thực hiện thành công (giá trị hàm trả về là TRUE(1)). 5.3.3. Ví dụ VeDaGiac minh họa thao tác thiết bị chuột Ứng dụng VeDaGiac cho phép người dùng click chuột trái để vẽ các đường thẳng trong một đa giác. Với thao tác click chuột trái lần đầu tiên (xét cho một đa giác), ta lưu lại vị trí điểm vừa được click, và khi thực hiện thao tác click tiếp theo ta sẽ vẽ từ điểm được click trước tới điểm vừa được click. Quy trình thực hiện cho đến khi người dùng click chuột phải, lúc đó ta sẽ vẽ từ điểm đầu tiên của đa giác đến điểm vừa được click chuột phải để hoàn thành đa giác. Ngoài ra, ứng dụng còn có chức năng hỗ trợ người dùng chọn menu File -> New (IDM_NEW) để xóa sạch màn hình và bắt đầu thao tác vẽ đa giác khác (cho dù chưa hoàn thành vẽ đa giác trước đó); và chọn màu bút vẽ thông qua menu Color với các item Black (IDM_BLACK), Red (IDM_RED), Green (IDM_GREEN) và Blue (IDM_BLUE) trong quá trình vẽ. 25
  26. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian Ta tạo lập project VeDaGiac và menu resource giống như đã làm với các project khác. Về việc viết code, nhận thấy rằng trong quá trình thao tác click chuột, ta cần lưu vị trí điểm đầu tiên của đa giác, vị trí điểm vừa nhận thao tác click và vị trí điểm trước đó để vẽ, do đó ta dùng 3 biến tĩnh static POINT ptFirst, ptCurrent, ptPast để lưu. Ngoài ra, ta cũng cần một biến xác định là đang trong quá trình vẽ đa giác hay là đã kết thúc quá trình này (khi click chuột phải hoặc chọn menu New), vì thế ta dùng biến static BOOL bClicked. Dựa trên thao tác chọn các menu item với thông điệp WM_COMMAND, và thông điệp click chuột với các thông điệp WM_LBUTTONDOWN và WM_RBUTTONDOWN, ta có đoạn code hàm WndProc như sau: 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 static BOOL bClicked; 8 static POINT ptFirst, ptCurrent, ptPast; 9 static COLORREF crColor; 10 HPENhPen, hOldPen; 11 12 switch (message) 13 { 14 case WM_CREATE: 15 bClicked = FALSE; 16 crColor = RGB(0, 0, 0); 17 break; 18 case WM_COMMAND: 19 wmId = LOWORD(wParam); 20 wmEvent = HIWORD(wParam); 21 // Parse the menu selections: 22 switch (wmId) 23 { 24 case IDM_NEW: 25 bClicked = FALSE; 26 ptCurrent.x = 0; 26
  27. Lập trình Windows 27 ptCurrent.y = 0; 28 ptPast = ptCurrent; 29 InvalidateRect(hWnd, NULL, TRUE); 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 break; 55 case WM_PAINT: 56 hdc = BeginPaint(hWnd, &ps); 57 58 hPen = CreatePen(PS_SOLID, 1, crColor); 59 hOldPen = (HPEN)SelectObject(hdc, hPen); 60 MoveToEx(hdc, ptPast.x, ptPast.y, NULL); 61 LineTo(hdc, ptCurrent.x, ptCurrent.y); 62 SelectObject(hdc, hOldPen); 63 DeleteObject(hPen); 64 65 EndPaint(hWnd, &ps); 66 break; 67 case WM_LBUTTONDOWN: 68 if(!bClicked) 69 { 70 bClicked = TRUE; 71 ptFirst.x = LOWORD(lParam); 72 ptFirst.y = HIWORD(lParam); 73 } 74 else 75 { 27
  28. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian 76 ptPast = ptCurrent; 77 InvalidateRect(hWnd, NULL, FALSE); 78 } 79 ptCurrent.x = LOWORD(lParam); 80 ptCurrent.y = HIWORD(lParam); 81 break; 82 case WM_RBUTTONDOWN: 83 bClicked = FALSE; 84 ptPast = ptFirst; 85 InvalidateRect(hWnd, NULL, FALSE); 86 break; 87 case WM_DESTROY: 88 PostQuitMessage(0); 89 break; 90 default: 91 return DefWindowProc(hWnd, message, wParam, lParam); 92 } 93 return 0; 94 } 5.4. BỘ ĐỊNH THỜI GIAN Với các thông điệp về thiết bị chuột và bàn phím vừa tìm hiểu, chúng ta đã có thể xử lý các ứng dụng trên Windows hiệu quả hơn. Tuy nhiên, trước khi kết thúc môn học, chúng ta sẽ tìm hiểu thêm một dạng thông điệp cũng rất hữu dụng là thông điệp về thời gian. Có khá nhiều ứng dụng đòi hỏi người lập trình thực hiện một thao tác một cách tự động theo chu kỳ hoặc một khoảng thời gian định trước nào đó. Để làm điều này, chúng ta chỉ việc định nghĩa một đối tượng gọi là bộ định thời gian (Timer) với các thông số cần thiết, và sau đó là viết code xử lý trong thông điệp hoặc hàm xử lý có liên quan. 5.4.1. Định nghĩa bộ định thời gian trong ứng dụng Không như các thông điệp xuất phát từ thiết bị chuột và bàn phím được Windows gởi tự động vào hàng đợi thông điệp của 28
  29. Lập trình Windows ứng dụng; để có các thông điệp về thời gian, ta phải khai báo các đối tượng bộ định thời gian trước bằng hàm SetTimer. Sau khi khai báo hàm này thì Windows sẽ gởi thông điệp WM_TIMER đều đặn vào hàng đợi của ứng dụng. Hàm SetTimer thiết lập bộ định thời gian xác định bởi định danh nIDEvent, dùng cho cửa sổ hWnd, với thông số thời gian mà Windows sẽ gởi thông điệp WM_TIMER là uElapse phần nghìn giây (milisecond). Tham số lpTimerFunc trỏ đến hàm sẽ xử lý khi thông điệp WM_TIMER phát sinh. Nếu giá trị này là NULL thì Windows sẽ gởi thông điệp WM_TIMER vào hàng đợi thông điệp của cửa sổ tương ứng. UINT_PTR SetTimer(HWND hWnd, UINT_PTR nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc); Khi không dùng bộ định thời gian nữa hay kết thúc ứng dụng ta gọi hàm KillTimer, với các thông số gởi vào là định danh nIDEvent của bộ định thời gian đã định nghĩa cũng như của sổ hWnd quản lý nó. BOOL KillTimer(HWND hWnd, UINT_PTR nIDEvent); Về mặt lý thuyết thì thông điệp thời gian do Windows cung cấp là chính xác đến từng phần nghìn giây, thế nhưng trên thực tế thì không hoàn toàn như vậy. Sự chính xác còn phụ thuộc vào đồng hồ của hệ thống và các hoạt động hiện thời của chương trình. Nguyên nhân của vấn đề là thông điệp thời gian WM_TIMER có độ ưu tiên thấp, như thông điệp tô vẽ lại màn hình WM_PAINT, chúng thường phải chờ cho các thông điệp có độ ưu tiên cao hơn xử lý xong trước đã. 5.4.2. Ví dụ về bộ định thời gian 29
  30. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian Chương trình DongHo minh họa cơ chế xử lý của bộ định thời gian, thực hiện việc xuất giờ ra giữa màn hình ứng dụng kiểu đồng hồ điện tử dạng hh:mm:ss (giờ:phút:giây). Để thực hiện thao tác trên, ta cho ứng dụng cập nhật giá trị biến thời gian và vẽ lại trên màn hình mỗi 1 giây (1000 ms), vì thế khi khởi tạo ứng dụng, trong thông điệp WM_CREATE ta định nghĩa một bộ định thời gian có định danh là IDT_TIMER với giá trị uElapse là 1000. Chương trình này minh họa thao tác gởi thông điệp về thời gian vào hàm xử lý TimerProc. Trong hàm này ta chỉ đơn giản gọi hàm InvalidateRect để gởi yêu cầu thực hiện WM_PAINT trong cửa sổ chính. Và như vậy, ta chỉ cần lấy thời gian hiện tại trên máy tính bằng hàm GetLocalTime, chuyển thông số thời gian vào chuỗi szTimer và xuất ra màn hình bằng hàm DrawText quen thuộc. VOID GetLocalTime(LPSYSTEMTIME lpSystemTime); Riêng cách sử dụng thông điệp WM_TIMER (không dùng hàm xử lý riêng) xin xem thêm trong ví dụ tổng hợp ở phần tóm tắt 5.5. 1 // DongHo.cpp : Defines the entry point for the application. 2 // 3 4 #include "stdafx.h" 5 #include "resource.h" 6 7 #define IDT_TIMER 1 8 9 #define MAX_LOADSTRING 100 10 11 // Global Variables: 12 HINSTANCE hInst; // current instance 13 TCHAR szTitle[MAX_LOADSTRING]; // The title bar text 14 TCHAR szWindowClass[MAX_LOADSTRING]; // The title bar text 30
  31. Lập trình Windows 15 16 // Foward declarations of functions included in this code module: 17 ATOM MyRegisterClass(HINSTANCE hInstance); 18 BOOL InitInstance(HINSTANCE, int); 19 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 20 LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM); 21 VOID CALLBACK TimerProc(HWND, UINT, UINT_PTR, DWORD); 22 23 // Đoạn này xin xem code tự phát sinh 24 25 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, 26 LPARAM lParam) 27 { 28 int wmId, wmEvent; 29 PAINTSTRUCT ps; 30 HDC hdc; 31 SYSTEMTIME st; 32 TCHAR szTimer[MAX_LOADSTRING]; 33 34 switch (message) 35 { 36 case WM_CREATE: 37 SetTimer(hWnd, IDT_TIMER, 1000, (TIMERPROC)TimerProc); 38 break; 39 case WM_COMMAND: 40 wmId = LOWORD(wParam); 41 wmEvent = HIWORD(wParam); 42 // Parse the menu selections: 43 switch (wmId) 44 { 45 case IDM_ABOUT: 46 DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, 47 (DLGPROC)About); 48 break; 49 case IDM_EXIT: 50 DestroyWindow(hWnd); 51 break; 52 default: 53 return DefWindowProc(hWnd, message, wParam, 54 lParam); 55 } 56 break; 57 case WM_PAINT: 58 hdc = BeginPaint(hWnd, &ps); 59 RECT rt; 60 GetClientRect(hWnd, &rt); 61 GetLocalTime(&st); 62 wsprintf(szTimer,"%2d : %2d : %2d", st.wHour, st.wMinute, 63 st.wSecond); 31
  32. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian 64 DrawText(hdc, szTimer, strlen(szTimer), &rt, 65 DT_CENTER|DT_VCENTER|DT_SINGLELINE); 66 EndPaint(hWnd, &ps); 67 break; 68 case WM_DESTROY: 69 KillTimer(hWnd, IDT_TIMER); 70 PostQuitMessage(0); 71 break; 72 default: 73 return DefWindowProc(hWnd, message, wParam, lParam); 74 } 75 return 0; 76 } 77 78 VOID CALLBACK TimerProc( HWND hWnd, UINT uMsg, UINT_PTR idEvent, 79 DWORD dwTime) 80 { 81 InvalidateRect ( hWnd, NULL, TRUE ); 82 } 5.5. TÓM TẮT Trong bài học cuối này, chúng ta một lần nữa được nhắc lại đặc điểm quan trọng nhất khi lập trình trên Windows là vấn đề thông điệp với mô hình thao tác như ở hình 5.1. Bên cạnh các thông điệp phát sinh từ các đối tượng trên ứng dụng như WM_COMMAND, WM_SIZE, Ở bài học này, chúng ta đã tìm hiểu thêm một số thông điệp cơ bản về nhập liệu như WM_KEYDOWN, WM_CHAR, của bàn phím ở phần 5.2, thông điệp WM_LBUTTONDOWN, WM_RBUTTONDOWN, của thiết bị chuột ở phần 5.3, cũng như thông điệp về thời gian WM_TIMER với đối tượng bộ định thời gian – phần 5.4. Với từng dạng thông điệp chúng ta cũng đã thực hành cài đặt xử lý chúng trong hàm WndProc của ứng dụng Win32 Application. Và ở đây, để kết thúc bài học, chúng ta sẽ cùng nhau viết ứng dụng MoveImg thể hiện một con bướm đang vỗ 32
  33. Lập trình Windows cánh, kết hợp tất cả các thao tác về bàn phím, thiết bị chuột và bộ định thời gian. Đầu tiên, ta cũng tạo lập project dạng A typical “Hello World!” application, cùng với 3 ảnh bitmap trong resource là IDB_BFLY1, IDB_BFLY2, và IDB_MASK với kích thước 77*77 tương ứng là hai hình con bướm với tư thế vỗ cánh khác nhau cùng với một hình trống dùng làm mặt nạ để xoá ảnh cũ trên màn hình khi cần thiết sau này. Hình 5.4 thể hiện ảnh con bướm IDB_BFLY2 ngay giữa vùng làm việc của ứng dụng. Hình 5.4. Thể hiện ảnh IDB_BFLY2 của ứng dụng MoveImg Để thể hiện ảnh bitmap từ resource lên màn hình, ta cần tạo đối tượng ảnh HBITMAP, vì thế ở đây ta có thể dùng 3 biến toàn cục HBITMAP hBfly1, hBfly2, hMask để lưu dữ liệu 3 33
  34. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian đối tượng ảnh cho tiến trình (instance) của ứng dụng. Khi ứng dụng thực thi, ta nạp ngay dữ liệu cho các đối tượng này bằng hàm LoadBitmap trong thông điệp WM_CREATE. Bên cạnh đó, để thể hiện thao tác con bướm vỗ cánh thông qua hai ảnh khác nhau, chúng ta chỉ việc thiết lập một bộ định thời gian sao cho ứng với một khoảng thời gian nhất định thì thể hiện trên màn hình chỉ một trong hai ảnh. Và với cách thay thế hai ảnh như thế, ta sẽ thấy như có một con bướm đang vỗ cánh. Do đó, trong thông điệp WM_CREATE, ta cũng tạo lập bộ định thời gian có định danh là IDT_BFLY và dùng thêm biến tĩnh xác định hình (con bướm) hiện tại static int nCurBmp. Biến nCurBmp sẽ nhận một trong các giá trị được định nghĩa trước là BITMAP_BFLY1, BITMAP_BFLY2 và BITMAP_MASK; giá trị ban đầu là BITMAP_BFLY1. Trong ứng dụng này, để xuất ảnh ra màn hình, ta không dùng thông điệp WM_PAINT nữa mà viết riêng hàm DrawBfly để vẽ ảnh. Với mỗi lần Windows gởi thông điệp WM_TIMER, ta gọi hàm này để xuất ảnh với một giá trị ảnh nCurBmp tương ứng. Như vậy chúng ta đã thực hiện được thao tác về bộ định thời gian. Chúng ta sẽ thao tác tiếp về bàn phím và thiết bị chuột. Về thao tác bàn phím, ứng dụng này thể hiện việc dịch chuyển vị trí con bướm lên, xuống, qua trái, phải theo thao tác các phím mũi tên. Để thực hiện điều này, ta đơn giản chặn thông điệp WM_KEYDOWN với giá trị wPraram là VK_UP, VK_DOWN, VK_LEFT và VK_RIGHT, cập nhật vị trí xuất ảnh rồi gọi hàm DrawBfly để xuất ảnh. 34
  35. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian Về thao tác thiết bị chuột, ứng dụng này chặn các thông điệp WM_LBUTTONDOWN, WM_LBUTTONUP và WM_MOUSEMOVE để thiết lập việc cho phép di chuyển ảnh khi người dùng click chuột lên đối tượng ảnh trên màn hình và kéo rê đến vị trí khác. Ta căn cứ vào giá trị lParam xác định vị trí ảnh được cập nhật và vẽ lại ảnh. Ngoài ra, trong ứng dụng còn sử dụng thông điệp WM_SIZE để lấy kích thước vùng làm việc. Giá trị kích thước này được dùng để xác định phạm vi cho phép thể hiện đối tượng ảnh trên cửa sổ ứng dụng. Mỗi lần cập nhật kích thước màn hình thì chọn lại vị trí xuất ảnh ở chính giữa vùng làm việc. Cuối cùng, khi người dùng kết thúc ứng dụng thì ta hủy đối tượng bộ định thời gian đã tạo lập ở trên bằng hàm KillTimer. Đoạn code tập tin MoveImg.cpp như sau (bỏ các hàm WinMain, InitInstance, cho gọn): 1 // MoveImg.cpp : Defines the entry point for the application. 2 // 3 4 #include "stdafx.h" 5 #include "resource.h" 6 7 #define MAX_LOADSTRING 100 8 #define IDT_BFLY 1 9 10 #define BITMAP_BFLY1 1 11 #define BITMAP_BFLY2 2 12 #define BITMAP_MASK 3 13 #define DIS_MOVE 4 14 #define BITMAP_WIDTH 77 15 #define BITMAP_HEIGHT 77 16 17 // Global Variables: 18 HINSTANCE hInst; // current instance 19 TCHAR szTitle[MAX_LOADSTRING]; // The title bar text 20 TCHAR szWindowClass[MAX_LOADSTRING]; // The title bar text 35
  36. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian 21 HBITMAP hBfly1, hBfly2, hMask; 22 23 // Foward declarations of functions included in this code module: 24 ATOM MyRegisterClass(HINSTANCE hInstance); 25 BOOL InitInstance(HINSTANCE, int); 26 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 27 LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM); 28 void DrawBfly(HWND hWnd, POINT pt, int curBmp); 29 30 // Các hàm WinMain, InitInstance xin xem code tự phát sinh 31 32 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, 33 LPARAM lParam) 34 { 35 int wmId, wmEvent; 36 static BOOL bMouseDown; 37 static POINT ptOrg, ptFromOrg; 38 static LPARAM lParamSize; 39 static int nCurBmp; 40 41 switch (message) 42 { 43 case WM_CREATE: 44 SetTimer(hWnd, IDT_BFLY, 300, NULL); 45 46 hBfly1 = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BFLY1)); 47 hBfly2 = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BFLY2)); 48 hMask = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_MASK)); 49 50 nCurBmp = BITMAP_BFLY1; 51 bMouseDown = FALSE; 52 break; 53 54 case WM_COMMAND: 55 wmId = LOWORD(wParam); 56 wmEvent = HIWORD(wParam); 57 // Parse the menu selections: 58 switch (wmId) 59 { 60 case IDM_ABOUT: 61 DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, 62 (DLGPROC)About); 63 break; 64 case IDM_EXIT: 65 DestroyWindow(hWnd); 66 break; 67 default: 68 return DefWindowProc(hWnd, message, wParam, 69 lParam); 36
  37. Lập trình Windows 70 } 71 break; 72 73 case WM_LBUTTONDOWN: 74 if(LOWORD(lParam)>=ptOrg.x && LOWORD(lParam) =ptOrg.y && 76 HIWORD(lParam) 0) 103 { 104 DrawBfly(hWnd, ptOrg, BITMAP_MASK); 105 ptOrg.x-=DIS_MOVE; 106 DrawBfly(hWnd, ptOrg, nCurBmp); 107 } 108 break; 109 case VK_RIGHT: 110 if(ptOrg.x+BITMAP_WIDTH 0) 37
  38. Lập trình Windows 119 { 120 DrawBfly(hWnd, ptOrg, BITMAP_MASK); 121 ptOrg.y-=DIS_MOVE; 122 DrawBfly(hWnd, ptOrg, nCurBmp); 123 } 124 break; 125 case VK_DOWN: 126 if(ptOrg.y+BITMAP_HEIGHT<HIWORD(lParamSize)) 127 { 128 DrawBfly(hWnd, ptOrg, BITMAP_MASK); 129 ptOrg.y+=DIS_MOVE; 130 DrawBfly(hWnd, ptOrg, nCurBmp); 131 } 132 break; 133 } 134 break; 135 136 case WM_TIMER: 137 DrawBfly(hWnd, ptOrg, nCurBmp); 138 if(nCurBmp == BITMAP_BFLY1) 139 nCurBmp = BITMAP_BFLY2; 140 else 141 nCurBmp = BITMAP_BFLY1; 142 break; 143 144 case WM_SIZE: 145 lParamSize = lParam; 146 ptOrg.x = (LOWORD(lParamSize)-BITMAP_WIDTH)/2; 147 ptOrg.y = (HIWORD(lParamSize)-BITMAP_HEIGHT)/2; 148 break; 149 150 case WM_DESTROY: 151 KillTimer(hWnd, IDT_BFLY); 152 PostQuitMessage(0); 153 break; 154 155 default: 156 return DefWindowProc(hWnd, message, wParam, lParam); 157 } 158 return 0; 159 } 160 161 void DrawBfly(HWND hWnd, POINT pt, int curBmp) 162 { 163 HDC hDC, hMemDC; 164 HBITMAP hOldBmp; 165 166 hDC = GetDC(hWnd); 167 hMemDC = CreateCompatibleDC(hDC); 38
  39. Bài 5. Bàn phím, Thiết bị Chuột, Bộ định thời gian 168 169 switch(curBmp) 170 { 171 case BITMAP_BFLY1: 172 hOldBmp = (HBITMAP)SelectObject(hMemDC, hBfly1); 173 break; 174 case BITMAP_BFLY2: 175 hOldBmp = (HBITMAP)SelectObject(hMemDC, hBfly2); 176 break; 177 case BITMAP_MASK: 178 hOldBmp = (HBITMAP)SelectObject(hMemDC, hMask); 179 break; 180 } 181 182 BitBlt(hDC, pt.x, pt.y, BITMAP_WIDTH, BITMAP_HEIGHT, hMemDC, 0, 0, 183 SRCCOPY); 184 SelectObject(hMemDC, hOldBmp); 185 DeleteObject(hMemDC); 186 ReleaseDC(hWnd, hDC); 187 } 5.6. CÂU HỎI ÔN TẬP – BÀI TẬP 5.6.1. Trình bày mô hình xử lý thông điệp của các ứng dụng trên Windows? Áp dụng mô hình này để giải thích cơ chế xử lý thao tác và lập trình tương ứng về thiết bị bàn phím và thiết bị chuột? Lấy ví dụ cho một thông điệp cụ thể nào đó? 5.6.2. Trình bày hiểu biết của bạn về thông tin mã phím ảo và mã ký tự khi thao tác bàn phím? Sự khác nhau của thông điệp key (phím) và char (ký tự)? Sự khác nhau giữa thao tác các phím thông thường và các phím hệ thống (system)? 5.6.3. Trình bày các dạng thông điệp liên quan đến thiết bị chuột (mouse), cụ thể là thông điệp phát sinh trong vùng làm việc (client-area messages)? Tìm hiểu các thông điệp thiết bị chuột tương ứng ngoài vùng làm việc (nonclient-area messages) trên MSDN? 5.6.4. Trình bày hiểu biết của bạn về bộ định thời gian (timer)? Sự khác nhau giữa thông điệp về bộ định thời gian so với các thông điệp về bàn phím và thiết bị chuột? 39
  40. Lập trình Windows 5.6.5. Với thao tác người dùng chọn một đối tượng control (nút nhấn, edit text, ) trên một hộp thoại, hãy trình bày lại mô hình thao tác và xử lý thông điệp tương ứng? Hướng dẫn: Lưu ý cửa sổ nhận thông điệp là hộp thoại, và thông điệp tương ứng là WM_COMMAND. 5.6.6. Viết chương trình minh họa thao tác nhấn phím tương tự ví dụ ZoomAnh ở trên. Chương trình thực hiện thao tác phóng to hoặc thu nhỏ một ảnh bitmap khi người dùng click chuột trái hoặc phải tương ứng trên màn hình? 5.6.7. Mở rộng chương trình 5.6.6 thực hiện thao tác zoom ảnh theo tỉ lệ được chọn trên một menu cho trước, gồm zoom ½, ¼, 1 và /8? 5.6.8. Mở rộng chương trình 5.6.6 thực hiện thao tác zoom ảnh chỉ khi người dùng click vào bên trong đối tượng ảnh đang hiển thị? 5.6.9. Viết chương trình xuất ra chính giữa màn hình tọa độ con trỏ chuột (thay đổi khi người dùng di chuyển thiết bị chuột trong vùng làm việc)? 5.6.10. Viết chương trình xuất ra màn hình ngẫu nhiên các đường tròn với màu sắc và bán kính bất kỳ mỗi một giây (second)? Hướng dẫn: Sử dụng bộ định thời gian để thiết lập định kỳ gởi thông điệp về thời gian. Mỗi khi thông điệp này được kích hoạt, ta dùng hàm int rand( void ); - thư viện stdlib.h – để phát sinh giá trị ngẫu nhiên cho bán kính, tâm đường tròn, cũng như giá trị màu sắc. Lưu ý là tâm đường tròn nên được xác định thông qua kích thước của màn hình (xác định bằng thông điệp WM_SIZE), và giá trị màu sắc là 0 255 cho từng thông số Red, Green, Blue khi dùng macro RGB. 40