Bài giảng Lập trình Windows - Bài 3: Hộp hội thoại

doc 40 trang phuongnguyen 2780
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 3: Hộp hội thoại", để 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_3_hop_hoi_thoai.doc

Nội dung text: Bài giảng Lập trình Windows - Bài 3: Hộp hội thoại

  1. Bài 3 HỘP HỘI THOẠI Trong bài học trước, chúng ta đã làm quen với việc lập trình GUI trên cửa sổ chính của ứng dụng sử dụng các đối tượng như menu, accelerator và control. Bài học này sẽ giúp các bạn tìm hiểu cách thao tác trên hộp hội thoại (dialog box) – đối tượng cửa sổ trao đổi thông tin với người sử dụng thông qua các đối tượng điều khiển (control) tạo lập bằng resource. 3.1. TỔNG QUAN VỀ HỘP HỘI THOẠI Hộp thoại là đối tượng cửa sổ thường được dùng trong một ứng dụng bên cạnh của sổ chính để trao đổi thêm thông tin với người dùng. Cũng giống như các đối tượng GUI đã trình bày ở bài 2, hộp thoại được tạo lập theo khuôn mẫu trong tài nguyên, và được gọi là hộp thoại tự định nghĩa (user-defined dialog) để phân biệt với các hộp thoại đã được hệ thống định nghĩa thể hiện các công việc thường có của các ứng dụng trên Windows, chẳng hạn mở tập tin (hộp thoại Open File), lưu tập tin (Save As), chọn font chữ (Choose Font), gọi là hộp thoại thông dụng (common dialog). Chúng ta sẽ tìm hiểu chi tiết về hai loại hộp thoại này trong các phần tiếp theo. Vì giống như cửa sổ chính, một khi hộp thoại được khởi tạo và thể hiện, chúng sẽ nhận và xử lý thông tin truyền từ các cửa sổ khác (nhất là từ cửa sổ ứng dụng, nếu hộp thoại là cửa sổ con của cửa sổ ứng dụng) cũng như từ người sử dụng dưới dạng các thông điệp. Thông qua hai thông điệp WM_INITDIALOG và
  2. Lập trình Windows WM_COMMAND sẽ được trình bày, chúng ta sẽ dễ dàng lập trình xử lý cho các thao tác thông thường trên các hộp thoại. Ngoài ra, về cơ chế truyền và xử lý thông điệp của hộp thoại, ở bài học này chúng ta sẽ tìm hiểu cách thức Windows xử lý các thông điệp chỉ trong một hàm xử lý của cửa sổ hay ở nhiều hàm xử lý khác nhau cho các cửa sổ khác nhau trên cùng một ứng dụng. Cũng như cách thao tác đối với hai dạng hộp thoại phân biệt theo cơ chế gọi tạo lập, xử lý, cũng như hủy bỏ là hộp thoại trạng thái (modal dialog) và hộp thoại không trạng thái (modeless). 3.2. HỘP THOẠI TỰ ĐỊNH NGHĨA Trong phần này, chúng ta sẽ tìm hiểu cách tạo lập và thao tác các hộp thoại thông qua một số ví dụ cụ thể. Đầu tiên các bạn sẽ cùng phân tích lại cách tạo lập cũng như cấu trúc tài nguyên hộp thoại About , cách cài đặt source code thể hiện và xử lý hộp thoại này trong project BaiTap. Sau đó chúng ta sẽ cùng làm một ví dụ thể hiện hộp thoại như là cửa sổ chính của ứng dụng. Ví dụ đầu minh họa dạng hộp thoại trạng thái. Ví dụ thứ hai thể hiện hộp thoại không trạng thái dưới dạng cửa sổ chính của ứng dụng luôn. Sau đó, chúng ta sẽ phân tích một lần nữa hai dạng hộp thoại này thông qua các hộp thoại thông dụng Open File, Find/Replace của chương trình MyNotePad – trình bày ở phần 3.3. 3.2.1. Tìm hiểu ví dụ hộp thoại About Khi chọn menu Help -> About của ứng dụng BaiTap.exe, hệ thống sẽ kích hoạt hiển thị hộp thoại About như trong hình 3.1. 2
  3. Bài 3. Hộp hội thoại Đây có thể xem là ví dụ đơn giản nhất về hộp thoại trạng thái, hiển thị một cửa sổ nhỏ giới thiệu ứng dụng. Hình 3.1. Hiển thị hộp thoại About của ứng dụng BaiTap.exe Cũng như các đối tượng GUI đã học ở bài 2, chúng ta sẽ tìm hiểu cách tạo lập hộp thoại trong resource, gọi các hàm tạo và hiển thị, cũng như xử lý và kết thúc hộp thoại này. 3.2.1.1. Tài nguyên hộp thoại About Trong bài học trước, chúng ta đã làm quen cách tạo lập một đối tượng mới trong hệ thống tài nguyên của một project. Việc tạo lập một hộp thoại cũng được thực hiện tương tự. Ta có thể Insert một Dialog mới nếu cần một hộp thoại mới, hoặc click vào một hộp thoại đã có để chỉnh sửa. Với project BaiTap được phát sinh trong bài học thứ nhất, ta đã có sẵn tài nguyên của hộp thoại About. 3
  4. Lập trình Windows Click chọn vào hộp thoại IDD_ABOUTBOX bên ResourceView, MS Visual C++ 6.0 sẽ nạp các thông tin đã định nghĩa tương ứng trong tập tin BaiTap.rc và hiển thị trên editor cho chúng ta thao tác như trong hình 3.2. Chọn hộp thoại trong Thao tác control của Chọn button để đưa vào hộp ResourceView hộp thoại trên editor thoại từ cửa sổ Controls Hình 3.2. Thao tác tạo lập tài nguyên hộp thoại About Click chọn vào hộp thoại IDD_ABOUTBOX bên ResourceView, MS Visual C++ 6.0 sẽ nạp các thông tin đã định nghĩa tương ứng trong tập tin BaiTap.rc và hiển thị trên editor cho chúng ta thao tác như trong hình 3.2. Như đã nói, hộp thoại là dạng cửa sổ giao tiếp với người dùng thông qua các đối tượng control, vì vậy, khi tạo lập, ta chỉ cần chọn các control cần thiết và sắp xếp trên hộp thoại sao cho hợp lý mà thôi. Để thao tác vị trí, kích thước đối tượng control, 4
  5. Bài 3. Hộp hội thoại cách đơn giản nhất là sử dụng mouse (thiết bị chuột), click chọn và kéo thả chúng trên hộp thoại. Để thêm các control vào hộp thoại, ta chọn từ cửa sổ Controls. Chọn đối tượng Thay đổi các thông tin như định danh, nội control để thao tác dung thể hiện, của control đã chọn Hình 3.3. Thay đổi thông tin các control trên hộp thoại 3.2.1.2. Định danh IDOK và IDCANCEL Để thay đổi thông tin của control nào trên hộp thoại, ta right- click lên control rồi chọn Properties, hoặc bằng cách chọn tổ hợp phím Alt+Enter. Hình 3.3 thể hiện thao tác thay đổi nội dung (caption) của static có định danh là IDC_STATIC. Thật ra, khi thêm một đối tượng control vào một hộp thoại thì Visual C++ 6.0 tự động tạo ra một tên định danh đại diện cho đối tượng đó. Các định danh của chúng mặc định có dạng là 5
  6. Lập trình Windows IDC_THUTUTHEOLOAI, ví dụ IDC_EDIT1, IDC_EDIT2, . Tuy nhiên, người lập trình nên đặt tên chúng lại thành các tên gợi nhớ IDC_TENGOINHO nào đó. Như đã nói ở bài 2, các tên này là các macro được định nghĩa đại diện cho các giá trị nguyên trong tập tin resource.h. Đối với việc tạo lập các menu item – xem bài 2, các bạn được hướng dẫn nên đặt tên các định danh với tiếp đầu ngữ là IDM, thì với các hộp thoại và control chúng ta cũng có cách đặt tên tương ứng là IDD (identifier dialog) và IDC (identifier control). Điều này giúp ta dễ hiểu khi cài đặt hoặc đọc lại source code liên quan tới các resource này. Ở đây chúng ta không phân tích về cấu trúc của một hộp thoại trong resource dưới dạng text – xin xem lại phần 2.1.2 – vì khi lập trình với Visual C++ 6.0, hầu như ta không thao tác trên text editor. Cũng như các menu item, khi người dùng tác động lên một đối tượng control thì hệ thống xác định đối tượng thông qua chính giá trị ID của nó. Các giá này tương ứng là các số nguyên. Chúng được Visual C++ 6.0 phát sinh ngẫu nhiên khi ta tạo bất cứ một control mới nào. Tuy nhiên, nếu tạo control là các static text thì chúng đều có chung ID là IDC_STATIC với giá trị bằng -1. Điều này khác với các control khác (có giá trị riêng cho từng đối tượng) vì các static text thường chỉ có vai trò thể hiện thông tin văn bản tĩnh mà thôi. Nếu cần thao tác riêng các đối tượng này thì ta cần đặt lại một tên gợi nhớ khác. Trong hầu hết các hộp thoại, các bạn thường thấy là khi ta chọn nút nhấn OK hoặc Cancel nào đó thì đồng nghĩa với việc ta đóng hộp thoại lại. Các thao tác này được cài đặt bên trong 6
  7. Bài 3. Hộp hội thoại hàm xử lý của hộp thoại tương ứng cho ID của hai nút nhấn này – xem phần 3.2.1.3. Với đặc điểm chung này - mặc dù người lập trình có thể thay đổi lại giao diện và cách xử lý theo ý mình - các hộp thoại mới khi được chèn vào hệ thống tài nguyên đều có hai nút nhấn như vậy. Và, chúng có định danh tương ứng là IDOK và IDCANCEL (không phải dạng IDC !). Hình 3.4 thể hiện hộp thoại IDD_DIALOG1 chèn thêm vào project BaiTap. Hình 3.4. Chèn mới một hộp thoại vào project Như đã biết, một cửa sổ chỉ nhận tác động bàn phím của người dùng nếu đang giữ focus. Đối với các control trên cửa sổ chính cũng vậy. Tuy nhiên, với hai control có ID là IDOK và IDCANCEL thì khác. Ngoài các thao tác giống các control khác, nếu người dùng gõ phím Enter hoặc Esc thì hệ thống xem đó là các thao tác tương ứng việc chọn hai đối tượng này 7
  8. Lập trình Windows và gởi đến cho hộp thoại, và như vậy sẽ thực hiện thao tác đã được cài đặt cho các control này và sẽ đóng hộp thoại lại. 3.2.1.3. Lập trình xử lý hộp thoại Sau khi thiết kế hộp thoại trong resource, người lập trình sẽ dùng các hàm API để tạo và hiển thị đối tượng cửa sổ hộp thoại. Tuỳ theo dạng hộp thoại là trạng thái (modal) hay không trạng thái (modeless), mà ta dùng các hàm khác nhau. Đối với hộp thoại trạng thái ta dùng hàm DialogBox, trong khi hộp thoại không trạng thái thì dùng hàm CreateDialog. INT_PTR DialogBox( HINSTANCE hInstance, LPCTSTR lpTemplate, HWND hWndParent, DLGPROC lpDialogFunc ); HWND CreateDialog(HINSTANCE hInstance, LPCTSTR lpTemplate, HWND hWndParent, DLGPROC lpDialogFunc ); Với hInstance là chỉ danh xác định module tiến trình quản lý tài nguyên hộp thoại; lpTemplate xác định tài nguyên hộp thoại; hWndParent là chỉ danh của cửa sổ cha chứa hộp thoại này; và lpDialogFunc là con trỏ trỏ đến hàm xử lý của hộp thoại được tạo lập này. Các tham số gởi vào cho hai hàm trên đều giống nhau, nhưng kết quả trả về thì khác nhau. Hàm DialogBox trả về giá trị xác định kết quả khi thao tác kết thúc hộp thoại của hàm EndDialog (sẽ trình bày sau), bởi vì hàm DialogBox thao tác cho hộp thoại trạng thái – hộp thoại mà khi đóng hộp thoại này thì các thao tác của người dùng mới tác động trở lại lên các cửa sổ khác của ứng dụng. Còn hàm CreateDialog tạo lập nên hộp thoại “tồn tại” song song với các cửa sổ khác trong ứng dụng nên cần chỉ danh (handle) của nó để quản lý. 8
  9. Bài 3. Hộp hội thoại Quay lại với ví dụ project BaiTap, khi người dùng chọn menu About, tức hệ thống gởi thông điệp WM_COMMAND với LOWORD(wParam) có giá trị là IDM_ABOUT – xem lại phần xử lý menu của bài 2 – ta có đoạn code tạo lập và hiển thị hộp thoại About như sau (xin xem đầy đủ code của chương trình BaiTap ở bài 1): 1 // BaiTap.cpp : Tập tin cài đặt chính của ứng dụng BaiTap 2 #include "stdafx.h" 3 #include "resource.h" 4 #define MAX_LOADSTRING 100 5 // Định nghĩa các biến toàn cục 6 HINSTANCE hInst; // Instance của ứng dụng 7 TCHAR szTitle[MAX_LOADSTRING]; // Thể hiện trên thanh tiêu đề 8 TCHAR szWindowClass[MAX_LOADSTRING]; // Tên lớp cửa sổ 9 // Định nghĩa trước các hàm sẽ được gọi trong module này 10 ATOM MyRegisterClass(HINSTANCE hInstance); 11 BOOL InitInstance(HINSTANCE, int); 12 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 13 LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM); // Xin xem đầy đủ ở bài 1 83 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, 84 LPARAM lParam) 85 { // Xin xem đầy đủ ở bài 1 92 switch (message) 93 { 94 case WM_COMMAND: // Thông điệp liên quan tới menu và các control 95 wmId = LOWORD(wParam); 96 wmEvent = HIWORD(wParam); 97 // Chuyển thao tác ứng với menu item được kích hoạt 98 switch (wmId) 99 { 100 case IDM_ABOUT: 101 DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, 102 hWnd, (DLGPROC)About); 103 break; // Xin xem đầy đủ ở bài 1 109 } 110 break; // Xin xem đầy đủ ở bài 1 9
  10. Lập trình Windows 123 } 124 return 0; 125 } 126 // Xử lý các thông điệp gởi cho hộp thoại IDD_ABOUTBOX 127 LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, 128 LPARAM lParam) 129 { 130 switch (message) 131 { 132 case WM_INITDIALOG: 133 return TRUE; 134 case WM_COMMAND: 135 if (LOWORD(wParam) == IDOK || 136 LOWORD(wParam) == IDCANCEL) 137 { 138 EndDialog(hDlg, LOWORD(wParam)); 139 return TRUE; 140 } 141 break; 142 } 143 return FALSE; 144 } Ở đây, chỉ danh của ứng dụng là biến toàn cục hInst – xem dòng 6, chỉ danh hộp thoại trong resource là IDD_ABOUTBOX, cửa sổ quản lý hộp thoại chính là cửa sổ chính của ứng dụng hWnd – đây cũng chính là biến được gởi vào hàm WndProc, cuối cùng hàm xử lý hộp thoại có tên là About. Hàm này được khai báo trước (prototype) ở dòng 13 và cài đặt ở dòng 127-144. Cũng giống như ở cửa sổ chính, tất cả các thao tác tác động lên cửa sổ đều được gởi vào hàm WndProc, thì các thao tác tác động lên hộp thoại IDD_ABOUTBOX đều gởi vào hàm About tương ứng. Chúng ta muốn chương trình xử lý như thế nào thì chỉ việc vào viết code tương ứng trong hàm này. Với project BaiTap, sau khi hiển thị hộp thoại About thì ứng dụng đợi người dùng click chọn nút nhấn OK, hoặc gõ 10
  11. Bài 3. Hộp hội thoại phím Enter / Esc thì đóng hộp thoại lại và quay về chương trình chính. Vấn đề được dễ dàng lập trình khi ta hiểu rõ cơ chế hệ thống gởi thông tin vào hộp thoại khi người dùng chọn một đối tượng control nào đó. Trên Windows, khi tác động vào đối tượng menu item hoặc control thì hệ thống sẽ gởi cho cửa sổ cha của các menu item và control này thông điệp WM_COMMAND với các thông số như sau: LOWORD(wParam): Định danh của control hoặc menu item nhận tác động. HIWORD(wParam): Mã thông điệp thông báo (notification message) ứng với các dạng tác động khác nhau cho mỗi loại control. Ví dụ như khi click lên một nút nhấn thì giá trị này là BN_CLICKED, hay là double click thì có giá trị là BN_DBLCLK, Và như vậy, ngoài việc vào chặn thông điệp WM_COMMAND để biết control nào vừa nhận tác động, ta còn có thể kiểm tra tiếp loại tác động thông qua giá trị này để viết những xử lý khác nhau một cách tương ứng. (HWND)lParam: Chỉ danh của control. Đôi lúc trong các ứng dụng, ta cần thao tác với chính handle của control, nhất là trong trường hợp control được tạo lập như một cửa sổ con trên cửa sổ chính (xem lại bài 2). Trong project này, việc xử lý các thao tác của người dùng tác động lên hai control có định danh là IDOK và IDCANCEL chính là đoạn code dòng 134-141. Khi người dùng click lên nút 11
  12. Lập trình Windows nhấn OK, hoặc gõ phím Enter hay Esc thì hộp thoại được đóng lại nhờ hàm EndDialog. BOOL EndDialog(HWND hDlg, INT_PTR nResult); Hàm EndDialog kết thúc hộp thoại trạng thái hDlg (hộp thoại được tạo lập bởi hàm DialogBox ở trên). Nếu thành công hàm trả về giá trị TRUE, ngược lại trả về FALSE. Để ý tham số thứ hai gởi vào hàm này, giá trị nResult này chính là giá trị trả về của hàm DialogBox (xin xem lại prototype hàm DialogBox). Trong trường hợp hộp thoại không trạng thái, để hủy hộp thoại (đuợc tạo lập bằng hàm CreateDialog) ta cũng làm tương tự như hủy các cửa sổ thông thường khác, với chỉ danh của sổ hộp thoại gởi vào hàm DestroyWindow. BOOL DestroyWindow(HWND hDlg); Ngoài thông điệp WM_COMMAND, khi viết code cho hàm xử lý của một hộp thoại, nếu cần có những thiết lập nào đó cho hộp thoại trong lần thể hiện đầu tiên (và duy nhất) thì ta viết qua thông điệp WM_INITDIALOG. 3.2.2. Ứng dụng với cửa sổ chính là hộp thoại Đối với một ứng dụng thông thường dạng Win32 Application, cửa sổ chính của ứng dụng được tạo lập thông qua hàm CreateWindow (xem lại bài 1), cửa sổ này thuộc một lớp cửa sổ đã được định nghĩa trước đó. Trong trường hợp này, nếu muốn tạo thêm các control trên cửa sổ chính ta chỉ có thể tạo lập chúng như các cửa sổ con (child window) trên cửa sổ chính cũng bằng họ hàm CreateWindow (xem bài 2) mà thôi. 12
  13. Bài 3. Hộp hội thoại Mặt khác, ta lại nhận thấy nếu một cửa sổ có nhiều control thì chỉ có hộp thoại với các control tạo bằng resource là đơn giản nhất. Hình 3.5. Ứng dụng với cửa sổ chính là hộp thoại Với hai vấn đề đặt ra ở trên, ta có thể hỏi là liệu có tạo được cửa sổ chính dưới dạng là một hộp thoại hay không? Nếu được thì việc viết các chương trình dạng như chương trình Calculator của MS Windows sẽ đơn giản hơn nhiều so với việc phải viết code tạo từng control của các ứng dụng Win32 Application. Câu trả lời là có, tuy nhiên kèm theo một số vấn đề mà chúng ta sẽ phải tìm hiểu tiếp sau đây thông qua ví dụ về ứng dụng thực hiện các phép tính đơn giản giữa hai số nguyên nhập từ hai edit control. Xét ứng dụng có yêu cầu như sau: khi thể hiện, chương trình có giao diện như ở hình 3.5. Lúc người dùng chọn các option thông qua nhóm radio button Addition, Subtraction, Multiplication, và Division thì static giữa hai edit nhập số sẽ có thể hiện tương ứng là +, -, * và /. Nếu người dùng chọn chức 13
  14. Lập trình Windows năng Run thì ứng dụng lấy giá trị số nguyên ở hai edit nhập liệu rồi tính toán theo phép toán đã chọn tương ứng (mặc định phép tính ban đầu là phép cộng (addition)) và xuất kết quả ra edit xuất. Nếu giá trị ở edit nhập thứ 2 bằng 0 mà người dùng chọn phép chia (division) thì thông báo không thực hiện được. 3.2.2.1. Resource project MayTinh trên MS Visual C++ 6.0 Ta tạo lập project MayTinh tương tự như việc tạo lập project BaiTap ở bài 1: cũng tạo lập project dạng Win32 Application với code được phát sinh sẵn theo dạng A typical “Hello World!” application. Ở đây, chúng ta đành chọn cách này để thực hành cho nhanh, nhưng sẽ phải chỉnh sửa khá nhiều vì project của chúng ta không cần dùng nhiều thông tin như project tạo lập sẵn. Các bạn cũng có thể tạo project rỗng (An empty project) hay project với khung sườn đơn giản (A simple Win32 Application) và viết lại code tựa như project sẽ làm này. Sau phần tạo lập project như trên, ta cần chỉnh sửa các resource cần dùng trong ứng dụng. Đối với project này, chúng ta sẽ không dùng Accelerator, Menu, và String Table. Các bạn vào ResourceView và xoá bỏ tất cả các đối tượng resource này bằng cách gõ phím Delete để xóa tất cả các đối tượng có trong các thư mục tương ứng. Chúng ta chỉ giữ lại phần Dialog và Icon. Tiếp theo, chúng ta sẽ tạo lập resource cho hộp thoại sẽ hiển thị. Đối với project này, chúng ta sẽ không dùng hộp thoại IDD_ABOUTBOX nữa, vì thế ta có thể tạo hộp thoại mới dựa trên hộp thoại này hoặc Insert một hộp thoại mới và tạo lập theo giao diện sẽ hiển thị như đã nói ở hình 3.5. 14
  15. Bài 3. Hộp hội thoại Hình 3.6. Các kiểu của hộp thoại MAYTINH Hình 3.7. Thông tin chung của hộp thoại MAYTINH Hộp thoại được tạo lập phải là một hộp thoại dạng Overlapped, có System Menu và Minimize Box (xem hình 3.6) có ID là MAYTINH và Caption là My Calculator (xem hình 3.7). Để thực hiện thao tác trên môi trường trực quan của Visual C++ 6.0 như vậy, các bạn có thể double click lên hộp thoại hoặc là chọn hộp thoại rồi chọn tổ hợp phím Alt+Enter. Ta tạo lập tiếp các control cho hộp thoại để có được giao diện như đã nói, kết quả thực hiện như ở hình 3.8. Trong hộp thoại này, ta cần tạo 3 edit. Hai edit trong khung (frame) Input có ID là IDC_INPUT1 và IDC_INPUT2, edit 15
  16. Lập trình Windows còn lại trong khung Output được đặt ID là IDC_OUTPUT. Tất cả các edit đều thiết lập style là Number. Các radio box trong khung Operation được tạo lập theo thứ tự từ trên xuống dưới, với các ID lần lượt là IDC_ADDITION, IDC_SUBTRACTION, IDC_MULTIPLICATION, và IDC_DIVISION. Hai nút nhấn Run và Exit có ID tương ứng là IDC_RUN và IDC_EXIT. Hình 3.8. Tạo lập hộp thoại MAYTINH trên MS Visual C++ 6.0 Nếu thực hiện như vừa nói thì ta thấy việc tạo lập cửa sổ chính là một hộp thoại trên resource cũng bình thường như đã thao tác hộp thoại About Tuy nhiên, nếu chỉ như vậy thì chưa đủ. Vấn đề là vì khi thể hiện và xử lý cho hộp thoại này (sẽ khảo sát kỹ ở phần 3.2.2.2), hộp thoại cần phải thuộc một lớp cửa sổ nào đó. Ở đây, chúng ta đặt tên lớp hộp thoại này là 16
  17. Bài 3. Hộp hội thoại MAYTINH, và ta cần phải sửa resource của nó bằng cách viết thêm thông tin dưới dạng văn bản (text). Ta mở tập tin MayTinh.rc dưới dạng văn bản bằng cách chọn menu File -> Open. Từ hộp thoại Open, chọn tập tin MayTinh.rc, rồi chọn dạng mở Open as là Text. Chúng ta cần viết thêm tên lớp MAYTINH vào resource hộp thoại – xem dòng 4 - sao cho có kết quả như sau (ở đây chỉ cần trình bày đoạn source về hộp thoại trong tập tin MayTinh.rc mà thôi): 1 MAYTINH DIALOG DISCARDABLE 22, 17, 169, 111 2 STYLE WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU 3 CAPTION "My Calculator" 4 CLASS "MAYTINH" 5 FONT 8, "MS Sans Serif" 6 BEGIN 7 EDITTEXT IDC_INPUT1,13,19,66,12,ES_RIGHT | ES_NUMBER 8 EDITTEXT IDC_INPUT2,13,45,66,12,ES_RIGHT | ES_NUMBER 9 EDITTEXT IDC_OUTPUT,13,81,66,12,ES_RIGHT | ES_NUMBER | 10 WS_DISABLED 11 CONTROL "&Addition", IDC_ADDITION, "Button", 12 BS_AUTORADIOBUTTON, 103,17,54,8 13 CONTROL "&Subtraction",IDC_SUBTRACTION,"Button", 14 BS_AUTORADIOBUTTON,103,29,51,8 15 CONTROL "&Multiplication",IDC_MULTIPLICATION,"Button", 16 BS_AUTORADIOBUTTON,103,40,56,8 17 CONTROL "&Division",IDC_DIVISION,"Button", 18 BS_AUTORADIOBUTTON,104,51,50,10 19 DEFPUSHBUTTON "&Run",IDC_RUN,102,72,54,12 20 PUSHBUTTON "&Exit",IDC_EXIT,102,88,54,12 21 GROUPBOX "Input",IDC_STATIC,6,6,81,60 22 LTEXT "+",IDC_OPERATION,43,34,9,9 23 GROUPBOX "Output",IDC_STATIC,6,70,81,32 24 GROUPBOX "Operation",IDC_STATIC,96,6,66,60 25 END 3.2.2.2. Một số thay đổi code cho ứng dụng MayTinh Với resource hộp thoại MAYTINH như ở trên, ta sẽ lập trình thể hiện và xử lý trong tập tin MayTinh.cpp như sau: 17
  18. Lập trình Windows 1 // MayTinh.cpp : Defines the entry point for the application. 2 // 3 4 #include "stdafx.h" 5 #include "resource.h" 6 7 // Global variables 8 TCHAR szAppName[] = TEXT("MAYTINH"); 9 10 // Foward declarations of functions included in this code module: 11 ATOM MyRegisterClass(HINSTANCE hInstance); 12 BOOL InitInstance(HINSTANCE, int); 13 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 14 15 int APIENTRY WinMain(HINSTANCE hInstance, 16 HINSTANCE hPrevInstance, 17 LPSTR lpCmdLine, 18 int nCmdShow) 19 { 20 // TODO: Place code here. 21 MSG msg; 22 23 MyRegisterClass(hInstance); 24 25 // Perform application initialization: 26 if (!InitInstance (hInstance, nCmdShow)) 27 { 28 return FALSE; 29 } 30 31 // Main message loop: 32 while (GetMessage(&msg, NULL, 0, 0)) 33 { 34 TranslateMessage(&msg); 35 DispatchMessage(&msg); 36 } 37 return msg.wParam; 38 } 39 40 // 41 // FUNCTION: MyRegisterClass() 42 // 43 // PURPOSE: Registers the window class. 44 // 45 // COMMENTS: 46 // 47 // This function and its usage is only necessary if you want this code 48 // to be compatible with Win32 systems prior to the 'RegisterClassEx' 49 // function that was added to Windows 95. It is important to call this function 18
  19. Bài 3. Hộp hội thoại 50 // so that the application will get 'well formed' small icons associated 51 // with it. 52 // 53 ATOM MyRegisterClass(HINSTANCE hInstance) 54 { 55 WNDCLASSEX wcex; 56 57 wcex.cbSize = sizeof(WNDCLASSEX); 58 59 wcex.style = CS_HREDRAW | CS_VREDRAW; 60 wcex.lpfnWndProc = (WNDPROC)WndProc; 61 wcex.cbClsExtra = 0; 62 wcex.cbWndExtra = DLGWINDOWEXTRA; // Chu y! 63 wcex.hInstance = hInstance; 64 wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_MAYTINH); 65 wcex.hCursor = LoadCursor(NULL, IDC_ARROW); 66 wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); 67 wcex.lpszMenuName = NULL; 68 wcex.lpszClassName = szAppName; // Chu y! 69 wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL); 70 71 return RegisterClassEx(&wcex); 72 } 73 74 // 75 // FUNCTION: InitInstance(HANDLE, int) 76 // 77 // PURPOSE: Creates main window 78 // 79 // COMMENTS: 80 // 81 // In this function, we create and display the main program window. 82 // 83 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) 84 { 85 HWND hWnd; 86 87 hWnd = CreateDialog(hInstance, szAppName, 0, NULL); 88 89 if (!hWnd) 90 { 91 return FALSE; 92 } 93 94 ShowWindow(hWnd, nCmdShow); 95 UpdateWindow(hWnd); 96 return TRUE; 97 } 98 19
  20. Lập trình Windows 99 // 100 // FUNCTION: WndProc(HWND, unsigned, WORD, LONG) 101 // 102 // PURPOSE: Processes messages for the main window. 103 // 104 // WM_COMMAND - process the application menu 105 // // WM_DESTROY - post a quit message and return 106 // 107 // 108 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, 109 LPARAM lParam) 110 { 111 static int nCurOpt=1; 112 int nInput1, nInput2; 113 switch (message) 114 { 115 case WM_COMMAND: 116 switch(LOWORD(wParam)) 117 { 118 case IDC_ADDITION: 119 SetDlgItemText(hWnd, IDC_OPERATION, "+"); 120 nCurOpt = 1; 121 break; 122 case IDC_SUBTRACTION: 123 SetDlgItemText(hWnd, IDC_OPERATION, "-"); 124 nCurOpt = 2; 125 break; 126 case IDC_MULTIPLICATION: 127 SetDlgItemText(hWnd, IDC_OPERATION, "*"); 128 nCurOpt = 3; 129 break; 130 case IDC_DIVISION: 131 SetDlgItemText(hWnd, IDC_OPERATION, "/"); 132 nCurOpt = 4; 133 break; 134 case IDC_RUN: 135 nInput1 = GetDlgItemInt(hWnd, IDC_INPUT1, NULL, 136 TRUE); 137 nInput2 = GetDlgItemInt(hWnd, IDC_INPUT2, NULL, 138 TRUE); 139 switch(nCurOpt) 140 { 141 case 1: // Addition 142 SetDlgItemInt(hWnd, IDC_OUTPUT, 143 nInput1+nInput2, TRUE); 144 break; 145 case 2: // Subtraction 146 SetDlgItemInt(hWnd, IDC_OUTPUT, 147 nInput1-nInput2, TRUE); 20
  21. Bài 3. Hộp hội thoại 148 break; 149 case 3: // Multiplication 150 SetDlgItemInt(hWnd, IDC_OUTPUT, 151 nInput1*nInput2, TRUE); 152 break; 153 case 4: // Division 154 if(nInput2==0) 155 MessageBox(hWnd, "Can not divide zero!", 156 "Error", MB_OK); 157 else 158 SetDlgItemInt(hWnd, IDC_OUTPUT, 159 nInput1/nInput2, TRUE); 160 break; 161 } 162 break; 163 case IDC_EXIT: 164 DestroyWindow(hWnd); 165 break; 166 } 167 break; 168 case WM_DESTROY: 169 PostQuitMessage(0); 170 break; 171 default: 172 return DefWindowProc(hWnd, message, wParam, lParam); 173 } 174 return 0; 175 } Với project này, ta không sử dụng các biến toàn cục ban đầu, mà chỉ cần một biến chứa tên lớp cửa sổ hộp thoại vì thế phần khai báo biến toàn cục sẽ như ở dòng 7-8. Ta cũng không sử dụng các phím nóng (accelerator), vì thế tất cả những phần code liên quan đến phím nóng ở trong hàm WinMain ta đều bỏ đi (xem dòng 15-38). Vấn đề tiếp theo và là vấn đề rất quan trọng của project này là lớp cửa sổ hộp thoại. Ta cũng định nghĩa và đăng ký lớp cửa sổ - dòng 53-72, nhưng chú ý vì đây là lớp cửa sổ có dạng mở rộng từ resource hộp thoại, vì thế tham số cbWndExtra cần được 21
  22. Lập trình Windows gán là DLGWINDOWEXTRA – dòng 62, và lớp cửa sổ được gán là MAYTINH – dòng 68 – thông qua biến toàn cục szAppName. Sau khi đã định nghĩa và đăng ký lớp cửa sổ này, ta sẽ tạo lập cửa sổ dưới dạng là hộp thoại thông qua hàm CreateDialog thay vì hàm CreateWindow – xem dòng 87. Cửa sổ hộp thoại này thuộc lớp cửa sổ đã được định nghĩa trước đó, vì thế cũng sẽ được xử lý trong hàm xử lý đã được định nghĩa là WndProc – xem dòng 60. 3.2.2.3. Hàm xử lý cửa sổ chính WndProc Sau khi tạo lập và hiển thị cửa sổ, tất cả mọi thao tác tác động lên ứng dụng đều được hệ thống chuyển vào hàm xử lý WndProc nhờ vòng lặp nhận và chuyển thông điệp trong hàm WinMain (dòng 32-36). Lúc này ta chỉ cần chặn các thông điệp có liên quan và xử lý theo yêu cầu của ứng dụng. Nhận thấy rằng ứng dụng của chúng ta chỉ xử lý các thao tác của người dùng tác động lên các control, do đó ta viết code với thông điệp WM_COMMAND. Như đã biết, mỗi lần người dùng thao tác control nào đó thì hệ thống gởi cho cửa sổ cha của control này thông điệp WM_COMMAND có thông số LOWORD(wParam) là định danh của control đã được định nghĩa. Vì thế, khi người dùng tác động nhóm radio button chọn phép toán, tức cần thể hiện lại nội dung của static IDC_OPERATION, ta chỉ cần chặn giá trị LOWORD(wParam) của từng radio button và dùng hàm SetDlgItemText để thiết lập nội dung text lpString tương ứng (dòng 118-133) cho control nIDDlgItem, đồng thời đánh dấu 22
  23. Bài 3. Hộp hội thoại phép toán hiện tại lại (ở đây chúng ta có thể sử dụng một biến tĩnh nCurOpt). BOOL SetDlgItemText(HWND hDlg, int nIDDlgItem, LPCTSTR lpString); Khi người dùng chọn chức năng Run, tức thao tác control IDC_RUN, ta căn cứ vào phép toán hiện tại nCurOpt, nhận hai giá trị nguyên từ edit IDC_INPUT1 và IDC_INPUT2 bằng hàm GetDlgItemInt rồi tính toán và thiết lập lại giá trị uValue cho edit IDC_OUTPUT bằng hàm SetDlgItemInt (dòng 134- 162). UINT GetDlgItemInt(HWND hDlg, int nIDDlgItem, BOOL *lpTranslated, BOOL bSigned); BOOL SetDlgItemInt(HWND hDlg, int nIDDlgItem, UINT uValue, BOOL bSigned); 3.3. HỘP THOẠI THÔNG DỤNG Một trong những đặc điểm chính nhất của Windows là cung cấp cơ chế giao diện chuẩn và khá đồng bộ cho tất cả các ứng dụng chạy trên hệ điều hành này. Như thường thấy, để thao tác về tập tin, ta thường chọn popup menu File của các ứng dụng. Hơn nữa, các thao tác về tập tin cũng khá giống nhau, ta thường mở một kiểu hộp thoại rồi chọn tập tin cần mở hay gõ vào tên tập tin cần lưu, Để thực hiện điều này, Windows cung cấp sẵn một thư viện cho phép chúng ta chỉ cần gọi một vài hàm API tương ứng để thể hiện các hộp thoại mà thôi, gọi là “thư viện các hộp thoại thông dụng”. Thư viện này nằm trong các tập tin liên kết động 23
  24. Bài 3. Hộp hội thoại trong thư mục hệ thống (system) của Windows – ví dụ trên MS Windows 2000 ta có tập tin commdlg.dll. Trong phần này, chúng ta sẽ tìm hiểu cách tạo lập và xử lý hai hộp thoại thông dụng là Open File (mở tập tin) và Find (tìm kiếm văn bản) minh họa với ứng dụng MyNotepad (xem bài 2). Hộp thoại Open File là dạng hộp thoại trạng thái (modal) và hộp thoại Find là hộp thoại không trạng thái (modeless). 3.3.1. Các dạng hộp thoại thông dụng trên Windows Trong bài 2, chúng ta đã cùng nhau xây dựng menu cho ứng dụng MyNotepad. Trong số đó, ngoài những item liên quan đến thao tác trên edit control, các item còn lại đều là mục chọn cho các thao tác sử dụng các hộp thoại thông dụng. Nếu liệt kê đầy đủ thì các hộp thông dụng mà Windows hỗ trợ, ta sẽ có hộp thoại Open và Save As liên quan đến thao tập tin, hộp thoại Find và Replace cho dữ liệu edit text, hộp thoại Print, Print Setup, Print Property Sheet và Page Setup liên quan đến định dang trang và in ấn, và cuối cùng là định dạng về màu sắc Color và font chữ Font. Để thao tác các hộp thoại ta cần tạo lập chúng, sử dụng các hàm riêng mà Windows đã cung cấp. Tương tự thao tác hộp thoại tự tạo với mẫu (template) trong tài nguyên và hàm xử lý được viết riêng, các hàm này sẽ tương ứng gọi hiển thị hộp thoại theo mẫu đã định sẵn, và cũng kích hoạt hàm xử lý của chúng (theo tập tin liên kết động trong hệ thống). Vì người dùng cần tạo và xử lý các hộp thoại chỉ với các đặc trưng của ứng dụng mình, nên trước khi gọi các hàm thao 24
  25. Lập trình Windows tác trên, công việc đầu tiên mà chúng ta cần làm là khởi tạo các thông số cho từng cấu trúc dùng trong ứng dụng. Ở đây, với phạm vi môn học, chúng ta chỉ tìm hiểu một số hàm, thông điệp và cấu trúc hộp thoại Open File và Find. Các hộp thoại còn lại xem như bài tập dành cho các bạn. 3.3.2. Hộp thoại trạng thái Open File Hộp thoại Open – xem hình 3.9 – cho phép người dùng chọn một tập tin để mở thông qua tên ỗ đĩa, thư mục và tên của tập tin. Hình 3.9. Hộp thoại Open chọn tập tin để mở Để sử dụng hộp thoại này, thao tác đầu tiên khi lập trình là thiết lập các thông số cho cấu trúc OPENFILENAME, sau đó gọi hàm GetOpenFileName để hiển thị hộp thoại. typedef struct tagOFN { DWORD lStructSize; // số byte kích thước cấu trúc 25
  26. Lập trình Windows HWND hwndOwner; // cửa sổ quản lý hộp thoại này HINSTANCE hInstance; // đối tượng vùng nhớ quản lý tài // nguyên hộp thoại, căn cứ thông // tin cờ Flags LPCTSTR lpstrFilter; // bộ lọc dạng tập tin hỗ trợ // (*.txt, *.*, ) LPTSTR lpstrCustomFilter; // bộ lọc và kích thước của nó DWORD nMaxCustFilter; // do người dùng định nghĩa DWORD nFilterIndex; // chỉ mục bộ lọc mặc định: 1, 2, LPTSTR lpstrFile; // vùng đệm chứa tên tập tin DWORD nMaxFile; // kích thước vùng đệm lpstrFile LPTSTR lpstrFileTitle; // vùng đệm và kích thước tên tập DWORD nMaxFileTitle; // tin (không chứa đường dẫn) LPCTSTR lpstrInitialDir; // tên thư mục khởi tạo LPCTSTR lpstrTitle; // tên hộp thoại (Open, ) DWORD Flags; // cờ thiết lập trạng thái khởi tạo WORD nFileOffset; // offset tên tập tin trong lpstrFile WORD nFileExtension; // offset đến vị trí phần đuôi LPCTSTR lpstrDefExt; // tên phần đuôi mặc định LPARAM lCustData; // thông tin tự định nghĩa cho // hàm hook lpfnHook LPOFNHOOKPROC lpfnHook; // hàm chặn xử lý thông điệp // hộp thoại LPCTSTR lpTemplateName; // tên mẫu tài nguyên hộp thoại #if (_WIN32_WINNT >= 0x0500) void * pvReserved; // để dành DWORD dwReserved; // để dành DWORD FlagsEx; // cờ mở rộng cho Windows 2000/XP #endif // (_WIN32_WINNT >= 0x0500) } OPENFILENAME, *LPOPENFILENAME; 26
  27. Bài 3. Hộp hội thoại Hàm GetOpenFileName trả về giá trị là TRUE nếu thành công với các thông số của lpofn được thiết lập theo thác của người dùng trên hộp thoại. Ngược lại giá trị trả về là FALSE. BOOL GetOpenFileName(LPOPENFILENAME lpofn); Áp dụng cho ứng dụng MyNotepad. Đầu tiên, ta khởi tạo các thông số của cấu trúc OPENFILENAME cho biến toàn cục ofn trong hàm viết thêm PopFileInitialize. Khi người dung chọn menu IDM_OPEN, ta gọi hàm PopFileOpenDlg để mở hộp thoại và nhận về tên tập tin cần đọc. Sau đó đọc nội dung tập tin và đưa vào edit hwndEdit bằng hàm PopFileRead. Lưu ý là để sử dụng hộp thoại thông dụng Open File, ta cần include thư viện commdlg.h vào project ứng dụng. Việc thao tác dữ liệu tập tin và đưa vào ứng dụng đòi hỏi các bạn phải nắm được cơ chế về quản lý vùng nhớ và tập tin – điều này vượt ra khỏi phạm vi môn học. Do đó chúng ta không phân tích kỹ về chúng, mà chỉ tham khảo với đoạn code tôi cung cấp kèm theo. 3.3.3. Hộp thoại không trạng thái Find Tương tự hộp thoại trạng thái Open, Windows cũng cung cấp cho chúng ta một số hộp thoại thông dụng khác. Các bạn có thể tìm hiểu kỹ hơn trong MSDN và các bài mẫu (nếu có) để hoàn thiện ứng dụng MyNotepad. Tuy nhiên, trước khi kết thúc bài học này, chúng ta cùng nhau làm quen với việc lập trình thao tác trên hộp thoại không trạng thái nữa, đó là hộp thoại tìm kiếm dữ liệu văn bản Find. 27
  28. Lập trình Windows Để thao tác hộp thoại Find, chúng ta cần tìm hiểu cấu trúc FINDREPLACE và gọi hàm FindText đã được Microsoft cung cấp. Công việc này tương tự như đối các hộp thoại Open File bằng việc chúng ta xây dựng hàm PopFindFindDlg. Tuy nhiên, nếu chỉ như vậy thì không có gì cần nói ở đây. Vấn đề là hộp thoại Find không như các hộp thoại trạng thái, sau khi gọi hiển thị hộp thoại, chúng ta vẫn chuyển mọi thao tác về cho ứng dụng. Vì thế, ở đây ta cần định nghĩa dạng thông điệp riêng và viết cách xử lý riêng cho hộp thoại này. Tại thông điệp WM_CREATE của cửa sổ chính, ta đăng ký một thông điệp tự định nghĩa có tên là msgFindReplace. Và khi chọn menu IDM_FIND, ta gọi hộp thoại Find và trả về handle là hDlgModeless. Cửa sổ này tồn tại song song với cửa sổ chính của ứng dụng. Và vì thế chúng ta phải lập trình sao đó để Windows biết và chuyển các thông điệp đến đúng từng cửa sổ cho chúng ta xử lý. Để làm điều này, tại vòng lặp xử lý thông điệp của ứng dụng, ta cần bổ sung thêm hàm thực hiện thao tác chuyển thông điệp đến đúng cửa sổ. Ta sử dụng hàm IsDialogMessage để kiểm tra xem một thông điệp có phải là của một hộp thoại hay không trước khi chuyển cho hàm WndProc của cửa sổ chính. Ta kiểm tra với biến hDlgModeless. Nếu là thao tác với hộp thoại Find này, Windows sẽ xử lý theo cài đặt mặc định và chuyển về cho cửa sổ chính thông điệp tự định nghĩa msgFindReplace. Lúc này ta chỉ việc kiểm tra thông điệp này trong hàm xử lý của cửa sổ chính và thao tác lên edit hwndEdit với hàm PopFindFindText. 28
  29. Lập trình Windows Quy trình cài đặt tương đối phức tạp. Các bạn xem kỹ trong ví dụ để hiểu rõ hơn. Nội dung tập tin MyNotepad.cpp sau khi cài đặt với hộp thoại Open File và Find ở trên như sau: 1 // MyNotepad.cpp : Defines the entry point for the application. 2 // 3 #include "stdafx.h" 4 #include "resource.h" 5 #include "richedit.h" 6 #include "commdlg.h" 7 8 #define MAX_LOADSTRING 100 9 #define MAX_STRING_LEN 256 10 #define UNTITLED TEXT ("Untitled") 11 12 // Global Variables: 13 HINSTANCE hInst; // current instance 14 TCHAR szTitle[MAX_LOADSTRING]; // The title bar text 15 TCHAR szWindowClass[MAX_LOADSTRING]; // The title bar text 16 HWND hWnd; 17 HWND hDlgModeless; 18 static TCHAR szFindText [MAX_STRING_LEN]; 19 OPENFILENAME ofn; 20 21 // Foward declarations of functions included in this code module: 22 ATOM MyRegisterClass(HINSTANCE hInstance); 23 BOOL InitInstance(HINSTANCE, int); 24 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 25 LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM); 26 void DoCaption(HWND hWnd, TCHAR * szTitleName); 27 void OkMessage(HWND hWnd, TCHAR * szMessage, TCHAR * szTitleName); 28 void PopFileInitialize (HWND hWnd); 29 BOOLPopFileOpenDlg(HWND hWnd, PTSTR pstrFileName, PTSTR pstrTitleName); 30 BOOLPopFileRead(HWND hwndEdit, PTSTR pstrFileName); 31 HWND PopFindFindDlg(HWND hWnd); 32 BOOL PopFindFindText(HWND hwndEdit, int * piSearchOffset, 33 LPFINDREPLACE pfr); 34 35 int APIENTRY WinMain(HINSTANCE hInstance, 36 HINSTANCE hPrevInstance, 37 LPSTR lpCmdLine, 38 int nCmdShow) 39 { 40 // TODO: Place code here. 41 MSG msg; 42 HACCEL hAccelTable; 43 29
  30. Bài 3. Hộp hội thoại 44 // Initialize global strings 45 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); 46 LoadString(hInstance, IDC_MYNOTEPAD, szWindowClass, 47 MAX_LOADSTRING); 48 MyRegisterClass(hInstance); 49 50 // Perform application initialization: 51 if (!InitInstance (hInstance, nCmdShow)) 52 { 53 return FALSE; 54 } 55 56 hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_MYNOTEPAD); 57 // Main message loop: 58 while (GetMessage(&msg, NULL, 0, 0)) 59 { 60 if (hDlgModeless == NULL || !IsDialogMessage (hDlgModeless, &msg)) 61 { 62 if (!TranslateAccelerator(hWnd, hAccelTable, &msg)) 63 { 64 TranslateMessage(&msg); 65 DispatchMessage(&msg); 66 } 67 } 68 } 69 70 return msg.wParam; 71 } 72 73 // FUNCTION: MyRegisterClass() 74 // 75 // PURPOSE: Registers the window class. 76 // 77 // COMMENTS: 78 // This function and its usage is only necessary if you want this code 79 // to be compatible with Win32 systems prior to the 'RegisterClassEx' 80 // function that was added to Windows 95. It is important to call this function 81 // so that the application will get 'well formed' small icons associated 82 // with it. 83 ATOM MyRegisterClass(HINSTANCE hInstance) 84 { 85 WNDCLASSEX wcex; 86 87 wcex.cbSize = sizeof(WNDCLASSEX); 88 89 wcex.style = CS_HREDRAW | CS_VREDRAW; 90 wcex.lpfnWndProc = (WNDPROC)WndProc; 91 wcex.cbClsExtra = 0; 92 wcex.cbWndExtra = 0; 30
  31. Lập trình Windows 93 wcex.hInstance = hInstance; 94 wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_MYNOTEPAD); 95 wcex.hCursor = LoadCursor(NULL, IDC_ARROW); 96 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 97 wcex.lpszMenuName = (LPCSTR)IDC_MYNOTEPAD; 98 wcex.lpszClassName = szWindowClass; 99 wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL); 100 101 return RegisterClassEx(&wcex); 102 } 103 104 // 105 // FUNCTION: InitInstance(HANDLE, int) 106 // 107 // PURPOSE: Saves instance handle and creates main window 108 // 109 // COMMENTS: 110 // In this function, we save the instance handle in a global variable and 111 // create and display the main program window. 112 // 113 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) 114 { 115 hInst = hInstance; // Store instance handle in our global variable 116 117 hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, 118 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); 119 120 if (!hWnd) 121 { 122 return FALSE; 123 } 124 125 ShowWindow(hWnd, nCmdShow); 126 UpdateWindow(hWnd); 127 128 return TRUE; 129 } 130 131 // 132 // FUNCTION: WndProc(HWND, unsigned, WORD, LONG) 133 // 134 // PURPOSE: Processes messages for the main window. 135 // 136 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, 137 LPARAM lParam) 138 { 139 int wmId, wmEvent; 140 static HWND hwndEdit; 141 int iSelBeg, iSelEnd, iEnable; 31
  32. Lập trình Windows 142 static TCHAR szFileName[MAX_PATH], szTitleName[MAX_PATH]; 143 static int iOffset; 144 static UINT msgFindReplace; 145 LPFINDREPLACE pfr; 146 147 switch (message) 148 { 149 case WM_CREATE: 150 LoadLibrary("riched32.dll"); 151 hwndEdit = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, 152 "RICHEDIT", NULL, WS_CHILD|WS_VISIBLE| 153 WS_HSCROLL|WS_VSCROLL|ES_AUTOHSCROLL| 154 ES_AUTOVSCROLL|ES_NOHIDESEL| 155 ES_DISABLENOSCROLL|ES_MULTILINE, 156 0, 0, 0, 0, hWnd, NULL, hInst, NULL); 157 DoCaption (hWnd, szTitleName); 158 PopFileInitialize(hWnd); 159 msgFindReplace = RegisterWindowMessage(FINDMSGSTRING); 160 return TRUE; 161 case WM_SIZE: 162 MoveWindow(hwndEdit, 0, 0, LOWORD(lParam), 163 HIWORD(lParam), TRUE); 164 break; 165 case WM_SETFOCUS: 166 SetFocus(hwndEdit); 167 break; 168 case WM_INITMENUPOPUP: 169 switch (lParam) 170 { 171 case 1: // Edit menu 172 // Enable Undo if edit control can do it 173 EnableMenuItem ((HMENU) wParam, IDM_UNDO, 174 SendMessage (hwndEdit, EM_CANUNDO, 0, 0L) ? 175 MF_ENABLED : MF_GRAYED) ; 176 // Enable Paste if text is in the clipboard 177 EnableMenuItem ((HMENU) wParam, IDM_PASTE, 178 IsClipboardFormatAvailable (CF_TEXT) ? 179 MF_ENABLED : MF_GRAYED) ; 180 // Enable Cut, Copy, and Del if text is selected 181 SendMessage (hwndEdit, EM_GETSEL, (WPARAM) 182 &iSelBeg, (LPARAM) &iSelEnd) ; 183 iEnable = iSelBeg != iSelEnd ? MF_ENABLED : 184 MF_GRAYED ; 185 EnableMenuItem ((HMENU) wParam, IDM_CUT, 186 iEnable) ; 187 EnableMenuItem ((HMENU) wParam, IDM_COPY, 188 iEnable) ; 189 EnableMenuItem ((HMENU) wParam, IDM_DELETE, 190 iEnable) ; 32
  33. Bài 3. Hộp hội thoại 191 break ; 192 } 193 case WM_COMMAND: 194 wmId = LOWORD(wParam); 195 wmEvent = HIWORD(wParam); 196 // Parse the menu selections: 197 switch (wmId) 198 { 199 // Popup File 200 case IDM_NEW: 201 MessageBox(NULL, "Ban vua chon muc File New", 202 "Thong bao", MB_OK); 203 break; 204 case IDM_OPEN: 205 if (PopFileOpenDlg (hWnd, szFileName, szTitleName)) 206 { 207 if (!PopFileRead (hwndEdit, szFileName)) 208 { 209 OkMessage (hWnd, TEXT ("Could not read 210 file %s!"), szTitleName) ; 211 szFileName[0] = '\0' ; 212 szTitleName[0] = '\0' ; 213 } 214 } 215 DoCaption (hWnd, szTitleName) ; 216 break; 217 case IDM_SAVE: 218 MessageBox(NULL, "Ban vua chon muc File Save", 219 "Thong bao", MB_OK); 220 break; 221 case IDM_SAVE_AS: 222 MessageBox(NULL, "Ban vua chon muc File Save As", 223 "Thong bao", MB_OK); 224 break; 225 case IDM_PAGE_SETUP: 226 MessageBox(NULL, "Ban vua chon muc File Page 227 Setup", "Thong bao", MB_OK); 228 break; 229 case IDM_PRINT: 230 MessageBox(NULL, "Ban vua chon muc File Print", 231 "Thong bao", MB_OK); 232 break; 233 case IDM_EXIT: 234 DestroyWindow(hWnd); 235 break; 236 // Popup Edit 237 case IDM_UNDO: 238 SendMessage (hwndEdit, WM_UNDO, 0, 0) ; 239 break; 33
  34. Lập trình Windows 240 case IDM_CUT: 241 SendMessage (hwndEdit, WM_CUT, 0, 0) ; 242 break; 243 case IDM_COPY: 244 SendMessage (hwndEdit, WM_COPY, 0, 0) ; 245 break; 246 case IDM_PASTE: 247 SendMessage (hwndEdit, WM_PASTE, 0, 0) ; 248 break; 249 case IDM_DELETE: 250 SendMessage (hwndEdit, WM_CLEAR, 0, 0) ; 251 break; 252 case IDM_FIND: 253 SendMessage (hwndEdit, EM_GETSEL, 0, (LPARAM) 254 &iOffset); 255 hDlgModeless = PopFindFindDlg(hWnd) ; 256 return 0 ; 257 break; 258 case IDM_FIND_NEXT: 259 MessageBox(NULL, "Ban vua chon muc Edit Find 260 Next", "Thong bao", MB_OK); 261 break; 262 case IDM_REPLACE: 263 MessageBox(NULL, "Ban vua chon muc Edit Replace", 264 "Thong bao", MB_OK); 265 break; 266 case IDM_GOTO: 267 MessageBox(NULL, "Ban vua chon muc Edit Goto", 268 "Thong bao", MB_OK); 269 break; 270 case IDM_SELECT_ALL: 271 SendMessage (hwndEdit, EM_SETSEL, 0, -1) ; 272 break; 273 case IDM_TIME_DATE: 274 MessageBox(NULL, "Ban vua chon muc Edit Time 275 Date", "Thong bao", MB_OK); 276 break; 277 // Popup Format 278 case IDM_WORD_WRAP: 279 MessageBox(NULL, "Ban vua chon muc Format Word 280 Wrap", "Thong bao", MB_OK); 281 break; 282 case IDM_FONT: 283 MessageBox(NULL, "Ban vua chon muc Format Font", 284 "Thong bao", MB_OK); 285 break; 286 // Popup Help 287 case IDM_HELP_TOPICS: 288 MessageBox(NULL, "Ban vua chon muc Help Topics", 34
  35. Bài 3. Hộp hội thoại 289 "Thong bao", MB_OK); 290 break; 291 case IDM_ABOUT: 292 DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, 293 (DLGPROC)About); 294 break; 295 default: 296 return DefWindowProc(hWnd, message, wParam, 297 lParam); 298 } 299 break; 300 case WM_DESTROY: 301 PostQuitMessage(0); 302 break; 303 default: 304 // Process "Find-Replace" messages 305 if (message == msgFindReplace) 306 { 307 pfr = (LPFINDREPLACE) lParam ; 308 if (pfr->Flags & FR_DIALOGTERM) 309 hDlgModeless = NULL; 310 if (pfr->Flags & FR_FINDNEXT) 311 if (!PopFindFindText (hwndEdit, &iOffset, pfr)) 312 OkMessage (hWnd, TEXT ("Text not found!"), 313 TEXT ("\0")); 314 return 0 ; 315 } 316 break ; 317 } 318 return DefWindowProc (hWnd, message, wParam, lParam); 319 } 320 321 // Mesage handler for about box. 322 LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, 323 LPARAM lParam) 324 { 325 switch (message) 326 { 327 case WM_INITDIALOG: 328 return TRUE; 329 case WM_COMMAND: 330 if (LOWORD(wParam) == IDOK || LOWORD(wParam) == 331 IDCANCEL) 332 { 333 EndDialog(hDlg, LOWORD(wParam)); 334 return TRUE; 335 } 336 break; 337 } 35
  36. Lập trình Windows 338 return FALSE; 339 } 340 341 void DoCaption (HWND hWnd, TCHAR * szTitleName) 342 { 343 TCHAR szCaption[64 + MAX_PATH] ; 344 wsprintf (szCaption, TEXT ("%s - %s"), 345 szTitleName[0] ? szTitleName : UNTITLED, szTitle) ; 346 SetWindowText (hWnd, szCaption) ; 347 } 348 349 void OkMessage (HWND hWnd, TCHAR * szMessage, TCHAR * szTitleName) 350 { 351 TCHAR szBuffer[64 + MAX_PATH] ; 352 wsprintf (szBuffer, szMessage, szTitleName[0] ? szTitleName : UNTITLED) ; 353 MessageBox (hWnd, szBuffer, szTitle, MB_OK | MB_ICONEXCLAMATION) ; 354 } 355 356 void PopFileInitialize(HWND hWnd) 357 { 358 static TCHAR szFilter[] = TEXT ("Text Files (*.TXT)\0*.txt\0") \ 359 TEXT ("All Files (*.*)\0*.*\0\0") ; 360 ofn.lStructSize = sizeof (OPENFILENAME) ; 361 ofn.hwndOwner = hWnd ; 362 ofn.hInstance = NULL ; 363 ofn.lpstrFilter = szFilter ; 364 ofn.lpstrCustomFilter = NULL ; 365 ofn.nMaxCustFilter = 0 ; 366 ofn.nFilterIndex = 0 ; 367 ofn.lpstrFile = NULL ; // Set in Open and Close functions 368 ofn.nMaxFile = MAX_PATH ; 369 ofn.lpstrFileTitle = NULL ; // Set in Open and Close functions 370 ofn.nMaxFileTitle = MAX_PATH ; 371 ofn.lpstrInitialDir = NULL ; 372 ofn.lpstrTitle = NULL ; 373 ofn.Flags = 0 ; // Set in Open and Close functions 374 ofn.nFileOffset = 0 ; 375 ofn.nFileExtension = 0 ; 376 ofn.lpstrDefExt = TEXT ("txt") ; 377 ofn.lCustData = 0L ; 378 ofn.lpfnHook = NULL ; 379 ofn.lpTemplateName = NULL ; 380 } 381 382 BOOL PopFileOpenDlg(HWND hWnd, PTSTR pstrFileName, PTSTR pstrTitleName) 383 { 384 ofn.hwndOwner = hWnd; 385 ofn.lpstrFile = pstrFileName; 386 ofn.lpstrFileTitle = pstrTitleName; 36
  37. Bài 3. Hộp hội thoại 387 ofn.Flags = OFN_HIDEREADONLY|OFN_CREATEPROMPT; 388 return GetOpenFileName (&ofn) ; 389 } 390 391 BOOL PopFileRead (HWND hwndEdit, PTSTR pstrFileName) 392 { 393 BYTE bySwap ; 394 DWORD dwBytesRead ; 395 HANDLE hFile ; 396 int i, iFileLength, iUniTest ; 397 PBYTE pBuffer, pText, pConv ; 398 399 // Open the file. 400 if (INVALID_HANDLE_VALUE == 401 (hFile = CreateFile (pstrFileName, GENERIC_READ, FILE_SHARE_READ, 402 NULL, OPEN_EXISTING, 0, NULL))) 403 return FALSE ; 404 405 // Get file size in bytes and allocate memory for read. 406 // Add an extra two bytes for zero termination. 407 iFileLength = GetFileSize (hFile, NULL) ; 408 pBuffer = (PBYTE) malloc (iFileLength + 2) ; 409 410 // Read file and put terminating zeros at end. 411 ReadFile (hFile, pBuffer, iFileLength, &dwBytesRead, NULL) ; 412 CloseHandle (hFile) ; 413 pBuffer[iFileLength] = '\0' ; 414 pBuffer[iFileLength + 1] = '\0' ; 415 416 // Test to see if the text is unicode 417 iUniTest = IS_TEXT_UNICODE_SIGNATURE | 418 IS_TEXT_UNICODE_REVERSE_SIGNATURE ; 419 420 if (IsTextUnicode (pBuffer, iFileLength, &iUniTest)) 421 { 422 pText = pBuffer + 2 ; 423 iFileLength -= 2 ; 424 if (iUniTest & IS_TEXT_UNICODE_REVERSE_SIGNATURE) 425 { 426 for (i = 0 ; i < iFileLength / 2 ; i++) 427 { 428 bySwap = ((BYTE *) pText) [2 * i] ; 429 ((BYTE *) pText) [2 * i] = ((BYTE *) pText) [2 * i + 1] ; 430 ((BYTE *) pText) [2 * i + 1] = bySwap ; 431 } 432 } 433 434 // Allocate memory for possibly converted string 35 pConv = (PBYTE) malloc (iFileLength + 2) ; 37
  38. Lập trình Windows 436 437 // If the edit control is not Unicode, convert Unicode text to 438 // non-Unicode (ie, in general, wide character). 439 440 #ifndef UNICODE 441 WideCharToMultiByte (CP_ACP, 0, (PWSTR) pText, -1, (LPSTR)pConv, 442 iFileLength + 2, NULL, NULL) ; 443 // If the edit control is Unicode, just copy the string 444 #else 445 lstrcpy ((PTSTR) pConv, (PTSTR) pText) ; 446 #endif 447 } 448 else // the file is not Unicode 449 { 450 pText = pBuffer ; 451 // Allocate memory for possibly converted string. 452 pConv = (PBYTE) malloc (2 * iFileLength + 2) ; 453 // If the edit control is Unicode, convert ASCII text. 454 #ifdef UNICODE 455 MultiByteToWideChar (CP_ACP, 0, pText, -1, (PTSTR) pConv, 456 iFileLength + 1) ; 457 // If not, just copy buffer 458 #else 459 lstrcpy ((PTSTR) pConv, (PTSTR) pText) ; 460 #endif 461 } 462 463 SetWindowText (hwndEdit, (PTSTR) pConv) ; 464 free (pBuffer) ; 465 free (pConv) ; 466 467 return TRUE ; 468 } 469 470 HWND PopFindFindDlg(HWND hWnd) 471 { 472 static FINDREPLACE fr ; // must be static for modeless dialog!!! 473 474 fr.lStructSize = sizeof (FINDREPLACE) ; 475 fr.hwndOwner = hWnd ; 476 fr.hInstance = NULL ; 477 fr.Flags = FR_HIDEUPDOWN | FR_HIDEMATCHCASE | 478 FR_HIDEWHOLEWORD ; 479 fr.lpstrFindWhat = szFindText; 480 fr.lpstrReplaceWith = NULL ; 481 fr.wFindWhatLen = MAX_STRING_LEN ; 482 fr.wReplaceWithLen = 0 ; 483 fr.lCustData = 0 ; 484 fr.lpfnHook = NULL ; 38
  39. Bài 3. Hộp hội thoại 485 fr.lpTemplateName = NULL ; 486 487 return FindText (&fr) ; 488 } 489 490 BOOL PopFindFindText (HWND hwndEdit, int * piSearchOffset, LPFINDREPLACE 491 pfr) 492 { 493 int iLength, iPos ; 494 PTSTR pstrDoc, pstrPos ; 495 // Read in the edit document 496 iLength = GetWindowTextLength (hwndEdit) ; 497 if (NULL == (pstrDoc = (PTSTR) malloc ((iLength + 1) * sizeof (TCHAR)))) 498 return FALSE ; 499 GetWindowText (hwndEdit, pstrDoc, iLength + 1) ; 500 // Search the document for the find string 501 pstrPos = _tcsstr (pstrDoc + * piSearchOffset, pfr->lpstrFindWhat) ; 502 free (pstrDoc) ; 503 // Return an error code if the string cannot be found 504 if (pstrPos == NULL) 505 return FALSE ; 506 // Find the position in the document and the new start offset 507 iPos = pstrPos - pstrDoc ; 508 * piSearchOffset = iPos + lstrlen (pfr->lpstrFindWhat) ; 509 // Select the found text 510 SendMessage (hwndEdit, EM_SETSEL, iPos, * piSearchOffset) ; 511 SendMessage (hwndEdit, EM_SCROLLCARET, 0, 0) ; 512 513 return TRUE ; 514 } 3.4. TÓM TẮT Tiếp theo bài 2, trong bài học này chúng ta đã làm quen với việc thao tác một đối tượng giao diện cũng rất phổ biến là hộp thoại. Phần 3.2 giúp chúng ta hiểu rõ cách tạo lập tài nguyên hộp thoại chứa các đối tượng control trực quan. Với tài nguyên có được, cũng như cửa sổ chính, chúng ta đã tìm hiểu và xây dựng hàm xử lý cho các hộp thoại. Ngoài ra, chúng ta cũng phần nào làm quen với ứng dụng dạng dialog-based khi xây dựng project MayTinh trong phần 3.2.2. 39
  40. Lập trình Windows Phần 3.3 trình bày một vấn đề tương đối khó hơn, đó là tìm hiểu các cấu trúc và các hàm API mà Windows đã hỗ trợ để xây dựng các hộp thoại phổ biến với giao diện và cách xử lý đã được định nghĩa trước trong thư viện liên kết động của hệ thống. Với hai hộp thoại Open File và Find, chúng ta đã hoàn thiện thêm một phần chức năng của ứng dụng MyNotepad. 3.5. CÂU HỎI ÔN TẬP – BÀI TẬP 3.5.1. Tạo lập một hộp thoại trong tài nguyên. Với công cụ Controls và resource editor, tìm hiểu các dạng control mà Visual C++ 6.0 hỗ trợ. Tìm hiểu các style cũng như các thuộc tính hỗ trợ trên hộp thoại Properties của chúng. Chọn chức năng Layout Test (Ctrl+T) để kiểm tra kết quả thể hiện. 3.5.2. Các control trên hộp thoại được sắp xếp theo thứ tự mà khi người dùng thao tác, họ có thể chọn tổ hợp phím Tab hoặc Ctrl+Tab để chuyển focus. Ta dùng chức năng Layout Tab Oder (Ctrl+D) để sắp xếp thứ tự của chúng và test kết quả thể hiện. Ngoài ra, các control cũng nhận thao tác phím nóng bằng cách thiết lập ký tự nóng của từng control, đó là ký tự có gạch dưới thiết lập bằng ký tự ‘&’ trong tài nguyên (như đối với menu). Thực hành và xem kết quả thể hiện. 3.5.3*. Tìm hiểu lại về các đối tượng control trong MSDN. Nếu được tạo lập trên hộp thoại thì chúng được xử lý như thế nào (kết hợp cho từng kiểu). Tìm hiểu các hàm về chúng. 3.5.4*. Tìm hiểu trong MSDN về các hộp thoại thông dụng như Print, Page Setup, Font, để hoàn thiện ứng dụng MyNotepad tựa như ứng dụng Notepad của MS Windows. 40