VIỆN ĐẠI HỌC MỞ HÀ NỘI

KHOA CÔNG NGHỆ TIN HỌC

DƯƠNG THĂNG LONG

LẬP TRÌNH TRÊN WINDOWS VỚI MFC

NHÀ XUẤT BẢN KHOA HỌC VÀ KỸ THUẬT

1

LỜI NÓI ĐẦU
Ngành sản xuất phần mềm đóng vai trò rất quan trọng trong lĩnh vực Công nghệ tin học, nhờ đó các vấn đề của cuộc sống đã được giải quyết một cách hiệu quả. Thực tế hiện nay đang cần nhiều phần mềm tin học để ứng dụng trong mọi lĩnh vực của cuộc sống, do đó đòi hỏi nhiều hơn nữa nỗ lực của các công ty phần mềm, các chuyên gia phát triển phần mềm, đặc biệt là các kỹ sư lập trình viên phần mềm. Trong đó khâu quan trọng và mất nhiều nhân lực để thực hiện là giai đoạn lập trình. Giai đoạn lập trình sử dụng một số phương pháp như lập trình cấu trúc, lập trình theo hướng đối tượng. Với mỗi phương pháp chúng ta có thể sử dụng các công cụ thực hiện gồm các ngôn ngữ lập trình, các môi trường lập trình và đặc biệt là phải dựa trên một nền tảng của kiến trúc hệ điều hành đang sử dụng. Hơn nữa, Windows là hệ điều hành của Microsoft được sử dụng hầu hết trong các máy tính và các ứng dụng phải lấy nó làm phần nền khi thực hiện, do đó các phần mềm hầu hết được lập trình để chạy trên hệ điều hành Windows. Để giúp các sinh viên chuyên ngành công nghệ tin học, các lập trình viên nắm bắt được những vấn đề cơ bản của lập trình trên Windows, ngôn ngữ lập trình C++ theo hướng đối tượng để học tập và làm việc chúng tôi xin giới thiệu cuốn sách "Lập trình trên Windows với MFC". Sách gồm có 10 chương như sau: Chương 1: Giới thiệu phương pháp lập trình trên Windows, sự tương tác giữa Windows và các phần mềm ứng dụng, các khái niệm liên quan. Qua đó sử dụng ngôn ngữ C++ và bộ thư viện MFC để thể hiện các kết quả trong tài liệu này. Chương 2: Trình bày cơ chế ánh xạ thông điệp, cách xử lý thông điệp và một số thông điệp cơ bản. Cơ chế xuất dữ liệu lên cửa sổ. Chương 3: Các tài nguyên của chương trình, cách tạo lập, sử dụng và xử lý thông điệp tương ứng bao gồm thực đơn, hộp thoại và các điều khiển trên đó, các biểu tượng và hình ảnh.

2

Chương 4: Trình bày cơ chế đồ họa của lập trình trên Windows, các lệnh làm việc trên ngữ cảnh thiết bị (Device Context - DC) và cửa sổ ảo - Virtual Window. Chương 5: Xử lý văn bản trên cửa sổ, tạo và sử dụng cấu trúc phông chữ, cơ chế cập nhật thông tin trên cửa sổ. Chương 6: Trình bày các điều khiển nâng cao trong Windows, các lớp làm việc tương ứng của thư viện MFC như thanh công cụ, trạng thái, cây... Chương 7: Một kiến trúc quan trọng trong hầu hết các ứng dụng, đó là kiến trúc tài liệu/quan sát (document/view), đi từ các khái niệm cơ bản đến cách làm việc trên ứng dụng. Chương 8: Trình bày cơ chế vào ra dữ liệu với các thiết bị ngoài như ổ đĩa, máy in. Chương 9: Một trong những đặc tính quan trọng nhất trên Windows đó là phương pháp lập trình đa tuyến được trình bày trong chương này. Chương 10: Cách tạo và sử dụng thư viện liên kết động - DLL, một thành phần không thể thiếu trong hầu hết các ứng dụng của Windows. Chương 11: Giới thiệu cách tiếp cận lập trình theo kiểu client/server, các khái niệm liên quan, cách tạo các ActiveX và lập trình trên nó. Trong mỗi chương đều có ví dụ minh họa cho các vấn đề được trình bày, các ví dụ đã được chạy thử và cho kết quả tốt. Cuối mỗi chương có các bài tập để cho bạn đọc có thể tham khảo và tự kiểm tra các kiến thức cũng như tăng thêm kỹ năng lập trình. Tác giả mong nhận được những đóng góp của các bạn đọc. Mọi ý kiến xin gửi về địa chỉ: lduongthang@yahoo.com hoặc Dương Thăng Long, Khoa Công nghệ Tin học - Viện Đại học Mở Hà Nội.

Hà Nội ngày 14 tháng 02 năm 2006

Tác giả

3

Chương 1 TỔNG QUAN VỀ LẬP TRÌNH WINDOWS
1.1. Giới thiệu về hệ điều hành Windows
Windows là một hệ điều hành đang được sử dụng hầu hết ở các máy tính trên thế giới, là một sản phẩm của hãng phần mềm nổi tiếng Microsoft. Windows được phát triển từ MS-DOS, trong đó mở rộng nhiều đặc tính quan trọng như giao diện, cách làm việc, sự hỗ trợ đối với các thiết bị ngoại vi,… Sau đây là một số đặc tính được đề cập nhằm giúp độc giả hiểu hơn về môi trường Windows, các hoạt động, các thành phần, sự tương tác giữa Windows với ứng dụng, từ đó lập trình sẽ dễ dàng hơn.

1.1.1. Đặc trưng của Windows
- Môi trường giao diện đồ họa Như chúng ta đã biết hệ điều hành Windows cung cấp giao diện đồ họa, nên mọi thành phần giao diện trên màn hình đều dưới dạng hình ảnh tạo cho người sử dụng cảm giác thân thiện và trực quan hơn. Các ứng dụng trên Windows cũng được xây dựng trên cơ sở giao diện đồ họa mà Windows đã cung cấp. Vì vậy một phần rất lớn các chương trình ứng dụng là cung cấp cơ chế giao diện đồ họa này. - Tính đa nhiệm Hệ điều hành Windows cho phép tại một thời điểm có thể thực hiện được nhiều công việc khác nhau trên máy tính, mỗi công việc gọi là một task. Thực chất của tính đa nhiệm là khai thác tối đa tốc độ xử lý nhanh của CPU trên máy tính, có thể minh họa theo mô hình gồm 3 task như sau:

4

- Hỗ trợ quản lý hầu hết các thiết bị ngoại vi. - Hỗ trợ kết nối mạng. - Tương thích với các ứng dụng của DOS. Khi thực hiện một chương trình trên DOS thì Windows sẽ tạo ra một môi trường DOS giả lập để chạy. Vì vậy hầu hết các chương trình trên DOS sẽ thực hiện được trên Windows.

1.1.2. Các thành phần giao diện trên Windows
Trên Windows các ứng dụng rất thân thiện với người dùng, bởi giao diện đồ họa trực quan, kết hợp với thiết bị chuột thao tác dễ dàng. Do vậy chương trình trên Windows thường rất lớn song chủ yếu là cung cấp các giao diện cho người dùng. Các thành phần giao diện chính như sau: - Desktop Model Giống như bàn làm việc của chúng ta, trên đó sẽ có những thành phần của chúng ta để làm việc và những cái chúng ta đang làm việc. Bao gồm các biểu tượng, các cửa sổ chương trình,... Có thể nói thành phần này là một cửa sổ lớn nhất, chứa toàn bộ các thành phần khác và nơi thể hiện của mọi ứng dụng.

5

- Mouse Chuột được sử dụng cho hầu hết các thao tác điều khiển, lựa chọn và vẽ. Tuy nhiên chúng ta có thể dùng bàn phím thay thế, nhưng đó là điều vi phạm tới nguyên tắc thiết kế cơ bản của Windows. - Icon và bitmap Chúng ta thấy rằng một bức tranh đáng giá với hàng ngàn từ - “a picture is worth a thousand words”. Icon là một bức tranh thu nhỏ diễn tả cho một thành phần nào đó của các ứng dụng trên Windows, bitmap là một định dạng hình ảnh để mô tả lưu giữ cho các icon. Icon được sử dụng để chuyển tải thông tin nhanh chóng, trực quan và đơn giản tới người dùng. - Windows Là các cửa sổ, nơi diễn ra mọi sự tương tác giữa người dùng với chương trình ứng dụng. - Menu, Toolbar, Status bar Là các thành phần trên cửa sổ, hỗ trợ và cung cấp cho người dùng những chức năng cần thiết ở mức đơn giản nhất có thể, thông báo tới người dùng trạng thái đang làm việc. - Dialog box Là một cửa sổ dạng hộp thoại cho phép người dùng tương tác với chương trình, như nhập dữ liệu, xem thông tin, ...

1.1.3. Các khái niệm cơ bản
- Process Process (tiến trình) là quá trình thực hiện một chương trình từ khi khởi động cho đến khi kết thúc. Windows cho phép thực hiện nhiều chương trình cùng một lúc, tức là sẽ có nhiều process cùng chạy trên máy. Có thể hiểu mỗi task (công việc thực hiện) sẽ tương ứng với một process, tuy nhiên có những process được tạo ra từ những process khác.

6

- Thread Thread (luồng) là một đơn vị thực hiện nào đó trong chương trình, như vậy trong một process có ít nhất một thread và có thể có nhiều. Điều này cho phép chúng ta xây dựng các chương trình với nhiều tác vụ cùng thực hiện, mỗi tác vụ tương ứng với một luồng.

Thread được tạo ra từ một process nào đó, thậm chí có thể được tạo ra bởi một thread khác và nó có thể kết thúc sau process, hoặc kết thúc tại thời điểm bất kỳ (như trên hình vẽ). - Các kiểu dữ liệu cơ bản Cũng như trong ngôn ngữ C, các kiểu dữ liệu chuẩn đều được sử dụng trên Windows bao gồm:
Tên ki u int, long, unsigned float, double char Ý nghĩa S nguyên S th c Ký t

Mọi thành phần và đối tượng trong chương trình đều quản lý bằng các số định danh (Handle Identify), thường là số nguyên không dấu 32 bít. Chúng ta có một số kiểu để lưu các định danh như sau:
Tên ki u WINAPI HANDLE HINSTANCE Ý nghĩa Ki u s d ng khai báo chương trình Giá tr đ đ nh danh các tài nguyên S hi u đ nh danh ti n trình đang ch y

7

HWND BYTE WORD LONG UINT BOOL LPSTR LPCSTR LRESULT LPARAM WPARAM LPVOID CALLBACK HDC HFONT HICON HFILE HMENU HPEN HBRUSH HBITMAP

S hi u đ nh danh m t c a s S nguyên 8 bít không d u S nguyên 16 bít không d u S nguyên 32 bít có d u S nguyên 32 bít không d u S nguyên có giá tr TRUE ho c FALSE Con tr t i xâu 1 ký t H ng con tr t i 1 xâu ký t Ki u tr v c a các hàm Ki u đ nh nghĩa tham s c a các hàm d ng long

Ki u đ nh nghĩa tham s c a hàm d ng word Con tr không ki u S d ng trong khai báo hàm g i ngư c S hi u đ nh danh ng c nh thi t b Đ nh danh phông ch Đ nh danh bi u tư ng Đ nh danh t p tin Đ nh danh th c đơn Đ nh danh nét bút v Đ nh danh ki u n n tô Đ nh danh nh bitmap các tài li u khác.

Còn r t nhi u ki u có th tham kh o

1.2. Lập trình trên Windows
1.2.1. Giao diện lời gọi (call-based interface)
- Thư viện API (Application Programming Interface) Lập trình trên Windows phải sử dụng giao diện lời gọi để truy nhập tới hệ thống (call-based interface). Đó chính là một tập rất lớn các hàm được xây dựng bởi hệ thống và thực hiện các tác vụ cơ bản trên hệ điều hành, như đăng ký bộ nhớ, xuất dữ liệu ra màn hình, tạo cửa sổ,... Các hàm này lập nên một thư viện API (Application Programming Interface). Một chương trình

8

ứng dụng sẽ gọi các hàm API này để thực hiện các chức năng, thao tác mong muốn, qua đó sẽ giao tiếp được với Windows. Có thể minh họa bằng sơ đồ sau:

- Thư viện GDI (Graphic Device Interface) Trong thư viện lập trình API có một nhóm các hàm chuyên xử lý đồ họa gọi là GDI, các hàm này sẽ cung cấp cho người lập trình làm việc với các thiết bị đồ họa một cách dễ dàng và độc lập với các thiết bị đó. - Thư viện liên kết động DLL (Dynamic Link Library) Thư viện API chứa một lượng rất lớn các hàm cung cấp cho người lập trình, khối lượng mã lệnh trong chương trình chủ yếu là các xử lý đồ họa vì vậy sẽ rất lớn, có các đoạn mã bị lặp. Do đó Windows cung cấp cho chúng ta một cơ chế liên kết chương trình, gọi là thư viện liên kết động (Dynamic Link Libraries - DLL). Thư viện liên kết động sẽ được lưu giữ ở một tệp mã lệnh độc lập với chương trình, sẽ được liên kết với chương trình trong khi thực hiện, do đó làm cho mã lệnh của các chương trình sẽ nhỏ hơn. Tuy nhiên các chương trình này khi thực hiện đòi hỏi phải có thư viện DLL tương ứng. Thư viện API được cung cấp sẵn trong windows, tức là sau khi cài đặt windows sẽ có luôn thư viện này. API tồn tại dưới dạng thư viện liên kết động. Chi tiết về phần thư viện liên kết động sẽ được trình bày chi tiết ở phần cuối.

9

1.2.2. Sự tương tác giữa chương trình với Windows
- Khái niệm về thông điệp Lập trình trên Windows cơ bản là việc xử lý các thông điệp (messages), các thông điệp này được sinh ra bởi người dùng tác động lên chương trình, bởi hệ thống yêu cầu chương trình hoặc ngay bản thân chương trình tự sinh ra. Mỗi thông điệp là một số nguyên 32 bít không dấu và được gắn với một sự kiện, một tình huống hay một thao tác nào đó, do vậy chúng ta phải nhận biết được để xử lý cho phù hợp. Việc nhận biết này thông qua các tham số của hàm cửa sổ và các macro được cung cấp bởi thư viện API. - Cơ chế phát sinh, nhận và chuyển phát thông điệp trên Windows Đối với hệ điều hành MS-DOS, người lập trình phải gọi các chức năng của DOS để thực hiện yêu cầu thông qua các ngắt (interrupt), DOS sẽ không tự gọi chương trình của chúng ta. Điều này hoàn toàn khác với Windows, một chương trình luôn ở trong trạng thái chờ các thông điệp (message) của mình được gửi đến từ Windows và khi nhận được thông điệp thì chương trình phải thực hiện yêu cầu tương ứng. Chúng ta có thể minh họa sự tương tác bằng sơ đồ sau:

Hệ điều hành DOS cung cấp cho chúng ta các chương trình phục vụ ngắt với các hàm, các chức năng để tương tác với hệ điều hành và làm việc với các thiết bị. Các chương trình ngắt này chỉ ở mức tối thiểu, do đó chúng ta phải xây dựng hầu hết các thủ tục cần thiết khi lập trình.

10

Đối với Windows thì các chức năng được gói trong thư viện API và cầu nối giữa hệ thống với chương trình ứng dụng là các thông điệp. Hơn nữa chương trình trên DOS là chủ động thực hiện các công việc, còn trên Windows hoàn toàn bị động, chương trình chỉ được thực hiện khi có thông điệp gửi tới. - Các loại thông điệp và một số thông điệp cơ bản Có hai loại thông điệp, loại được định nghĩa bởi hệ thống và loại được định nghĩa bởi người lập trình để cung cấp giao diện cho người dùng, gọi tắt là thông điệp hệ thống và thông điệp người dùng. Các thông điệp chuẩn của hệ thống thường định nghĩa dưới dạng các tên hằng có chữ WM (Windows Message) ở đầu. Sau đây là bảng một số thông điệp chuẩn cơ bản:
WM_CHAR WM_LBUTTONUP WM_CREATE WM_CLOSE WM_TIMER WM_NOTIFY WM_KEYDOWN WM_PAINT WM_MOVE WM_KEYDOWN WM_MOUSEMOVE WM_RBUTTONDOWN WM_VSCROLL WM_HSCROLL WM_KEYPRESS WM_COMMAND WM_DESTROY WM_CREATE WM_KEYUP WM_QUIT ...

Còn thông điệp người dùng có thể định nghĩa qua tên hằng hoặc dùng trực tiếp số nguyên, các thông điệp này chủ yếu định nghĩa cho các thành phần giao diện như thực đơn, các điều khiển trên hộp thoại hoặc cửa sổ.

1.2.3. Lập trình trên Windows
- Các thành phần cơ bản của chương trình trên Windows Theo kiến trúc dựa vào thông điệp của Windows, tất cả các chương trình đều có cấu trúc các thành phần chung như nhau. Trong ngôn ngữ C chúng ta phải lập trình tường minh các thành phần chung này trong chương trình. Tuy nhiên với lập trình C++ có một số phần sẽ do cơ chế tự động sinh ra bởi môi trường lập trình (Visual C++), vì vậy người lập trình giảm bớt được công sức viết mã lệnh và chương trình trở nên súc tích, rõ ràng hơn.

11

Dù sao chúng ta cũng phải xem xét các thành phần cơ bản của một chương trình trên Windows là gì? Nó bao gồm 4 thành phần sau: WinMain() Là hàm chính của chương trình, khi chạy chương trình trên máy thì đầu tiên hệ thống sẽ gọi thực hiện hàm này. Hàm này sẽ có khai báo như sau:
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,

int nCmdShow );

Hai tham số đầu hInstance và hPreInstance có kiểu HINSTANCE để xác định số hiệu process tương ứng mỗi lần thực hiện chương trình và số hiệu của lần đang thực hiện kế trước đó, tham số thứ lpCmdLine có kiểu LPSTR xác định tham số dòng lệnh khi khởi động chương trình, cuối cùng là tham số nCmdShow có kiểu int để xác định chế độ cửa sổ chương trình khi khởi động. Trong lập trình C chúng ta phải khai báo và xây dựng hàm này nhưng ở trong C++ chúng sẽ không phải làm việc này. Có thể nói hàm này là lối vào (entry point) đầu tiên của chương trình, sau đó mọi hoạt động sẽ do Windows quản lý và điều khiển. Hàm cửa sổ - Window Function Là hàm rất đặc biệt sẽ được gắn với các cửa sổ trên Windows, hàm này sẽ do máy tự gọi khi hệ thống muốn chuyển thông điệp tới cửa sổ tương ứng. Người lập trình phải xây dựng hàm này để thực hiện xử lý các thông điệp mong muốn. Khai báo của hàm như sau:
LRESULT CALLBACK tên_hàm( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );

Mỗi cửa sổ tồn tại trên Windows phải có một hàm tương ứng để nhận và xử lý thông điệp, tham số kiểu HWND xác định số hiệu cửa sổ chứa thông điệp nhận được, thứ 2 kiểu UINT xác định tên thông điệp, hai tham số còn lại kiểu WPARAM và LPARAM chứa các thông tin liên quan đến thông điệp.
12

Trong lập trình C++ chúng ta sẽ không phải khai báo và xây dựng hàm cửa sổ này. Vòng lặp thông điệp - Message Loop Sẽ được thực hiện trong hàm chính của chương trình (hàm WinMain) sau khi đã tạo và thiết lập để hiển thị cửa sổ chương trình. Vòng lặp này sẽ thực hiện nhận tất cả các thông điệp ứng với tiến trình của chương trình và sẽ gửi đến các cửa sổ tương ứng. Vòng lặp này được thực hiện như sau:
MSG msg; while (GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); }

Trong đó hàm GetMessage() nhận tất cả các thông điệp liên quan đến chương trình, thông điệp nhận được lưu ở tham số msg. Sau đó phân tích, làm mịn thông điệp bằng lệnh TranslateMessage() và chuyển phát tới các hàm cửa sổ tương ứng bằng lệnh DispatchMessage(). Vòng lặp này sẽ kết thúc khi hàm GetMessage() trả về giá trị FALSE, tương ứng với thông điệp nhận được trong msg là WM_QUIT, ngược lại trả về TRUE. - Phương pháp lập trình C++ trên Windows Lập trình C++ hay lập trình hướng đối tượng, chủ yếu xây dựng các lớp sử dụng kế thừa từ các lớp của thư viện MFC (Microsoft Foundation Classes). Do vậy các bạn hãy nắm vững kỹ thuật lập trình hướng đối tượng trước khi thực hiện theo cách này. Các lớp trong thư viện MFC được mở rộng từ thư viện các lớp của ngôn ngữ C++, bao gồm các cấu trúc cơ sở được sử dụng để tạo các ứng dụng trên Windows. Một khung ứng dụng được thiết kế trong thư viện MFC chính là

13

để tạo chương trình trên Windows, nó định nghĩa cấu trúc một ứng dụng và chứa các công việc thông thường cho ứng dụng. Thư viện lập trình MFC sẽ bao gói hầu hết các hàm của thư viện API theo hình thức 1-1, tức là mỗi hàm có trong API sẽ được gói thành một hàm thành viên trong một lớp nào đó của MFC. Tuy nhiên khi lập trình MFC chúng ta có thể sử dụng các hàm API như bình thường. Sơ đồ phân lớp thư viện API, GDI, DLL và MFC minh họa như sau:

- Các thuận lợi khi lập trình MFC MFC tạo ra một khung ứng dụng và từ đó chúng ta có thể xây dựng một ứng dụng trên Windows. Cung cấp thư viện lớn các mã nguồn có thể sử dụng lại, có thể tích hợp với lập trình hướng thủ tục. MFC rút ngắn thời gian phát triển chương trình, các hỗ trợ không làm giảm tính mềm dẻo của chương trình, hỗ trợ các công nghệ phát triển như ActiveX hay lập trình Internet. MFC hỗ trợ truy nhập cơ sở dữ liệu qua thư viện OLE DB và ADO, hỗ trợ lập trình mạng qua Windows Sockets. Cho phép tạo các thành phần giao diện trong chương trình như trang thuộc tính, hộp thoại in, thanh công cụ, … Khi tiếp cận lập trình theo cách này, chúng ta phải xây dựng các lớp mới thừa kế từ các lớp có sẵn của MFC. Việc xử lý các thông điệp sẽ được ánh xạ và định nghĩa thành các phương thức của lớp tương ứng.
14

- Thư viện các lớp trong MFC Thư viện các lớp MFC được cung cấp bởi tệp khai báo afxwin.h do đó phải thực hiện lệnh nạp thư viện này vào trong chương trình như sau:
#include <afxwin.h>

Các lớp cơ bản trong thư viện MFC được giới thiệu ở bảng sau:
Tên l p CWinApp CFrameWnd CView CFile CDocument CDialog CSplitterWnd CFileDialog CFontDialog CPrintDialog CColorDialog CToolBarCtrl CStatusBarCtrl CTreeCtrl CMonthCalCtrl CDC CSingleDocTemplate Ý nghĩa L p ng d ng L p khung c a s L p quan sát tài li u L p làm vi c v i t p tin trên đĩa L p tài li u L p h p tho i L p chia c a s thành nhi u ph n L p h p tho i file L p h p tho i phông ch L p h p tho i in n L p h p tho i ch n màu L p đi u khi n thanh công c L p đi u khi n thanh tr ng thái L p đi u khi n hi n th d ng cây L p đi u khi n ch n tháng L p ng c nh thi t b L p m u tài li u đơn ng d ng

Chú ý: Các lớp có tên với tiền tố là chữ C (Class), các từ trong tên lớp có chữ cái đầu viết hoa, còn lại viết thường. - Các hàm thành viên Là các hàm mà người lập trình luôn phải gọi từ các đối tượng của lớp trong thư viện MFC, các hàm thành viên này hầu hết là bao gói các hàm của thư viện API do đó việc chuyển từ lập trình C sang lập trình MFC tương đối dễ, tuy nhiên cách lập trình MFC giảm bớt rất nhiều công sức cho người lập trình.
15

- Các hàm toàn cục Ngoài các hàm thành viên ở các lớp MFC, thư viện afxwin.h còn cung cấp một số hàm toàn cục (không thuộc lớp của thư viện MFC) và tên các hàm này giống với các hàm API nhưng có thêm tiền tố là Afx (application framework). Ví dụ hàm AfxMessageBox() để hiển thị hộp thông báo cho người dùng. Lập trình C++ theo MFC chủ yếu xây dựng các lớp, các thành viên trong lớp. Để sử dụng các hàm API chúng ta phải sử dụng toán tử phạm vi dấu 4 chấm (::) để viết, ví dụ viết GetDC(); sẽ khác với ::GetDC(); (2) Ở cách viết (1) có nghĩa là gọi hàm thành viên trong lớp đang xử lý, còn cách (2) sẽ gọi hàm của thư viện API. (1)

1.3. Xây dựng khung chương trình MFC
1.3.1. Lớp ứng dụng - CWinApp
Lớp CWinApp là lớp để xây dựng một chương trình ứng dụng trong thư viện MFC, vì vậy chúng ta phải kế thừa từ lớp này để tạo một lớp ứng dụng mới. Cách định nghĩa lớp ứng dụng là:
class { /* khai báo các thành viên mới trong lớp */ public: BOOL InitInstance( ); tên_lớp_ứng_dụng : public CWinApp

/* khai báo các thành viên mới trong lớp */ };

16

Trong đó chúng ta phải nạp chồng hàm InitInstance() để khởi tạo một ứng dụng như tạo cửa sổ và một số tác vụ khác, nếu thực hiện khởi tạo ứng dụng thành công thì trả về giá trị TRUE để tiếp tục, hoặc ngược lại trả về giá trị FALSE để báo cho máy biết chương trình sẽ dừng không chạy tiếp. Ví dụ khai báo một lớp ứng dụng đơn giản sau:
class CMyApp : public CWinApp { public: BOOL InitInstance( ); };

- Một số thành viên dữ liệu của lớp CWinApp:
Tên thành viên Ý nghĩa

m_pszAppName m_hInstance m_lpCmdLine m_nCmdShow m_pActiveWnd m_pszExeName m_pszProfileName m_pMainWnd m_hThread m_nThreadID m_pszRegistryKey m_pszProfileName

Tên của chương trình ứng dụng Số hiệu process của chương trình đang chạy Chuỗi ký tự chứa tham số dòng lệnh Trạng thái cửa sổ ứng dụng Cửa sổ chính của ứng dụng Tên tệp EXE của chương trình Tên tệp INI của ứng dụng Số hiệu cửa sổ chính của chương trình Số hiệu định danh luồng hiện thời Số hiệu của luồng hiện thời Xâu ký tự chứa đăng ký trong hệ thống Xâu ký tự chứa tên các tham số chương trình

Các thành viên hàm của lớp CWinApp sẽ được trình bày cụ thể trong từng phần của tài liệu này.

1.3.2. Lớp CFrameWnd
Lớp này cho phép tạo các cửa sổ cho chương trình và chúng ta kế thừa từ lớp này để xây dựng lớp cửa sổ ứng dụng mới theo mong muốn.

17

Cách khai báo lớp cửa sổ là:
class { /* các khai báo thêm cho lớp cửa sổ mới này */ public: /* khai báo hàm tạo mặc ñịnh ñể tạo cửa sổ */ tên-lớp-cửa-sổ (); }; tên-lớp-cửa-sổ : public CFrameWnd

Ví dụ khai báo một lớp cửa sổ:
class CMyWin : public CFrameWnd { public: CMyWin(); DECLARE_MESSAGE_MAP() };

Trong hàm tạo của lớp cửa sổ này chúng ta phải thực hiện tạo một cửa sổ mới bằng lệnh:
BOOL Create( LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle = WS_OVERLAPPEDWINDOW, const RECT& rect = rectDefault, CWnd* pParentWnd = NULL, LPCTSTR lpszMenuName = NULL, DWORD dwExStyle = 0, CCreateContext* pContext = NULL );

Trong đó: + lpszClassName: tên lớp cửa sổ đã được đăng ký hoặc là tên chuẩn có sẵn trong hệ thống, lớp cửa sổ quy định các thuộc tính chung cho cửa sổ. Tuy nhiên chúng ta có thể đặt là NULL thì máy sẽ dùng các thuộc tính mặc định cho cửa sổ, + lpszWindowName: tên cửa sổ, là chuỗi ký tự xuất hiện trên thanh tiêu đề cửa sổ, + các tham số còn lại có thể không cần và máy sẽ lấy giá trị mặc định. Ví dụ viết lệnh cho hàm tạo của lớp CMyWin để thực hiện tạo một cửa sổ:
18

CMyWin :: CMyWin() { Create( NULL , "Chương trình đơn gi n nh t" ); }

Công việc tạo một đối tượng cửa sổ từ lớp cửa sổ này cho chương trình sẽ được thực hiện trong hàm InitInstance() của lớp CWinApp ở trên, sau đó sẽ hiển thị cửa sổ và vẽ lại nội dung trên cửa sổ. Ví dụ viết lệnh cho hàm InitInstance() của lớp CMyApp ở trên để tạo một cửa sổ:
BOOL CMyApp :: InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); }

Lệnh thứ nhất tạo đối tượng cửa sổ gán vào thành viên m_pMainWnd của lớp ứng dụng CWinApp, tiếp theo hiển thị cửa sổ đã được tạo bằng lệnh ShowWindow() với tham số là trạng thái cửa sổ, có thể lấy từ thành viên m_nCmdShow của lớp ứng dụng hoặc lấy một trong các giá trị sau:
Tên giá tr SW_SHOWNORMAL SW_SHOWMAXIMIZED SW_SHOWMINIMIZED SW_RESTORE SW_HIDE SW_SHOW Ý nghĩa Ch đ bình thư ng Phóng nh t Thu nh t Khôi ph c ch đ trư c đó Làm n c a s Hi n l i c a s sau khi n

Tuy nhiên trong một chương trình không cần tạo cửa sổ thì chúng ta không phải xây dựng lớp cửa sổ này. Khi đó trong hàm InitInstance() của lớp CWinApp không thực hiện tạo cửa sổ, mà chỉ thực hiện những tác vụ khác theo yêu cầu.

19

1.3.3. Hộp thông báo trong MFC
Hộp thông báo là một cửa sổ đặc biệt chỉ để thông báo với người sử dụng về tình trạng thực hiện của chương trình. Trên hộp thông báo có tiêu đề của hộp, dòng thông báo trên hộp và biểu tượng của hộp. MFC cung cấp một hàm API để hiển thị hộp thông báo có cú pháp như sau:
int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );

Trong đó: + lpszText: là dòng chữ thông báo, + nType: kiểu hộp thông báo, xác định các nút lệnh trên đó và biểu tượng. Có thể sử dụng toán tử hoặc bit (|) để kết hợp một giá trị kiểu nút lệnh với một giá trị biểu tượng trong các giá trị sau:
Giá tr Quy đ nh nút l nh trên h p thông báo MB_ABORTRETRYIGNORE MB_HELP MB_OK MB_OKCANCEL MB_RETRYCANCEL MB_YESNO MB_YESNOCANCEL Quy đ nh bi u tư ng trên h p thông báo MB_ICONEXCLAMATION, MB_ICONWARNING MB_ICONINFORMATION, MB_ICONASTERISK MB_ICONQUESTION Bi u tư ng h i MB_ICONSTOP, MB_ICONERROR, MB_ICONHAND Có 3 nút l nh là abort, retry và ignore Có nút l nh tr giúp Nút l nh Ok Nút l nh Ok và Cancel Nút l nh Retry và Cancel Nút l nh Yes và No Nút l nh Yes, No và Cancel Ý nghĩa

Bi u tư ng c nh báo

Bi u tư ng thông tin

Bi u tư ng d ng

20

Tuy nhiên chúng ta có thể bỏ qua tham số nType và nIDHelp và máy sẽ lấy mặc định, khi đó trên hộp thoại sẽ có nút lệnh Ok và biểu tượng chấm than. Hàm hiển thị hộp thông báo này sẽ trả về giá trị nút lệnh mà người sử dụng đã chọn ứng với các giá trị sau:
Giá tr IDYES IDNO IDCANCEL IDOK IDIGNORE IDRETRY IDABORT Ý nghĩa Ch n nút Yes Ch n No Ch n Cancel Ch n Ok Ch n Ignore Ch n Retry Ch n Abort

1.4. Cách tổ chức chương trình MFC
Trong môi trường phát triển chương trình của Visual C++ gồm có hai bộ dịch song song đó là bộ dịch mã lệnh (code compilation) và bộ dịch tài nguyên (resource compilation). Sơ đồ tổ chức và hoạt động của các thành phần trong chương trình được minh họa bởi hình vẽ trang sau. Trong chương trình có các tệp mã lệnh (source files) được kết hợp với các khai báo thư viện của MFC (MFC header files) để dịch thành tệp OBJ files, tệp chứa định nghĩa tài nguyên (resource script files) được dịch thành tệp RES files. Trong quá trình dịch này được kết hợp bởi tệp tin định nghĩa hằng resource.h, cuối cùng hai tệp RES và OBJ được liên kết cùng với các thư viện mã lệnh của Windows và MFC để thành tệp tin thi hành EXE files. Một chương trình gồm có các phần sau: - Khai báo các lớp Phần này thường được thực hiện trong một tệp có phần mở rộng là *.h, chứa mọi khai báo lớp cho ứng dụng. - Viết lệnh cho các hàm thành viên của các lớp đã khai báo

21

Phần này thực hiện trong một tệp mã lệnh *.cpp và trong tệp này chỉ cần nạp tệp khai báo ở trên bằng câu lệnh #include "*.h". - Các phần khác Ngoài ra còn có các phần khác sau này sẽ định nghĩa sau, thường là các tài nguyên của chương trình. Chú ý: Trong tệp mã lệnh chúng ta phải tạo ra một đối tượng ứng dụng duy nhất từ lớp kế thừa của CWinApp.

Nếu chương trình nhỏ thì chúng ta có thể khai báo các lớp và viết lệnh vào trong một tệp mã lệnh *.cpp.

22

Nếu chương trình lớn thì chúng ta có thể tách mỗi khai báo và viết lệnh cho một lớp thành hai tệp *.h và *.cpp riêng biệt. - Cách tạo một chương trình trên Visual C++ Bước 1: Khởi động chương trình Microsoft Visual C++ Chọn thứ tự các chức năng sau: Start Studio 6.0 Microsoft Visual C++. Programs Microsoft Visual

Giao diện của VisualC++ gồm 3 phần cơ bản: phần hiển thị các thành phần của một dự án (project) chương trình, phần lập trình cho một tệp tin mã lệnh và phần kết quả dịch chương trình. Bước 2: Tạo một chương trình mới, mỗi chương trình gọi là một Project Vào thứ tự các chức năng [File] [New] chọn thẻ [Projects] chọn mục [Win32 Application] nhập tên chương trình vào ô [Project name] và chọn [Ok] tiếp theo chọn mục [An empty project] và chọn [Finish]. Chúng ta có thể chọn chức năng [MFC AppWizard (exe)] để tạo chương trình theo các bước hướng dẫn, tuy nhiên có nhiều phần máy tự tạo ra không kiểm soát được. Để đơn giản chúng ta lập trình từ đầu, từ đơn giản đến phức tạp. Bước 3: Tạo một tệp tin mã lệnh để lập trình Chọn chức năng [File] [New] chọn thẻ [Files] chọn mục [C++ Source File] nhập tên tệp vào ô [File name] chọn [Ok]. Nếu cần tạo tệp khai báo chúng ta chọn mục [C/C++ Header File] trong thẻ [Files]. Bước 4: Nhập chương trình vào cửa sổ của tệp vừa tạo ở bước 3. Bước 5: Đặt chương trình ở chế độ có sử dụng thư viện MFC Chọn [Project] Static Library" [Settings...] chọn [Ok]. chọn thẻ [General] chọn "Use MFC in

Bước 6: Bấm "Ctrl+F5" để dịch và chạy thử chương trình.

23

Sau khi dịch kết quả hiển thị ở phần cửa sổ dưới cùng, nếu có lỗi chúng ta bấm đúp chuột vào thông báo lỗi máy sẽ tự động chuyển đến dòng bị lỗi đó và sửa lại.

Ví dụ 1.1
Lập một chương trình chỉ hiện lên một thông báo nào đó khi thực hiện.
#include<afxwin.h> class CMyApp : public CWinApp { public: BOOL InitInstance(); }; CMyApp theApp; BOOL CMyApp::InitInstance() { AfxMessageBox("Chuong trinh don gian nhat, chi co thong bao"); return true; }

Kết quả khi chạy chương trình là:

Ví dụ 1.2
Lập chương trình đơn giản tạo một cửa sổ và hiện lên khi chạy chương trình.
#include<afxwin.h> class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public:

24

CMyWin(); }; CMyApp theApp; /* Hàm kh i t o ng d ng */ BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow( m_nCmdShow ); m_pMainWnd->UpdateWindow(); return true; } /* Hàm t o c a l p c a s */ CMyWin::CMyWin() { Create(NULL, "Chuong trinh tao cua so don gian"); }

Trong hàm InitInstance() chúng ta thực hiện lệnh tạo đối tượng cửa sổ từ lớp CMyWin và gán vào thành phần m_pMainWnd của đối tượng lớp ứng dụng, sau đó hiển thị cửa sổ và vẽ lại cửa sổ đó. Trạng thái hiển thị của cửa sổ là thành phần m_nCmdShow của lớp ứng dụng. Kết quả chương trình sẽ là:

25

1.5. Bài tập
Bài tập 1.1
Lập chương trình hiện thông báo có hai nút lệnh Yes và No cùng với biểu tượng hỏi.

Bài tập 1.2
Lập chương trình tạo một cửa sổ và hiển thị với kích thước to nhất.

Bài tập 1.3
Lập chương trình tạo một cửa sổ và hiển thị ở chế độ thu nhỏ.

Bài tập 1.4
Lập chương trình tạo hai cửa sổ, một cửa sổ có tiêu đề là “Bài tập 1.4” và cửa sổ còn lại tiêu đề là họ tên của bạn. Hiển thị hai cửa sổ đó lên màn hình ở chế độ bình thường.

Bài tập 1.5
Lập chương trình hiện 3 thông báo với nội và biểu tượng như sau: Thông báo 1: “Lập trình Windows” với biểu tượng cảnh báo và hai nút lệnh Ok, Cancel. Thông báo 2: “Thư viện MFC” với biểu tượng thông tin và hai nút lệnh Yes, No. Thông báo 3: “Lập trình Windows với MFC” với biểu tượng dừng và ba nút lệnh Yes, No, Retry.

Bài tập 1.6
Lập chương trình tạo 15 cửa sổ giống nhau với tiêu đề bất kỳ và hiện chúng lên màn hình ở chế độ bình thường.

26

Chương 2 ÁNH XẠ XỬ LÝ THÔNG ĐIỆP
2.1. Cơ chế ánh xạ xử lý thông điệp
Như đã đề cập trong chương trước, lập trình trên Windows là phải xử lý các thông điệp được gửi đến chương trình từ Windows và thực hiện các công việc tương ứng trong chương trình, cụ thể là trong các hàm cửa sổ (windowfunction) hoặc hàm hộp thoại (dialog-function). Đối với lập trình MFC chúng ta phải thực hiện ánh xạ các thông điệp thành các thành viên hàm (methods-phương thức) của một lớp cửa sổ hay hộp thoại nào đó, vì không có hàm cửa sổ trong MFC. Ánh xạ thông điệp thực chất là sự kết hợp giữa một số hiệu thông điệp (message ID) với một hàm thành viên của lớp. Thông điệp thường được chia làm 2 nhóm, gồm nhóm các thông điệp được cung cấp bởi Windows, nhóm các thông điệp là các lệnh từ thực đơn, đối tượng điều khiển của chương trình, ứng với 2 nhóm này thì cách thức ánh xạ sẽ khác nhau. Một lớp nếu có xử lý thông điệp thì cuối cùng của khai báo lớp phải có macro DECLARE_MESSAGE_MAP(), mỗi thông điệp được ánh xạ tới một hàm thành viên của lớp và hàm đó phải được khai báo dạng afx_msg. Khai báo lớp có xử lý thông điệp theo mẫu sau:
class { tên_lớp_dẫn_xuất : public tên_lớp_cơ_sở /* các khai báo khác của lớp */ /* khai báo hàm thành viên thực hiện ánh xạ thông ñiệp */ DECLARE_MESSAGE_MAP() };

27

Trong phần khai báo thành viên hàm chúng ta phải thực hiện khai báo các hàm sẽ xử lý thông điệp tương ứng theo mẫu sau:
afx_msg tên-kiểu-dữ-liệu tên-hàm( các-tham-số-nếu-có );

Sau đây là một số thông điệp và mẫu khai báo hàm thành viên trong lớp tương ứng:
Tên thông đi p WM_CHAR WM_PAINT WM_LBUTTONDOWN WM_DESTROY WM_TIMER WM_VSCROLL WM_SIZING WM_SIZE WM_MOVE WM_MOVING WM_MOUSEMOVE WM_MOUSEWHEEL WM_KEYDOWN WM_CLOSE WM_KEYUP WM_LBUTTONDOWN WM_RBUTTONDOWN WM_LBUTTONUP WM_RBUTTONUP Cú pháp hàm x lý thông đi p afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ); afx_msg void OnPaint( ); afx_msg void OnLButtonDown( UINT nFlags, CPoint point ); afx_msg void OnDestroy( ); afx_msg void OnTimer( UINT nIDEvent ); afx_msg void OnVScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar ); afx_msg void OnSizing( UINT nSide, LPRECT lpRect ); afx_msg void OnSize( UINT nType, int cx, int cy ); afx_msg void OnMove( int x, int y ); afx_msg void OnMoving( UINT nSide, LPRECT lpRect ); afx_msg void OnMouseMove( UINT nFlags, CPoint point ); afx_msg LRESULT OnRegisteredMouseWheel( WPARAM wParam, LPARAM lParam ); afx_msg void OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags ); afx_msg void OnClose( ); afx_msg void OnKeyUp(UINT c, UINT r, UINT f); afx_msg void OnLButtonDown( UINT f, CPoint p); afx_msg void OnRButtonDown( UINT f, CPoint p); afx_msg void OnLButtonUp( UINT f, CPoint p); afx_msg void OnRButtonUp( UINT f, CPoint p);

Chi tiết các tham số cho từng hàm thành viên sẽ được trình bày ở phần sau. Các thông điệp do chúng ta định nghĩa thì việc ánh xạ tới các hàm thành viên do chúng ta tự đặt tên, không theo quy định của Windows.

28

Cú pháp thực hiện ánh xạ các thông điệp tới các hàm thành viên của một lớp:
BEGIN_MESSAGE_MAP ( tên_lớp_dẫn_xuất , tên_lớp_cơ_sở ) /*ánh xạ thông ñiệp ñược ñịnh nghĩa bởi Windows*/ ON_WM_tên_thông_ñiệp_của_windows( ) /*ánh xạ thông ñiệp ñịnh nghĩa bởi chúng ta*/ ON_COMMAND( số_hiệu_thông_ñiệp , tên_hàm_thực_hiện ) END_MESSAGE_MAP( )

Chúng ta phải thực hiện ánh xạ trước khi viết lệnh cho các thành viên của lớp.

Ví dụ 2.1
Lập trình tạo một cửa sổ và thực hiện ánh xạ xử lý thông điệp bàn phím để hiện thông báo sau mỗi lần gõ phím.
#include<afxwin.h> /* ====== Khai báo l p ng d ng ========= */ class CMyApp : public CWinApp { public: BOOL InitInstance(); }; /* ====== Khai báo l p c a s ====== */ class CMyWin : public CFrameWnd { public: CMyWin(); afx_msg void OnChar(UINT kytu, UINT solan, UINT co); DECLARE_MESSAGE_MAP() }; /* ======= T o m t đ i tư ng ng d ng ======== */ CMyApp theApp; /* == Vi t l nh cho phương th c kh i t o ng d ng == */ BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return true;

29

} /* ==== Ánh x thông đi p đ x lý trong l p c a s ===== */ BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_CHAR() END_MESSAGE_MAP() /* ==== Vi t l nh cho các phương th c trong l p c a s ===== */ CMyWin::CMyWin() { Create(NULL, "Chuong trinh tao cua so don gian"); } afx_msg void CMyWin::OnChar(UINT kytu, UINT solan, UINT co) { char s[200]; sprintf(s,"Ban vua go chu [%c]",kytu); AfxMessageBox(s); }

Trong ví dụ trên có sử dụng hàm sprintf() để hiện các dữ liệu vào một chuỗi ký tự, hàm này có cú pháp giống với lệnh hiện dữ liệu ra màn hình trong ngôn ngữ C. Trong đó định dạng %c để hiện dữ liệu dưới dạng ký tự, giá trị mã ký tự lưu trong tham số kytu của hàm. Kết quả chương trình sau khi chạy và bấm phím chữ h sẽ xuất hiện như sau:

30

2.2. Ngữ cảnh thiết bị và hiện dữ liệu ra cửa sổ
2.2.1. Khái niệm về ngữ cảnh thiết bị
Ngữ cảnh thiết bị (Device Context - DC) là một cấu trúc mô tả cho giao diện hiển thị của cửa sổ trên màn hình, bao gồm trình điều khiển thiết bị của màn hình và các tham số như phông chữ, màu chữ... do vậy mỗi cửa sổ sẽ có một DC tương ứng. Minh họa bằng hình vẽ sau:

Trên cửa sổ sẽ có hệ tọa độ cho việc xác định vị trí dữ liệu khi hiện ra, tọa độ này được xác lập theo gốc ở góc trái trên cửa sổ và tăng từ trái sang phải theo chiều x, tăng từ trên xuống theo chiều y.

31

Góc trái trên sẽ có tọa độ là (0,0) và tăng cho đến góc phải dưới. Để xác định kích thước vùng hiện dữ liệu trên cửa sổ ta sử dụng hàm sau:
void CWnd :: GetClientRect( LPRECT lpRect ) const;

Trong đó lpRect là địa chỉ biến nhớ kiểu cấu trúc RECT để lưu tọa độ kích thước của vùng cửa sổ bên trong. Cấu trúc RECT như sau:
typedef { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT; struct tagRECT

Trước khi chương trình muốn hiện dữ liệu ra cửa sổ thì phải lấy DC tương ứng của cửa sổ đó. Tiếp theo sử dụng các lệnh để hiện dữ liệu, sau khi kết thúc DC và cửa sổ tương ứng sẽ không được liên kết với nhau nữa. Do vậy nếu muốn hiện tiếp ta phải thực hiện từ đầu. Đối với lập trình truyền thống ta phải sử dụng kiểu HDC để lưu ngữ cảnh thiết bị lấy được bằng cách sử dụng hàm GetDC(). Tiếp theo hiện dữ liệu ra bằng hàm TextOut() và sau khi hiện xong phải giải phóng DC đã lấy bằng hàm ReleaseDC(), bởi vì Windows sẽ cung cấp quản lý một số giới hạn DC nào đó trong chương trình. Trong lập trình MFC chúng ta thực hiện việc này bằng đối tượng lớp CDC, cho nên công việc trở nên dễ dàng và thuận tiện hơn rất nhiều.

2.2.2. Lớp CDC và xuất dữ liệu ra cửa sổ
Lớp CDC (Class Device Context) cho phép quản lý ngữ cảnh thiết bị cửa sổ và thực hiện các thao tác hiện dữ liệu trên cửa sổ đó. Để xác định đối tượng lớp CDC của một cửa sổ ta sử dụng hàm thành viên sau của lớp CWnd:
CDC* CWnd :: GetDC( );

32

hàm trả về kiểu biến trỏ của lớp CDC. Vì lớp CWnd là lớp cơ sở của lớp CFrameWnd nên chúng ta có thể sử dụng hàm này trong lớp CFrameWnd cũng như các lớp dẫn xuất từ lớp CWnd. Trong lớp CDC có rất nhiều hàm thành viên để hiện dữ liệu ra cửa sổ và các lệnh vẽ đồ họa lên cửa sổ, ở đây chỉ giới thiệu hàm hiện chuỗi ra cửa sổ còn những hàm khác sẽ trình bày chi tiết phần sau. - Hàm hiện dòng chữ lên màn hình
virtual BOOL int nCount ); CDC :: TextOut( int x, int y, LPCTSTR lpszString,

hoặc
BOOL CDC :: TextOut( int x, int y, const CString& str );

Trong đó: + x,y là tọa độ bắt đầu của dòng chữ cần hiện lên cửa sổ, + lpszString là chuỗi ký tự cần hiện, + nCount là độ dài chuỗi, + str là hằng chuỗi ký tự có kiểu CString.

2.2.3. Thông điệp WM_PAINT
Thông điệp WM_PAINT được sinh ra và gửi đến cửa sổ khi hệ thống cần chương trình phải hiện lại các dữ liệu trên cửa sổ, tức là khi có sự thay đổi kích thước, khi chuyển đổi trạng thái kích hoạt. Mỗi khi có sự thay đổi trạng thái cửa sổ (kích thước, kích hoạt) thì mọi dữ liệu trên cửa sổ sẽ bị mất và do vậy chúng ta phải có cơ chế hiện lại những dữ liệu đó. Chúng ta xử lý thông điệp WM_PAINT để làm điều này. Trong hàm xử lý thông điệp này có thể dùng hàm GetDC() để lấy ngữ cảnh thiết bị cửa sổ, tuy nhiên thông thường sử dụng lệnh BeginPaint() để bắt đầu hiện dữ liệu và sau khi hiện xong dùng lệnh EndPaint() để kết thúc, cú pháp 2 lệnh đó là:
CDC* void BeginPaint( LPPAINTSTRUCT lpPaint ); EndPaint( LPPAINTSTRUCT lpPaint ); 33

Trong đó tham số lpPaint có kiểu là một cấu trúc PAINTSTRUCT lưu giữ các thông tin về cửa sổ sẽ được hiện lại dữ liệu và nó được định nghĩa như sau:
typedef struct _PAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[32]; } PAINTSTRUCT;

Trong đó: + hdc: lưu số hiệu ngữ cảnh thiết bị ứng với cửa sổ, + fErase: cho biết chúng ta có phải vẽ lại nền cửa sổ hay không, + rcPaint: cho biết toạ độ kích thước của khung cửa sổ được hiện thị, + fRestore, fIncUpdate, rgbReserved: là các tham số dành riêng cho hệ thống. Mẫu của hàm xử lý sự kiện cho thông điệp WM_PAINT sẽ được viết như sau:
afx_msg { PAINTSTRUCT ps; void tên_lớp_cửa_sổ :: OnPaint()

CDC *dc = BeginPaint( &ps ); ... viết các lệnh thực hiện in dữ liệu lên cửa sổ qua dc ... EndPaint( &ps ); }

34

Cơ chế hiện dữ liệu lên cửa sổ sẽ thực hiện qua thông điệp WM_PAINT này, mọi dữ liệu hiện lên cửa sổ sẽ được lưu trong các thành viên dữ liệu của cửa sổ, mọi sự thay đổi dữ liệu trên cửa sổ phải thực hiện các thành viên dữ liệu này, sau đó mới hiện lên cửa sổ qua thông điệp WM_PAINT. Tuy nhiên nếu khi nào cần hiện lại dữ liệu, tức là phát sinh thông điệp WM_PAINT chúng ta sử dụng hàm sau:
void InvalidateRect ( LPCRECT lpRect, BOOL bErase = TRUE );

Trong đó lpRect là cấu trúc kiểu RECT như trình bày ở trên chứa tọa độ kích thước cửa sổ cần thực hiện cho thông điệp WM_PAINT, tuy nhiên có thể đặt tham số này bằng NULL nếu thực hiện trên cả cửa sổ hiện thời. Tham số thứ hai bErase cho biết có xóa và vẽ lại nền cửa sổ hay không, mặc định có giá trị TRUE.

Ví dụ 2.2
Lập trình tạo một cửa sổ để hiện lên dòng chữ "Khoa Cong nghe Tin hoc chao cac ban".
#include<afxwin.h> class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); afx_msg void OnPaint(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow);

35

m_pMainWnd->UpdateWindow(); return true; } BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_PAINT() END_MESSAGE_MAP() CMyWin::CMyWin() { Create(NULL, "Chuong trinh vi du 2 - Chapter 02"); } afx_msg void CMyWin::OnPaint() { PAINTSTRUCT ps; CDC *dc = BeginPaint(&ps); dc->TextOut(10,10,"Khoa Cong nghe Tin hoc chao cac ban"); EndPaint(&ps); }

Kết quả trên màn hình khi chạy chương trình sẽ là:

2.3. Xử lý các thông điệp cơ bản
2.3.1. Thông điệp bàn phím
Thông điệp bàn phím được sinh ra khi có sự tác động của người sử dụng lên bàn phím và hệ thống sẽ gửi một thông điệp tới chương trình ứng với việc gõ phím đó. Thông điệp bàn phím bao gồm các tên được định nghĩa như sau: WM_CHAR

36

Thông điệp này được sinh ra khi gõ một phím không phải là phím hệ thống điều khiển như F1,F2,Ctrl,Alt,... nó bao gồm các phím chữ, phím số, các phím dấu. Hàm xử lý thông điệp này được khai báo theo mẫu sau,
afx_msg nFlags ); void OnChar( UINT nChar, UINT nRepCnt, UINT

Trong đó: + nChar: là mã phím được gõ, + nRepCnt: là số lần được gõ, + nFlags: là trạng thái của các phím khác, trong đó nếu bít 29 bằng 1 thì phím Alt đang được giữ, ngược lại sẽ bằng 0. WM_KEYDOWN, WM_KEYUP Thông điệp WM_KEYDOWN được sinh ra khi bắt đầu nhấn phím xuống, thông điệp WM_KEYUP được sinh ra sau khi nhả phím và thực hiện cho mọi phím trên bàn phím, hàm xử lý thông điệp khai báo theo mẫu sau:
afx_msg void UINT nFlags ); OnKeyDown( UINT nChar, UINT nRepCnt,


afx_msg nFlags ); void OnKeyUp( UINT nChar, UINT nRepCnt, UINT

Trong đó các tham số như hàm OnChar() ở trên nhưng chỉ có tham số trạng thái nFlags sẽ được trình bày chi tiết như sau:
Các bít 0-7 8 9-10 11-12 13 14,15 Ý nghĩa Mã m r ng các phím B ng 1 n u đó là phím ch c năng ho c phím s m r ng Không s d ng S d ng b i Windows B ng 1 n u phím Alt đư c gi và ngư c l i Tr ng thái trư c đó c a phím

37

Ví dụ 2.3
Lập chương trình tạo cửa sổ và hiện lên cửa sổ các chữ được gõ từ bàn phím, mỗi chữ cách nhau 20 pixels, mỗi hàng có 10 chữ và cũng cách nhau 20 pixels.
#include<afxwin.h> class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return true; } BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_CHAR() END_MESSAGE_MAP() CMyWin::CMyWin() { Create(NULL, "Chuong trinh vi du 3 - Chapter 02"); } afx_msg void CMyWin::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { static int x=10,y=10;

38

CDC *dc = GetDC(); char s[5]; sprintf(s,"%c",nChar); dc->TextOut(x,y,s); x += 20; if ( x>200 ) { x=10; y +=20; } }

Trong hàm OnChar() của ví dụ trên sử dụng hai biến x và y kiểu biến tĩnh, tức là giá trị của nó được lưu giữ cho những lần thực hiện tiếp theo của hàm. Kết quả trên màn hình sau khi gõ một số phím sẽ là:

Tuy nhiên khi gõ đầy cửa sổ thì nó sẽ không tự động trượt lên như các cửa sổ văn bản, mà công việc này chúng ta phải tự lập trình. Hơn nữa nếu ta thay đổi kích thước hay trạng thái cửa sổ thì các chữ sẽ bị mất, nếu muốn hiện lại được ta phải thực hiện trong hàm OnPaint() như đã trình bày.

Ví dụ 2.4
Lập trình tạo cửa sổ và hiện lên trạng thái các phím chức năng mở rộng khi gõ một phím chữ nào đó. Chúng ta sẽ xử lý thông điệp WM_KEYDOWN trong lớp cửa sổ và hiện lên các thông báo mã phím được bấm. Nội dung chương trình sẽ là:
39

#include<afxwin.h> class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return true; } BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_KEYDOWN() END_MESSAGE_MAP() CMyWin::CMyWin() { Create(NULL, "Chuong trinh vi du 4 - Chapter 02"); } afx_msg void CMyWin::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { char s[200]; CDC *dc=GetDC(); sprintf(s,"Phim ALT %s ",((nFlags>>13)&1==1)?"dang duoc an":"khong duoc an"); dc->TextOut(10,10,s); sprintf(s,"Ma phim mo rong duoc bam la %d ",nFlags&0xFF); dc->TextOut(10,30,s); sprintf(s,"Ma ASCII cua phim duoc bam la %d ",nChar); dc->TextOut(10,50,s); }

40

Kết quả chạy chương trình sau khi bấm phím chữ A sẽ xuất hiện như sau, đồng thời giữ phím Alt:

2.3.2. Thông điệp chuột
Thông điệp chuột được sinh ra khi có sự tác động của người sử dụng lên thiết bị chuột, các tên thông điệp liên quan đến chuột là:
Tên thông đi p WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK WM_RBUTTONDOWN WM_RBUTTONUP WM_RBUTTONDBLCLK WM_MBUTTONDOWN WM_MBUTTONUP WM_MBUTTONDBLCLK WM_MOUSEMOVE WM_MOUSEWHEEL Ý nghĩa B m nút trái chu t xu ng Nh nút trái chu t B m đúp nút trái chu t B m nút ph i chu t xu ng Nh nút ph i chu t B m đúp nút ph i chu t B m nút gi a chu t xu ng Nh nút gi a chu t B m đúp nút gi a chu t Di chuy n con chu t Tác đ ng lên nút cu n c a chu t

Các mẫu hàm khai báo cho ánh xạ xử lý các thông điệp liên quan đến chuột như sau:
afx_msg afx_msg void void OnLButtonDown( UINT nFlgs, OnLButtonUp( UINT nFlgs, CPoint pt ); CPoint pt );

41

afx_msg afx_msg afx_msg afx_msg afx_msg afx_msg afx_msg afx_msg

void void void void void void void void

OnLButtonDblClk( UINT nFlgs, OnRButtonDown( UINT nFlgs, OnRButtonUp( UINT nFlgs,

CPoint pt ); CPoint pt );

CPoint pt ); CPoint pt ); CPoint pt );

OnRButtonDblClk( UINT nFlgs, OnMButtonDown( UINT nFlgs, OnMButtonUp( UINT nFlgs,

CPoint pt ); CPoint pt );

OnMButtonDblClk( UINT nFlgs, OnMouseMove( UINT nFlgs, OnMouseWheel( UINT nFlgs,

CPoint pt ); short zDelta,

afx_msg BOOL CPoint pt );

Trong đó: + UINT nFlgs: là giá trị các bít cờ trạng thái của các phím điều khiển, bao gồm các giá trị được định nghĩa như sau:
MK_CONTROL MK_SHIFT MK_LBUTTON MK_RBUTTON MK_MBUTTON Phím Ctrl đư c gi Phím Shift đư c gi Phím trái chu t đư c gi Phím ph i chu t đư c gi Phím gi a chu t đư c gi

Nếu có phím hoặc nút chuột được giữ khi có thông điệp chuột thì tham số nFlgs sẽ chứa các giá trị cờ tương ứng như bảng trên. + CPoint pt : là vị trí của chuột khi phát sinh thông điệp, lớp CPoint chứa một điểm 2 tọa độ (x là hoành độ, y là tung độ) bao gói từ cấu trúc POINT như sau:
typedef struct tagPOINT { LONG x; LONG y; } POINT;

Tọa độ này của chuột sẽ tương ứng với gốc ở góc trái trên của cửa sổ, chiều x tăng từ trái sang phải, chiều y tăng từ trên xuống dưới.
42

+ short zDelta: xác định khoảng cuộn nút chuột, nếu là số âm có nghĩa là cuộn lên; nếu số dương có nghĩa là cuộn xuống. Thông thường giá trị của zDelta là 120 pixels.

Ví dụ 2.5
Lập trình tạo cửa sổ và hiện thông báo tương ứng với trạng thái chuột tại các vị trí của chuột trên cửa sổ. Trong chương trình này chúng ta xử lý các thông điệp về chuột gồm WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_RBUTTONDBLCLK, WM_MOUSEWHEEL. Nội dung chương trình như sau:
#include<afxwin.h> class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); afx_msg void OnLButtonDown( UINT nFlgs, CPoint pt ); afx_msg void OnLButtonUp( UINT nFlgs, CPoint pt ); afx_msg void OnLButtonDblClk( UINT nFlgs, CPoint pt ); afx_msg void OnRButtonDown( UINT nFlgs, CPoint pt ); afx_msg void OnRButtonUp( UINT nFlgs, CPoint pt ); afx_msg void OnRButtonDblClk( UINT nFlgs, CPoint pt ); afx_msg void OnMouseMove( UINT nFlgs, CPoint pt ); afx_msg BOOL OnMouseWheel( UINT nFlgs, short zDelta, CPoint pt ); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return true;

43

} BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_LBUTTONDBLCLK() ON_WM_RBUTTONDOWN() ON_WM_RBUTTONUP() ON_WM_RBUTTONDBLCLK() ON_WM_MOUSEMOVE() ON_WM_MOUSEWHEEL() END_MESSAGE_MAP() CMyWin::CMyWin() { Create(NULL, "Chuong trinh vi du 5 - Chapter 02"); } afx_msg void CMyWin::OnLButtonDown( UINT nFlgs, CPoint pt ) { CDC *dc=GetDC(); dc->TextOut(pt.x,pt.y,"Nut trai duoc bam "); } afx_msg void CMyWin::OnLButtonUp( UINT nFlgs, CPoint pt ) { CDC *dc=GetDC(); dc->TextOut(pt.x,pt.y,"Nut trai duoc nha "); } afx_msg void CMyWin::OnLButtonDblClk( UINT nFlgs, CPoint pt ) { CDC *dc=GetDC(); dc->TextOut(pt.x,pt.y,"Nut trai duoc bam dup "); } afx_msg void CMyWin::OnRButtonDown( UINT nFlgs, CPoint pt ) { CDC *dc=GetDC(); dc->TextOut(pt.x,pt.y,"Nut phai duoc bam "); } afx_msg void CMyWin::OnRButtonUp( UINT nFlgs, CPoint pt ) { CDC *dc=GetDC(); dc->TextOut(pt.x,pt.y,"Nut phai duoc nha "); } afx_msg void CMyWin::OnRButtonDblClk( UINT nFlgs, CPoint pt ) {

44

"); } afx_msg void CMyWin::OnMouseMove( UINT nFlgs, CPoint pt ) { CDC *dc=GetDC(); char s[200]; sprintf(s,"[x=%d,y=%d] ",pt.x,pt.y); dc->TextOut(1,5,s); } afx_msg BOOL CMyWin::OnMouseWheel( UINT nFlgs, short zDelta, CPoint pt ) { CDC *dc=GetDC(); char s[200]; sprintf(s,"Mouse Wheel with delta=%d",zDelta); dc->TextOut(pt.x,pt.y,s); return 1; }

CDC *dc=GetDC(); dc->TextOut(pt.x,pt.y,"Nut phai duoc bam dup

Kết quả chạy chương trình sau khi tác động lên chuột ta được:

2.3.3. Thông điệp thời gian
Thông điệp thời gian được sinh ra theo chu kỳ thời gian và được gắn vào mỗi cửa sổ. Thông điệp thời gian có tên là WM_TIMER, khoảng chu kỳ thời gian do chúng ta đặt và mỗi cửa sổ có thể có nhiều chu kỳ thời gian ứng với các thông điệp thời gian khác nhau.

45

Mỗi chu kỳ thời gian ứng với một thông điệp WM_TIMER sẽ được hệ thống gửi tới hàng đợi thông điệp và gọi hàm thời gian để thực hiện. Để đặt chu kỳ thời gian trên một cửa sổ chúng ta sử dụng hàm thành viên của cửa sổ được khai báo theo mẫu sau:
UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)( HWND, UINT, UINT, DWORD) );

Trong đó: + nIDEvent: là số hiệu chu kỳ thời gian sẽ đặt cho cửa sổ, + nElapse: là khoảng thời gian xác định một chu kỳ tính bằng mili giây, + lpfnTimer: là địa chỉ của hàm thời gian, hàm này sẽ được gọi thực hiện mỗi một chu kỳ thời gian trôi qua. Có thể đặt tham số này là NULL. Hàm đặt chu kỳ này sẽ trả về số hiệu của chu kỳ thời gian đặt thành công, nếu không thành công hàm trả về số 0. Để xử lý thông điệp WM_TIMER ứng với chu kỳ thời gian đã đặt, chúng ta sử dụng mẫu khai báo hàm ánh xạ thông điệp như sau:
afx_msg void OnTimer( UINT nIDEvent );

Trong đó nIDEvent là số hiệu chu kỳ thời gian ứng với thông điệp WM_TIMER, số hiệu này có được khi đặt chu kỳ bằng lệnh SetTimer() ở trên. Nếu muốn hủy bỏ chu kỳ thời gian đã đặt trên cửa sổ chúng ta sử dụng lệnh sau:
BOOL KillTimer( int nIDEvent );

Trong đó nIDEvent là số hiệu chu kỳ thời gian cần hủy bỏ. Nếu máy tìm thấy chu kỳ ứng với số hiệu thì sẽ hủy bỏ và trả về giá trị TRUE, ngược lại trả về giá trị FALSE khi không tìm thấy chu kỳ tương ứng với số hiệu đưa vào.

Ví dụ 2.6
Lập trình tạo cửa sổ và đặt chu kỳ thời gian xử lý thông điệp WM_TIMER để hiện lên cửa sổ thời gian của hệ thống.

46

Trong chương trình cần lấy thời gian hiện tại của hệ thống nên chúng ta sử dụng lớp CTime, trong đó có hàm thành viên tĩnh GetCurrentTime() để xác định thời gian của máy và trả về đối tượng kiểu CTime.
static CTime PASCAL CTime :: GetCurrentTime( );

Lớp CTime có một số hàm thành viên cơ bản như sau:
Hàm thành viên GetHour() GetMinute() GetSecond() GetYear() GetMonth() GetDay() GetDayOfWeek() Ý nghĩa Cho gi Cho phút Cho giây Cho năm Cho tháng Cho ngày Cho ngày trong tu n (1=ch nh t, 2=th hai,...)

Nội dung chương trình như sau:
#include<afxwin.h> class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); ~CMyWin(); afx_msg void OnTimer( UINT nIDEvent ); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return true; }

47

BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_TIMER() END_MESSAGE_MAP() CMyWin::CMyWin() { Create(NULL, "Chuong trinh vi du 6 - Chapter 02"); //SetTimer SetTimer(117,1000,NULL); } CMyWin::~CMyWin() { //KillTimer KillTimer(117); } afx_msg void CMyWin::OnTimer( UINT nIDEvent ) { CDC *dc=GetDC(); char s[200]; CTime t = CTime::GetCurrentTime(); sprintf(s,"Thoi gian la = [%2d:%2d:%2d] ", t.GetHour(), t.GetMinute(), t.GetSecond()); dc->TextOut(5,5,s); }

Kết quả chạy chương trình sẽ là:

Chúng ta thực hiện đặt chu kỳ thời gian trong hàm tạo của lớp cửa sổ, sau lệnh tạo cửa sổ và hủy bỏ chu kỳ đó trong hàm hủy của cửa sổ tương ứng.

2.3.4. Một số thông điệp khác
- Thông điệp đóng cửa sổ WM_CLOSE Mỗi lần đóng cửa sổ thoát khỏi chương trình máy sẽ phát sinh thông điệp WM_CLOSE, người lập trình có thể xử lý thông điệp này nếu muốn thực hiện một số thao tác trước khi thoát khỏi chương trình. Ví dụ như hỏi người dùng có muốn thoát hay không, hay lưu giữ một số dữ liệu của chương trình lên ổ đĩa.

48

Mẫu hàm khai báo cho xử lý thông điệp này như sau:
afx_msg void OnClose( );

Trong cài đặt cho hàm OnClose() này sẽ gọi đến hàm hủy bỏ cửa sổ DestroyWindow(), nếu chúng ta không muốn đóng cửa sổ thì không gọi hàm này. Khi cửa sổ bị xóa bỏ máy sẽ phát sinh thông điệp WM_DESTROY và mẫu hàm xử lý thông điệp này như sau:
afx_msg void OnDestroy( );

Chú ý: Nếu có xử lý thông điệp WM_CLOSE thì trong hàm OnClose() của lớp cửa sổ phải gọi đến hàm OnClose() của lớp cơ sở để thực hiện đóng cửa sổ nếu cần. - Thông điệp kích hoạt cửa sổ WM_ACTIVATE Thông điệp này được sinh ra khi một cửa sổ được kích hoạt lên (do đang bị che khuất bởi một cửa sổ khác, do đang ở chế độ thu nhỏ). Mẫu hàm ánh xạ xử lý thông điệp này khai báo như sau:
afx_msg void OnActivate( UINT nState, pWndOther, BOOL bMinimized ); CWnd*

Trong đó tham số đầu (nState) kiểu UINT quy định trạng thái kích hoạt có thể là WA_ACTIVE, WA_CLICKACTIVE hoặc WA_INACTIVE, tham số thứ 2 (pWndOther) kiểu CWnd xác định cửa sổ đang được kích hoạt và tham số cuối (nMinimized) cho biết cửa sổ có ở chế độ thu nhỏ hay không tương ứng giá trị TRUE hoặc FALSE. - Thông điệp thay đổi vị trí và kích thước cửa sổ WM_MOVING, WM_MOVE, WM_SIZING, WM_SIZE Các thông điệp này được sinh ra khi một cửa sổ bị dịch chuyển, bị thay đổi kích thước. Sự kiện đang diễn ra hoặc đã diễn ra tương ứng thông điệp có hậu tố ING (động từ dạng Verb+ing) hoặc không. Mẫu hàm ánh xạ xử lý thông điệp có khai báo tương ứng như sau:
afx_msg void OnMoving( UINT nSide, LPRECT lpRect ); afx_msg void OnMove( int x, int y ); 49

afx_msg void OnSizing( UINT nSide, LPRECT lpRect ); afx_msg void OnSize( UINT nType, int cx, int cy );

Trong đó tham số nSide quy định cạnh của cửa sổ đang bị thay đổi, lpRect xác định kích thước và vị trí mới. Hoặc tham số x, y cho biết vị trí mới, cx và cy cho biết kích thước mới sau khi thay đổi. Chúng ta thấy rằng việc xử lý thông điệp thông thường thực hiện trong lớp cửa sổ, dẫn xuất từ CFrameWnd, từ CWnd. Hầu hết các hàm ánh xạ này được định nghĩa trong lớp CWnd, bây giờ chúng ta "viết đè" lại các hàm đó để ánh xạ thông điệp.

Ví dụ 2.7
Lập trình tạo cửa sổ và xử lý thông điệp WM_CLOSE để hỏi người dùng có chắc chắn hay không, mở rộng từ ví dụ 2.6 (xử lý thời gian). Trong chương trình này chúng ta sẽ thực hiện đặt chu kỳ thời gian trong hàm InitInstance() qua thành viên m_pMainWnd của lớp ứng dụng, thực hiện hủy chu kỳ thời gian trong hàm OnDestroy() của lớp cửa sổ. Nội dung chương trình như sau:
#include<afxwin.h> class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); afx_msg void OnDestroy(); afx_msg void OnClose(); afx_msg void OnTimer(UINT nIDEvent); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BOOL CMyApp::InitInstance() {

50

m_pMainWnd = new CMyWin; //SetTimer if (m_pMainWnd->SetTimer(117,1000,NULL)!=117) { MessageBox(NULL,"Khong dat duoc chu ky thoi gian","Loi chuong trinh",MB_OK); return FALSE; } m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return true; } BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_DESTROY() ON_WM_CLOSE() ON_WM_TIMER() END_MESSAGE_MAP() CMyWin::CMyWin() { Create(NULL, "Chuong trinh vi du 7 - Chapter 02"); } afx_msg void CMyWin::OnDestroy() { KillTimer(117); } afx_msg void CMyWin::OnClose() { if (MessageBox("Co chac chan khong?","Dong cua so",MB_YESNO | MB_ICONQUESTION)==IDYES) { DestroyWindow(); } } afx_msg void CMyWin::OnTimer(UINT nIDEvent) { CDC *dc=GetDC(); char s[200]; CTime t = CTime::GetCurrentTime(); sprintf(s,"Thoi gian la = [%2d:%2d:%2d] ", t.GetHour(), t.GetMinute(), t.GetSecond() ); dc->TextOut(5,5,s); }

51

Kết quả sau khi chạy chương trình sẽ như sau: nếu chọn đóng cửa sổ sẽ xuất hiện thông báo hỏi có chắc chắn không, nếu chọn Yes thì sẽ đóng cửa sổ bằng lệnh gọi DetroyWindow().

2.4. Bài tập
Bài tập 2.1
Lập trình tạo cửa sổ và xử lý thông điệp WM_PAINT để hiện lên bảng cửu chương của 5.

Bài tập 2.2
Lập trình tạo cửa sổ và xử lý thông điệp WM_PAINT để hiện lên bảng chữ cái tiếng Anh in hoa và in thường.

Bài tập 2.3
Lập trình tạo cửa sổ và xử lý thông điệp WM_CHAR để nhận các ký tự lưu vào một biến xâu ký tự, sau đó hiện các ký tự đã nhận được lên cửa sổ trong thông điệp WM_PAINT.

Bài tập 2.4
Lập trình tạo cửa sổ và xử lý thông điệp WM_KEYUP để dịch chuyển một ký tự có mã 0xB1 trên cửa sổ theo các phím mũi tên (lên, xuống, trái, phải). Mỗi lần dịch chuyển một khoảng 2 pixels, nếu tọa độ của ký tự vượt ra ngoài cửa sổ thì không dịch, tọa độ bắt đầu của ký tự là ở giữa cửa sổ.

52

Bài tập 2.5
Lập trình tạo cửa sổ và xử lý thông điệp WM_MOUSEMOVE để hiện tọa độ của chuột lên giữa tâm cửa sổ, xử lý thông điệp WM_RBUTTONDOWN để đóng cửa sổ nếu cần.

Bài tập 2.6
Lập trình tạo cửa sổ và xử lý thông điệp WM_TIMER để hiện lên giữa tâm cửa sổ thông tin về thứ, ngày, tháng, năm hiện tại của máy, thông tin về giờ, phút và giây.

Bài tập 2.7
Lập trình tạo cửa sổ và xử lý thông điệp WM_MOUSEMOVE để tính độ dài con đường của chuột đã dịch chuyển qua trên cửa sổ, độ dài được tính bằng đơn vị pixel. Mỗi lần dịch sẽ được tính thêm và hiện lên cửa sổ độ dài đó.

Bài tập 2.8
Lập trình tạo cửa sổ và xử lý các thông điệp đã học để kiểm tra tốc độ gõ phím. Tại mỗi thời điểm đã định (chẳng hạn cứ mỗi 0.5 giây) thì máy sẽ hiện một chữ cái bất kỳ tại một vị trí bất kỳ trên cửa sổ, nếu người sử dụng gõ đúng ký tự đó thì tính 3 điểm, gõ sai trừ 1 điểm và hiện kết quả chơi lên góc trái trên cửa sổ. Chương trình cho phép thay đổi tốc độ chơi bằng cách bấm các phím F1 và F2 để tăng hoặc giảm tốc độ.

Bài tập 2.9
Lập trình tạo cửa sổ và xử lý các thông điệp đã học để hiện cửa sổ bảng các chữ số từ 0 đến 9. Nếu người sử dụng gõ một phím số thì chữ số tương ứng trên cửa sổ sẽ bị mất và thay thế bằng một chữ số ngẫu nhiên nào đó, quá trình sẽ tiếp tục cho đến khi các chữ số giống nhau thì thông báo người chơi thắng cuộc.

Bài tập 2.10
Lập trình tạo cửa sổ và xử lý các thông điệp để tạo một đồng hồ đếm ngược, độ lớn thời gian sẽ được xác định bằng cách bấm chuột trái sẽ tăng, bấm chuột phải sẽ giảm và hiện độ lớn đó lên cửa sổ. Nếu bấm phím Enter sẽ bắt đầu đếm ngược cho đến hết và đóng cửa sổ.
53

Chương 3 TẠO VÀ SỬ DỤNG TÀI NGUYÊN
3.1. Khái niệm về tài nguyên
Tài nguyên (resources) là các đối tượng gồm biểu tượng, hình ảnh, thực đơn, thanh công cụ,… được tạo ra để cung cấp cho chương trình. Trong Visual C++ cung cấp một công cụ để tạo các tài nguyên và gắn chúng với chương trình. Các tài nguyên được lưu trong một tệp tin có phần mở rộng là *.rc, sau đây chúng tôi sẽ giới thiệu một số tài nguyên hay sử dụng.

3.2. Thực đơn lệnh (menu) và lớp CMenu
Menu là một thể hiện giao diện chứa các chức năng của chương trình, ở mỗi thời điểm một cửa sổ có thể được gắn với một menu. Để thực hiện việc gắn kết một menu với cửa sổ ta làm các bước sau: Bước 1: Định nghĩa các mục trên menu trong tệp tài nguyên. Bước 2: Nạp menu khi chương trình tạo cửa sổ chứa nó. Bước 3: Xử lý các lựa chọn trên menu. Tệp tài nguyên (resource file) ở đây là tệp chứa tất cả các định nghĩa tài nguyên trong chương trình, tệp có phần mở rộng là *.RC. Tên tệp tài nguyên phải trùng với tên tệp chương trình, ví dụ chúng ta có tên tệp chương trình là main.cpp thì tệp tài nguyên đi kèm phải là main.rc.

3.2.1. Tạo thực đơn và gắn vào cửa sổ
Menu có thể có nhiều cấp, menu ngang ở phía trên cùng cửa sổ, menu dọc của mỗi mục menu ngang gọi là popup. Do đó việc định nghĩa theo cấu trúc sau:
54

Định nghĩa menu ngang:
Tên_menu { _ñịnh nghĩa các mục chọn ngang ở ñây_ } MENU [tùy chọn]

- Phần tùy chọn ở đây để cho biết mục chọn ở trạng thái nào và các tham số của nó, gồm có:
Tùy ch n DISCARDABLE FIXED LOADONCALL MOVEABLE PRELOAD Ý nghĩa Menu có th xóa kh i b nh n u không dùng n a Menu s đư c c đ nh trong vùng nh Menu s đư c n p khi s d ng Menu có th di chuy n trong b nh Menu đư c n p khi chương trình b t đ u th c hi n

Mỗi mục có thể là mục chọn hoặc là một thực đơn dọc (popup), do vậy ta có định nghĩa mục chọn theo mẫu sau:
MENUITEM chọn] Tên_mục_chọn_ngang, số_hiệu_mục_chọn [,tùy

Trong đó: + tên mục chọn ngang: là nội dung văn bản sẽ hiện lên mục chọn, + số hiệu mục chọn: để định danh mục chọn cho việc xử lý thông điệp lựa chọn sau này. Định nghĩa một popup theo mẫu sau:
POPUP { _ñịnh nghĩa các mục chọn dọc ở ñây_ } Tên_popup [,tuỳ chọn]

Trong đó tùy chọn của mục chọn ngang hoặc popup cho biết các tham số liên quan đến mục chọn, bao gồm:
55

Tuỳ ch n CHECKED GRAYED HELP INACTIVE MENUBARBREAK MENUBREAK SEPARATOR

Ý nghĩa M t d u hi u s đư c đánh tên m c ch n

M c ch n có màu xám và không th ch n Có th liên k t v i m c ch n tr giúp M c ch n b t t kích ho t T o m t m c ch n trên m t phân cách m i Gi ng MENUBARBREAK nhưng không có đư ng phân cách T o m t đư ng phân cách gi a các m c ch n

Định nghĩa các mục chọn dọc cũng như các mục chọn ngang ở trên. Chú ý: Thông thường các số hiệu của mục chọn thực đơn được định nghĩa qua các tên hằng để cho dễ quản lý và xử lý trong chương trình, các định nghĩa này được thực hiện trong tệp khai báo RESOURCE.H của chương trình.

Ví dụ 3.1
Định nghĩa một thực đơn theo mẫu sau:

- Tệp chứa khai báo các tên số hiệu mục chọn thực đơn resource.h như sau:
/* ======= t p resource.h ========*/ #define IDR_MYMENU 101 #define IDM_FILE_NEW 40001 #define IDM_FILE_OPEN 40002 #define IDM_FILE_EXIT 40003

Trong tệp này chúng ta định nghĩa số hiệu thực đơn, số hiệu các mục chọn thực đơn qua các tên. - Tệp chứa định nghĩa thực đơn *.rc như sau:

56

#include "resource.h" IDR_MYMENU MENU DISCARDABLE BEGIN POPUP "File" BEGIN MENUITEM "New", IDM_FILE_NEW MENUITEM "Open", IDM_FILE_OPEN MENUITEM SEPARATOR MENUITEM "Exit", IDM_FILE_EXIT, CHECKED END MENUITEM "Edit", IDM_EDIT MENUITEM "Tools", IDM_TOOLS MENUITEM "Help", IDM_HELP END

Trong định nghĩa này chúng ta sử dụng cặp từ khóa BEGIN và END thay thế cho cặp ký tự { và } để bắt đầu và kết thúc quá trình định nghĩa các mục chọn thực đơn. Sau khi định nghĩa xong thực đơn chúng ta thực hiện gắn thực đơn vào cửa sổ bằng cách đặt tham số thực đơn vào lệnh Create() trong hàm tạo của lớp cửa sổ.
Create( NULL, tiêu_ñề_cửa_sổ, WS_OVERLAPPEDWINDOW, rectDefault, NULL, số_hiệu_thực_ñơn );

Trong đó tham số cuối cùng xác định số hiệu thực đơn chính là số hiệu của thực đơn khi định nghĩa thực đơn đó và phải chuyển về dạng chuỗi bằng cách sử dụng cơ chế ép về kiểu LPSTR, ví dụ:
Create( NULL, “Chương trình ví dụ”, WS_OVERLAPPEDWINDOW, rectDefault, NULL, (LPSTR)IDR_MYMENU );

Tuy nhiên có thể thực hiện gắn thực đơn vào cửa sổ bất kỳ thời điểm nào bằng cách sử dụng đối tượng lớp CMenu để quản lý và thực hiện theo các bước sau: Bước 1: Tạo đối tượng từ lớp CMenu Bước 2: Nạp thực đơn từ chương trình vào đối tượng bằng lệnh sau:
BOOL CMenu :: LoadMenu( LPCTSTR lpszResourceName );

57

hoặc:
BOOL CMenu :: LoadMenu( UINT nIDResource );

Trong đó lpszResourceName là tên tài nguyên thực đơn cần nạp vào máy, hoặc nIDResource là số hiệu thực đơn. Bước 3: Gắn thực đơn với cửa sổ bằng lệnh sau:
BOOL CWnd :: SetMenu( CMenu* pMenu );

Ví dụ 3.2
Lập chương trình tạo cửa sổ và gắn thực đơn ở trên với cửa sổ được tạo ra. Chúng ta thực hiện gắn thực đơn vào cửa sổ trong lệnh tạo cửa sổ ở hàm tạo của lớp, chương trình sẽ được viết như sau:
/* T p ch a đ nh nghĩa các h ng c a m c ch n resource.h */ #define IDR_MYMENU 101 #define IDM_FILE_NEW 40001 #define IDM_FILE_OPEN 40002 #define IDM_FILE_EXIT 40003 /* T p ch a đ nh nghĩa tài nguyên th c đơn main.rc */ #include "resource.h" IDR_MYMENU MENU DISCARDABLE BEGIN POPUP "File" BEGIN MENUITEM "New", IDM_FILE_NEW MENUITEM "Open", IDM_FILE_OPEN MENUITEM SEPARATOR MENUITEM "Exit", IDM_FILE_EXIT, CHECKED END MENUITEM "Edit", IDM_EDIT MENUITEM "Tools", IDM_TOOLS MENUITEM "Help", IDM_HELP END /* T p chương trình chính main.cpp c a ví d này */ #include<afxwin.h> #include "resource.h" class CMyApp : public CWinApp { public:

58

BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); }; CMyApp theApp; BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return true; } CMyWin::CMyWin() { Create( NULL, "Chuong trinh vi du 01 - Chapter 03", WS_OVERLAPPEDWINDOW, rectDefault, NULL, (LPSTR)IDR_MYMENU ); }

Kết quả khi chạy chương trình sẽ có cửa sổ như sau:

Trong đó có một số mục chọn đang bị mờ do chúng ta chưa xử lý thông điệp chọn chức năng trên thực đơn. Chương trình này sử dụng cơ chế gắn thực đơn vào cửa sổ bằng tham số trong lệnh tạo cửa sổ Create().

59

3.2.2. Xử lý thông điệp lựa chọn thực đơn
Thông điệp lựa chọn thực đơn sẽ được máy gửi tới chương trình qua thông điệp WM_COMMAND, trong đó sẽ gửi kèm theo số hiệu của mục thực đơn (hoặc chức năng) đã được chọn. Có thể thực hiện ánh xạ xử lý thông điệp chọn thực đơn theo từng chức năng hoặc theo nhóm chức năng, các bước thực hiện như sau: Bước 1: Khai báo và xây dựng các hàm thành viên để xử lý cho thông điệp chọn thực đơn, hàm này được khai báo theo kiểu afx_msg và trả về kiểu void. Bước 2: Sử dụng các macro sau để thực hiện ánh xạ xử lý thông điệp chọn thực đơn tới các hàm thành viên của lớp cửa sổ
ON_COMMAND( id, memberFxn );

Trong đó id là số hiệu mục chọn của thực đơn cần ánh xạ, memberFxn là tên hàm thành viên sẽ xử lý lựa chọn cho mục chọn tương ứng với số hiệu đó. Thực hiện khai báo này trong phần ánh xạ thông điệp của lớp bởi các macro BEGIN_MESSAGE_MAP và END_MESSAGE_MAP. Chú ý: Nếu cần ánh xạ theo nhóm chức năng thành một hàm thì hàm đó phải có một tham số kiểu UINT để nhận số hiệu chức năng khi lựa chọn và sử dụng macro sau để thực hiện ánh xạ:
ON_COMMAND_RANGE( id, idLast, memberFxn );

Trong đó id là số hiệu chức năng đầu nhóm, idLast là số hiệu chức năng cuối trong nhóm, memberFxn là tên hàm thành viên sẽ thực hiện ứng với các chức năng trong nhóm được chọn.

Ví dụ 3.3
Lập trình tạo cửa sổ có một thực đơn như ví dụ 3.2, xử lý các thông điệp lựa chọn trên thực đơn để hiện lên cửa sổ các thông báo của chức năng được chọn tương ứng. Chúng ta sử dụng đối tượng lớp CMenu để nạp thực đơn và đặt vào cửa sổ trong hàm tạo của cửa sổ tương ứng.

60

/* T p ch a đ nh nghĩa tên các ch c năng – resource.h */ #define IDR_MYMENU 101 #define ID_CAPNHAT_NHAPMOI 40001 #define ID_CAPNHAT_SUACHUA 40002 #define ID_TIMKIEM_TIMTHEOTEN 40003 #define ID_TIMKIEM_TIMTHEOQUEQUAN 40004 #define ID_BAOCAO_BAOCAODOANHTHU 40005 #define ID_BAOCAO_BAOCAOLOINHUAN 40006 #define ID_HETHONG_TROGIUP 40007 #define ID_HETHONG_THOAT 40008 /* T p ch a đ nh nghĩa tài nguyên th c đơn – main.rc */ #include “resource.h” IDR_MYMENU MENU DISCARDABLE BEGIN POPUP "Cap nhat" BEGIN MENUITEM "Nhap moi", ID_CAPNHAT_NHAPMOI MENUITEM "Sua chua", ID_CAPNHAT_SUACHUA END POPUP "Tim kiem" BEGIN MENUITEM "Tim theo ten", ID_TIMKIEM_TIMTHEOTEN MENUITEM "Tim theo que quan", ID_TIMKIEM_TIMTHEOQUEQUAN END POPUP "Bao cao" BEGIN MENUITEM "Bao cao doanh thu", ID_BAOCAO_BAOCAODOANHTHU MENUITEM "Bao cao loi nhuan", ID_BAOCAO_BAOCAOLOINHUAN END POPUP "He thong" BEGIN MENUITEM "Tro giup", ID_HETHONG_TROGIUP MENUITEM "Thoat", ID_HETHONG_THOAT END END /* T p ch a n i dung chương trình chính – main.cpp */ #include<afxwin.h> #include"resource.h" class CMyApp : public CWinApp { public:

61

BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); afx_msg void OnNhapmoi(); afx_msg void OnSuachua(); afx_msg void OnTimtheoten(); afx_msg void OnTimtheoquequan(); afx_msg void OnBaocaodoanhthu(); afx_msg void OnBaocaoloinhuan(); afx_msg void OnTrogiup(); afx_msg void OnThoat(); DECLARE_MESSAGE_MAP() }; BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_COMMAND( ID_CAPNHAT_NHAPMOI , OnNhapmoi ) ON_COMMAND( ID_CAPNHAT_SUACHUA , OnSuachua ) ON_COMMAND( ID_TIMKIEM_TIMTHEOTEN , OnTimtheoten ) ON_COMMAND( ID_TIMKIEM_TIMTHEOQUEQUAN , OnTimtheoquequan ) ON_COMMAND( ID_BAOCAO_BAOCAODOANHTHU , OnBaocaodoanhthu ) ON_COMMAND( ID_BAOCAO_BAOCAOLOINHUAN , OnBaocaoloinhuan ) ON_COMMAND( ID_HETHONG_TROGIUP , OnTrogiup ) ON_COMMAND( ID_HETHONG_THOAT , OnThoat ) END_MESSAGE_MAP() CMyWin::CMyWin() { Create(NULL,"Chuong trinh vi du 03 - Chapter 03"); //Load & set menu for program CMenu *m = new CMenu; m->LoadMenu(IDR_MYMENU); SetMenu(m); m->DestroyMenu(); delete m; } afx_msg void CMyWin::OnNhapmoi()

62

{ CDC *dc=GetDC(); dc->TextOut(10,50,"Ban da chon muc nhap moi } afx_msg void CMyWin::OnSuachua() { CDC *dc=GetDC(); dc->TextOut(10,50,"Ban da chon muc sua chua ");

");

} afx_msg void CMyWin::OnTimtheoten() { CDC *dc=GetDC(); dc->TextOut(10,50,"Ban da chon muc tim theo ten "); } afx_msg void CMyWin::OnTimtheoquequan() { CDC *dc=GetDC(); dc->TextOut(10,50,"Ban da chon muc tim theo que quan "); } afx_msg void CMyWin::OnBaocaodoanhthu() { CDC *dc=GetDC(); dc->TextOut(10,50,"Ban da chon muc bao cao doanh thu "); } afx_msg void CMyWin::OnBaocaoloinhuan() { CDC *dc=GetDC(); dc->TextOut(10,50,"Ban da chon muc bao cao loi nhuan "); } afx_msg void CMyWin::OnTrogiup() { CDC *dc=GetDC(); dc->TextOut(10,50,"Ban da chon muc tro giup "); } afx_msg void CMyWin::OnThoat() { //Dong cua so & thoat khoi chuong trinh OnClose(); } CMyApp theApp;

63

Kết quả khi chạy chương trình và chọn một chức năng trên thực đơn:

3.2.3. Sử dụng phím nóng (accelerator key)
Mỗi chức năng trên thực đơn chúng ta có thể gắn bằng một phím tắt, khi đó thay thế cho việc dùng chuột để chọn chức năng chúng ta có thể bấm phím tắt đó. Để làm điều này trong chương trình chúng ta làm theo các bước sau: Bước 1: Định nghĩa bảng các phím tắt theo mẫu sau
tên_bảng_phím_tắt { ACCELERATORS tên_phím_1 , số_hiệu_chức_năng_1 [ , kiểu ] [ tùy chọn ] tên_phím_2 , số_hiệu_chức_năng_2 [ , kiểu ] [ tùy chọn ] tên_phím_3 , số_hiệu_chức_năng_3 [ , kiểu ] [ tùy chọn ] ... }

Trong đó tên_bảng_phím_tắt là tên của một hằng số nguyên thông thường giống với tên thực đơn đã định nghĩa, tên_phím là ký hiệu các phím tắt cần đặt, có thể mã phím dưới dạng ascii hoặc mã phím ảo. Nếu là phím dạng mã ascii thì tham số kiểu là ASCII, còn nếu sử dụng mã phím ảo thì kiểu là VIRTKEY bao gồm một trong các phím có trên bàn phím hoặc tổ hợp phím bao gồm:

64

Ký hi u tên phím “0”, “1”, ..., “9”, “A”...”Z” VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN VK_F1, ..., VK_F12 VK_CONTROL, VK_ALT, VK_SHIFT và r t nhi u phím khác

Ý nghĩa Các phím s , phím ch , ... Các phím mũi tên Các phím ch c năng F1 đ n F12 Các phím Ctrl, Alt, Shift

Tham số tùy chọn quy định các phím tổ hợp được kết hợp với phím đã đặt, bao gồm NOINVERT, ALT, SHIFT, CONTROL. Giá trị NOINVERT quy định chức năng sẽ không được kích hoạt khi bấm phím tắt tương ứng, còn ALT, SHIFT, CONTROL là các phím sẽ được kết hợp làm phím tắt. Ví dụ một bảng phím như sau:
IDR_MYMENU ACCELERATORS DISCARDABLE BEGIN "1", ID_CAPNHAT_NHAPMOI, VIRTKEY, CONTROL, NOINVERT "2", ID_CAPNHAT_SUACHUA, VIRTKEY, CONTROL, NOINVERT "A", ID_TIMKIEM_TIMTHEOQUEQUAN, VIRTKEY, ALT, NOINVERT "B", ID_TIMKIEM_TIMTHEOTEN, VIRTKEY, ALT, NOINVERT VK_F1, ID_BAOCAO_BAOCAODOANHTHU, VIRTKEY, CONTROL, NOINVERT VK_F2, ID_BAOCAO_BAOCAOLOINHUAN, VIRTKEY, CONTROL, NOINVERT VK_F3, ID_BAOCAO_BAOCAOTONKHO, VIRTKEY, CONTROL, NOINVERT END

Chú ý: Bảng các phím tắt được định nghĩa trong tệp tài nguyên *.rc của chương trình cùng với thực đơn. Bước 2: Sau khi tạo bảng các phím tắt cho các chức năng trên thực đơn chúng ta phải nạp bảng phím này vào chương trình mới sử dụng được, bằng cách dùng hàm LoadAccelTable() của lớp CFrameWnd như sau:
BOOL LoadAccelTable( LPCTSTR lpszResourceName );

Trong đó tham số lpszResourceName là tên của bảng phím tắt đã định nghĩa, nếu là số nguyên phải chuyển về dạng LPSTR bằng ép kiểu. Thực hiện nạp bảng phím vào chương trình sau khi tạo cửa sổ bằng lệnh Create(), thường viết trong hàm tạo của lớp cửa sổ tương ứng.

65

Ví dụ 3.4
Lập trình tạo cửa sổ có thực đơn và hiện thông báo khi chọn thực đơn đó, đồng thời đặt các phím nóng cho các chức năng tương ứng trên thực đơn.
/* T p đ nh nghĩa các tên h ng – resource.h */ #define IDR_MYMENU 101 #define ID_CAPNHAT_NHAPMOI 40001 #define ID_CAPNHAT_SUACHUA 40002 #define ID_CAPNHAT_XEMDANHSACH 40003 #define ID_HETHONG_TROGIUP 40007 #define ID_HETHONG_THOAT 40008 /* T p các tài nguyên g m th c đơn và b ng phím – main.rc */ IDR_MYMENU MENU DISCARDABLE BEGIN POPUP "Cap nhat du lieu" BEGIN MENUITEM "Nhap moi\t Ctrl+1", ID_CAPNHAT_NHAPMOI MENUITEM "Sua chua\tAlt+2", ID_CAPNHAT_SUACHUA MENUITEM "Xem danh sach\tCtrl+A", ID_CAPNHAT_XEMDANHSACH END POPUP "He thong chuong trinh" BEGIN MENUITEM "Tro giup\tF1", ID_HETHONG_TROGIUP MENUITEM "Thoat\tCtrl+Atl+F4", ID_HETHONG_THOAT END END // Accelerator IDR_MYMENU ACCELERATORS DISCARDABLE BEGIN "1", ID_CAPNHAT_NHAPMOI, VIRTKEY, CONTROL, NOINVERT "2", ID_CAPNHAT_SUACHUA, ASCII, ALT, NOINVERT VK_F1, ID_HETHONG_TROGIUP, VIRTKEY, NOINVERT VK_F4, ID_HETHONG_THOAT, VIRTKEY, CONTROL, ALT, NOINVERT "^A", ID_CAPNHAT_XEMDANHSACH, ASCII, NOINVERT END /* T p n i dung chương trình chính – main.cpp */ #include<afxwin.h> #include"resource.h" class CMyApp : public CWinApp { public: BOOL InitInstance();

66

}; class CMyWin : public CFrameWnd { public: CMyWin(); afx_msg void OnNhapmoi(); afx_msg void OnSuachua(); afx_msg void OnXemdanhsach(); afx_msg void OnTrogiup(); afx_msg void OnThoat(); DECLARE_MESSAGE_MAP() }; BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_COMMAND( ID_CAPNHAT_NHAPMOI , OnNhapmoi ) ON_COMMAND( ID_CAPNHAT_SUACHUA , OnSuachua ) ON_COMMAND( ID_CAPNHAT_XEMDANHSACH , OnXemdanhsach ) ON_COMMAND( ID_HETHONG_TROGIUP , OnTrogiup ) ON_COMMAND( ID_HETHONG_THOAT , OnThoat ) END_MESSAGE_MAP() CMyWin::CMyWin() { Create(NULL,"Chuong trinh vi du 04 - Chapter 03"); //Load & set menu for program CMenu *m = new CMenu; m->LoadMenu(IDR_MYMENU); SetMenu(m); //Load table of accelerator LoadAccelTable((LPSTR)IDR_MYMENU); } afx_msg void CMyWin::OnNhapmoi() { MessageBox("Ban da chon muc nhap moi","Thong bao",MB_OK+ MB_ICONEXCLAMATION);

67

} afx_msg void CMyWin::OnSuachua() { MessageBox("Ban da chon muc sua chua","Thong bao",MB_OK | MB_ICONQUESTION); } afx_msg void CMyWin::OnXemdanhsach() { MessageBox("Ban da chon muc xem danh sach","Thong bao",MB_OK | MB_ICONINFORMATION); } afx_msg void CMyWin::OnTrogiup() { MessageBox("Ban da chon muc tro giup","Thong bao",MB_OK | MB_ICONHAND); } afx_msg void CMyWin::OnThoat() { //Dong cua so & thoat khoi chuong trinh MessageBox("Ban da chon muc thoat","Thong bao",MB_OK | MB_ICONSTOP); OnClose(); } CMyApp theApp;

Kết quả khi chạy chương trình và bấm phím “^A” (Ctrl+A) sẽ xuất hiện như sau:

68

3.2.4. Lớp CMenu cho thực đơn
Trong thư viện của MFC cung cấp lớp CMenu để tạo và quản lý thực đơn của chương trình, mỗi thực đơn sẽ được quản lý bởi một đối tượng CMenu. Trong lớp này có các hàm thành viên để thực hiện thao tác trên thực đơn như sau: - Hàm tạo thực đơn ngang
BOOL CreateMenu()

Hàm này sẽ trả về giá trị TRUE nếu tạo thành công và ngược lại sẽ trả về giá trị FALSE. - Hàm tạo thực đơn dọc (popup menu)
BOOL CreatePopupMenu( );

Lệnh tạo này cho phép tạo các thực đơn ngữ cảnh (context menu) khi bấm chuột phải vào một vị trí nào đó trên cửa sổ. - Hàm thêm mục chọn vào thực đơn
BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

hoặc
BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );

Trong đó: + nFlags: là giá trị cờ xác định mục chọn sẽ thêm vào gồm các giá trị sau:
Giá tr c MF_STRING MF_SEPARATOR MF_POPUP MF_MENUBREAK Ý nghĩa M c ch n là m t chu i ký t M c ch n là d u ngăn cách M c ch n có th c đơn d c T o m c ch n trên danh sách m i

+ nIDNewItem: là số hiệu mục chọn sẽ thêm vào thực đơn, số hiệu này được sử dụng để định danh và xử lý lựa chọn của người dùng trong chương trình.
69

Chú ý: Nếu giá trị cờ là MF_POPUP thì tham số này sẽ là số hiệu (HMENU) của thực đơn con cho mục chọn đó, giá trị số hiệu của một thực đơn này được lưu trong thành phần m_hMenu của đối tượng lớp CMenu. + lpszNewItem: là dòng chữ hiện trên mục chọn của thực đơn. + *pBmp: là ảnh bitmap được gắn với mục chọn, đối tượng ảnh bitmap sẽ được trình bày ở phần sau. - Chèn mục chọn vào thực đơn
BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

hoặc:
BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );

Các tham số giống như lệnh thêm mục chọn AppendMenu() ở trên, có thêm tham số nPosition để xác định vị trí của mục chọn cần thêm trong danh sách của thực đơn. Giá trị nPosition có thể xác định vị trí hoặc số hiệu chức năng tương ứng với giá trị cờ nFlags có chứa MF_BYPOSITION hoặc MF_BYCOMMAND. - Xóa bỏ mục chọn trên thực đơn
BOOL RemoveMenu( UINT nPosition, UINT nFlags );

Trong đó UINT nPosition là vị trí hoặc số hiệu của mục chọn cần xóa, tùy thuộc vào giá trị nFlags là MF_BYCOMMAND hoặc MF_BYPOSITION. - Lấy thực đơn chọn dọc (thực đơn con)
CMenu* GetSubMenu( int nPos ) const;

Hàm trả về biến trỏ kiểu CMenu của thực đơn con dọc của một thực đơn ngang tại một vị trí nPos được đưa vào. - Hàm đánh dấu mục chọn trên thực đơn
UINT CheckMenuItem( UINT nIDCheckItem, UINT nCheck );

Hàm trả về giá trị trạng thái mục chọn trước khi đánh dấu, cụ thể là MF_CHECKED, MF_UNCHECKED hoặc giá trị 0xFFFFFFFF nếu như mục chọn không tồn tại.
70

Trong đó nIDCheckItem là số hiệu hoặc vị trí của mục chọn cần đánh dấu, tùy thuộc vào tham số nCheck được kết hợp từ các giá trị MF_BYCOMMAND, MF_BYPOSITION và MF_CHECKED, MF_UNCHECKED. - Hàm thay đổi trạng thái của mục chọn thực đơn
UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );

Hàm trả về trạng thái trước đó (MF_DISABLED, MF_ENABLED, MF_GRAYED, hoặc -1). Trong đó giá trị nIDEnableItem là số hiệu hoặc vị trí của mục chọn cần thao tác, nEnable là giá trị trạng thái được kết hợp từ 1 trong 2 giá trị MF_BYCOMMAND, MF_BYPOSITION với 1 trong 3 giá trị MF_DISABLED, MF_ENABLED và MF_GRAYED. - Hàm hiện thực đơn dạng popup (thực đơn ngữ cảnh)
BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );

Trong đó: + int x,y: là tọa độ xác định vị trí cần hiện thực đơn trên cửa sổ. Tọa độ này phải tính theo màn hình, từ góc trái trên của màn hình. + UINT nFLags: giá trị cờ để hiện thực đơn, bao gồm:
Giá tr c TPM_CENTERALIGN TPM_LEFTALIGN TPM_RIGHTALIGN TPM_LEFTBUTTON TPM_RIGHTBUTTON Ý nghĩa T a đ x s căn gi a th c đơn T a đ x s căn trái th c đơn T a đ x s căn ph i th c đơn Ch n th c đơn b ng chu t trái Ch n th c đơn b ng chu t ph i

+ CWnd* pWnd: là tham số xác định cửa sổ chứa thực đơn ngữ cảnh khi hiển thị, + LPCRECT lpRect: là tham số xác định khung giới hạn cửa sổ hiện thực đơn, mặc định là NULL.
71

- Hàm thay đổi mục chọn trên thực đơn
BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

hoặc:
BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );

Có thể thay đổi một chức năng xác định theo vị trí hoặc số hiệu nếu giá trị nFlags là MF_BYPOSITION hoặc MF_BYCOMMAND, với chức năng đã được xác định chúng ta có thể thay số hiệu bởi một số hiệu mới nIDNewItem hoặc thay đổi nội dung của chức năng bởi lpszNewItem hoặc dạng ảnh pBmp. - Hàm đặt hình ảnh cho chức năng của thực đơn
BOOL SetMenuItemBitmaps( UINT nPosition, UINT nFlags, const CBitmap* pBmpUnchecked, const CBitmap* pBmpChecked );

Xác định chức năng cần đặt hình ảnh bằng tham số nPosition và nFLags như các hàm khác, ngoài ra tham số pBmpUnchecked xác định hình ảnh ở trạng thái không chọn, pBmpChecked xác định hình ảnh của chức năng ở trạng thái chọn. Hình cần đặt phải dưới dạng bitmap thông qua đối tượng lớp CBitmap, sẽ được trình bày ở phần sau. - Hàm xóa bỏ tài nguyên thực đơn khỏi đối tượng CMenu
BOOL DestroyMenu( );

Chú ý: Ngoài ra nếu cần làm việc thực đơn hệ thống của cửa sổ chúng ta cũng sử dụng lớp CMenu bằng cách xác định con trỏ đến nó bằng lệnh sau:
CMenu* GetSystemMenu( BOOL bRevert ) const;

Trong đó tham số bRevert quy định thực đơn có được khôi phục lại trạng thái ban đầu hay không, nếu bằng TRUE có khôi phục và FALSE nếu ngược lại.
72

Hàm trả về con trỏ kiểu lớp CMenu tới đối tượng thực đơn của cửa sổ, thông thường thực đơn này có các chức năng sau:

Tuy nhiên chúng ta có thể thay đổi nếu cần thiết và ánh xạ thông điệp chọn các chức năng tới các hàm thành viên mong muốn của lớp cửa sổ tương ứng.

Ví dụ 3.5
Lập trình tạo cửa sổ với thực đơn ngang được tạo và quản lý bằng một đối tượng CMenu gồm các chức năng: File, Edit, Tools, Help. Xử lý thông điệp nút phải chuột để tạo và hiện lên cửa sổ thực đơn ngữ cảnh với các chức năng như: Phóng to, Thu nhỏ, Đóng. Thực hiện xử lý các thông điệp chọn chức năng của thực đơn ngữ cảnh tương ứng.
/* T p tin đ nh nghĩa các tên h ng m c ch n - resource.h */ #define IDM_FILE 10004 #define IDM_EDIT 10005 #define IDM_TOOL 10006 #define IDM_HELP 10007 #define IDM_CONTEXT_MAX 100011 #define IDM_CONTEXT_MIN 100012 #define IDM_CONTEXT_RES 100013 /* T p tin chương trình chính - main.cpp */ #include<afxwin.h> #include"resource.h" class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd {

73

public: CMyWin(); afx_msg void OnRButtonDown(UINT nFlags, CPoint point); afx_msg void OnMax(); afx_msg void OnMin(); afx_msg void OnRes(); DECLARE_MESSAGE_MAP() }; BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_RBUTTONDOWN() ON_COMMAND( IDM_CONTEXT_MAX, OnMax ) ON_COMMAND( IDM_CONTEXT_MIN, OnMin ) ON_COMMAND( IDM_CONTEXT_RES, OnRes ) END_MESSAGE_MAP() CMyWin::CMyWin() { Create(NULL,"Chuong trinh vi du 05 - Chapter 03"); //Create & set menu for program CMenu *m = new CMenu; m->CreateMenu(); m->AppendMenu(MF_BYCOMMAND,IDM_FILE,"File"); m->AppendMenu(MF_BYCOMMAND,IDM_EDIT,"Edit"); m->AppendMenu(MF_BYCOMMAND,IDM_TOOL,"Tools"); m->AppendMenu(MF_BYCOMMAND,IDM_HELP,"Help"); SetMenu(m); } afx_msg void CMyWin::OnRButtonDown(UINT nFlags, CPoint point) { CRect r1; int h1 = GetSystemMetrics(SM_CYCAPTION); h1 = h1 + GetSystemMetrics(SM_CYMENU); CMenu *m = new CMenu; m->CreatePopupMenu(); m->AppendMenu(MF_BYCOMMAND,IDM_CONTEXT_MAX,"Phong to");

74

m->AppendMenu(MF_BYCOMMAND,IDM_CONTEXT_MIN,"Thu to"); m->AppendMenu(MF_BYCOMMAND,IDM_CONTEXT_RES,"Binh thuong"); GetWindowRect(&r1); m->TrackPopupMenu(TPM_LEFTALIGN,r1.left+point.x,r1.top+point.y+h1,this); } afx_msg void CMyWin::OnMax() { ShowWindow(SW_SHOWMAXIMIZED); } afx_msg void CMyWin::OnMin() { ShowWindow(SW_SHOWMINIMIZED); } afx_msg void CMyWin::OnRes() { ShowWindow(SW_RESTORE); } CMyApp theApp;

Trong chương trình có sử dụng lệnh xác định kích thước tiêu đề cửa sổ và thực đơn ngang, đó là:
int GetSystemMetrics( int nIndex );

Lệnh này sẽ trả về giá trị của các tham số hệ thống xác định trong tham số nIndex, bao gồm một số tham số sau:
Giá tr nIndex SM_CMOUSEBUTTONS SM_CXBORDER, ...CY... SM_CXCURSOR, ...CY... SM_CXFULLSCREEN, ...CY... SM_CXICON, ...CY... SM_CXSCREEN, ...CY... SM_CYCAPTION SM_CYMENU SM_MOUSEPRESENT SM_MOUSEWHEELPRESENT Ý nghĩa (tr l i) S nút chu t Đ dày c a đư ng vi n theo x, y Đ l n c a con tr chu t theo x,y Đ l n c a c a s khi phóng to x,y Đ l n c a bi u tư ng theo x,y Đ l n c a màn hình theo x,y đ cao c a tiêu đ c a s Đ cao c a th c đơn c a s Có chu t hay không Chu t có nút cu n hay không

75

Vì lệnh hiện thực đơn ngữ cảnh TrackPopupMenu() đòi hỏi phải xác định tọa độ màn hình nên chúng ta phải lấy tọa của chuột trên cửa sổ cộng với tọa độ cửa sổ và thêm độ cao của tiêu đề với thực đơn. Kết quả của chương trình sẽ như sau:

Chú ý: Có thể lấy thực đơn hệ thống trên cửa sổ (nằm ở góc trái trên) và thay đổi nó qua lớp CMenu. Cú pháp thực hiện bằng lệnh sau:
CMenu* GetSystemMenu( BOOL bRevert ) const;

Hàm trả về con trỏ tới đối tượng thực đơn ứng với cửa sổ, tham số bRevert quy định thực đơn sau khi lấy và thay đổi có được khôi phục lại hay không ứng với TRUE hoặc FALSE. Có thể thêm hoặc bớt các mục chọn trong thực đơn hệ thống của cửa sổ, với các mục chọn mới có thể ánh xạ xử lý bởi các hàm trong lớp cửa sổ tương ứng. Ví dụ sau minh họa thực đơn hệ thống của cửa sổ đã bị thay đổi:

76

3.3. Biểu tượng và ảnh (icon, cursor, bitmap)
Biểu tượng chương trình và chuột là những thành phần quan trọng của các ứng dụng trên Windows, tuy nhiên trong các chương trình trước đây đều sử dụng biểu tượng mặc định của Windows. Trong phần này sẽ trình bày cách tạo và sử dụng những biểu tượng theo mong muốn. Biểu tượng và hình ảnh là những tài nguyên của chương trình, được tạo ra bằng những chương trình biên tập hình ảnh. Sau khi tạo thành công các biểu tượng hình ảnh sẽ được định nghĩa vào trong tệp tài nguyên của chương trình. Trước khi sử dụng chúng ta phải nạp vào máy tính từ chương trình. Nội dung ba loại tài nguyên này được lưu trong ba loại tệp tin sau: + ICON lưu trong tệp có phần mở rộng là: *.ico, + CURSOR lưu trong tệp có phần mở rộng là: *.cur, + BITMAP lưu trong tệp có phần mở rộng là: *.bmp. Để định nghĩa một tài nguyên biểu tượng, ảnh trong chương trình chúng ta sử dụng theo cách sau:
tên_icon tên_sursor tên_bitmap ICON CURSOR BITMAP tên_tệp_lưu_icon tên_tệp_lưu_cursor tên_tệp_lưu_bitmap

Ví dụ chúng ta có 3 biểu tượng ảnh lưu trong 3 tệp như sau:
mybitmap.bmp mycursor.cur myicon.ico

Lệnh để định nghĩa các biểu tượng ảnh vào trong tệp tài nguyên chương trình là:
MYBITMAP BITMAP mybitmap.bmp MYCURSOR CURSOR mycursor.cur MYICO ICO myico.ico

77

Chú ý: Các tệp lưu ảnh biểu tượng này phải được chép vào cùng với thư mục của chương trình, trong thư mục project của chương trình. Mỗi tài nguyên biểu tượng hình ảnh khi nạp vào máy trong chương trình sẽ được quản lý bằng một số hiệu gồm: HICON, HCURSOR, HBITMAP.

3.3.1. Nạp và sử dụng ICON và CURSOR
Để nạp các tài nguyên biểu tượng hình ảnh này vào máy khi chạy chương trình chúng ta sử dụng các hàm thành viên của lớp ứng dụng CWinApp như sau: - Nạp tài nguyên biểu tượng ICON
HICON CWinApp :: LoadIcon( tên_số_hiệu_tài_nguyên );

- Nạp tài nguyên biểu tượng CURSOR
HCURSOR CWinApp :: LoadCursor( tên_số_hiệu_tài_nguyên );

Ngoài ra có thể sử dụng các tài nguyên biểu tượng sẵn có trong Windows và nạp bằng các lệnh sau:
HICON CWinApp :: LoadStandardIcon( tên_tài_nguyên ); CWinApp :: LoadStandardCursor( tên_tài_nguyên ); HCURSOR

Với giá trị tên tài nguyên của icon sẽ là một trong các giá trị sau:
Tên tài nguyên IDI_ASTERISK IDI_EXCLAMATION IDI_HAND IDI_QUESTION IDI_WINLOGO Ý nghĩa Thông tin C nh báo D ng H i Lôgô c a windows

Giá trị tên tài nguyên của cursor cung cấp bởi Windows có thể là:
Tên tài nguyên IDC_ARROW IDC_CROSS IDC_IBEAM IDC_WAIT Ý nghĩa Mũi tên Ch th p Ch I (con tr đ nh p li u) Đ ng h cát

78

Tài nguyên biểu tượng ICON sẽ được sử dụng để gắn cho cửa sổ của chương trình và có thể thực hiện bằng lệnh SetIcon() ở bất kỳ thời điểm nào sau khi tạo xong cửa sổ trong chương trình:
HICON CWnd :: SetIcon( HICON hIcon, BOOL bBigIcon );

Là một hàm thành viên của lớp CWnd, trong đó tham số hIcon xác định số hiệu icon đã được nạp, bBigIcon cho biết icon ở trạng thái nhỏ hay to. Hàm sẽ trả về số hiệu icon cũ cửa sổ trước khi đặt mới. Đối với việc đặt biểu tượng cursor chúng ta phải sử dụng lớp cửa sổ (window class), lớp cửa sổ là cấu trúc chứa các giá trị của các tham số liên quan đến việc thiết lập một cửa sổ. Lớp cửa sổ phải được đăng ký rồi mới tạo cửa sổ, tuy nhiên trong các ví dụ trước chúng ta sử dụng lớp cửa sổ mặc định của MFC cung cấp. Phần này chúng ta sử dụng lệnh sau để đăng ký lớp cửa sổ và thiết lập các tham số bao gồm: kiểu lớp cửa sổ, biểu tượng cursor, kiểu nền cửa sổ, biểu tượng icon.
LPCTSTR AFXAPI AfxRegisterWndClass( UINT nClassStyle, HCURSOR hCursor = 0, HBRUSH hbrBackground = 0, HICON hIcon = 0 );

Trong đó nClassStyle xác định kiểu lớp cửa sổ, hCursor và hIcon xác định hai biểu tượng con trỏ chuột và biểu tượng cửa sổ, hbrBackground xác định kiểu nền cửa sổ. Thông thường tham số nClassStyle đặt bằng NULL và máy sẽ lấy mặc định, còn tham số hCursor và hIcon là số hiệu biểu tượng cần đặt, có thể sử dụng kết quả của lệnh nạp hai biểu tượng như sau:
HCURSOR :: LoadCursor()


UICON :: LoadIcon().

Hàm đăng ký lớp cửa sổ sẽ trả về giá trị tên của một lớp cửa sổ đã đăng ký thành công, giá trị ta đưa vào tham số đầu tiên của lệnh tạo Create() trong lớp cửa sổ.
Create( tên_lớp_cửa_sổ , tiêu_ñề_cửa_sổ )

79

Ngoài ra, tham số hbrBackground sẽ lấy mặc định là màu trắng hoặc sử dụng lệnh sau để xác định các kiểu nền có sẵn của Windows:
HGDIOBJ GetStockObject( int fnObject );

Trong đó tham số fnObject có thể một trong các giá trị sau để chọn kiểu nền: WHITE_BRUSH (nền trắng), LTGRAY_BRUSH (nền xám nhạt), BLACK_BRUSH (nền đen), GRAY_BRUSH (nền xám), DKGRAY_BRUSH (nền xám đậm). Hàm này trả về một giá trị kiểu HGDIOBJ và chúng ta sẽ ép thành kiểu HBRUSH để đặt vào tham số trong lệnh đăng ký ở trên.

Ví dụ 3.6
Lập trình tạo một cửa sổ và đặt biểu tượng cửa sổ cũng như biểu tượng chương trình theo hình ảnh đã tạo hoặc lấy mặc định.
/* T p đ nh nghĩa các tên h ng - resource.h */ #define IDI_MYICON 102 #define IDC_MYCURSOR 103 /* T p đ nh nghĩa các tài nguyên - main.rc */ #include "resource.h" IDI_MYICON ICON DISCARDABLE IDC_MYCURSOR CURSOR DISCARDABLE /* T p chương trình chính - main.cpp */ #include<afxwin.h> #include"resource.h" /* Khai báo l p ng d ng và l p c a s chương trình */ class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); }; /* T o đ i tư ng ng d ng */

"icon2.ico" "cursor1.cur"

80

CMyApp theApp; /* Vi t l nh cho hàm kh i t o ng d ng */ BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } /* Vi t l nh cho hàm t o c a l p c a s */ CMyWin::CMyWin() { /* L y bi u tư ng chương trình trong tài nguyên */ HICON a = theApp.LoadIcon(IDI_MYICON); /* L y bi u tư ng chu t trong tài nguyên */ HCURSOR b = theApp.LoadCursor(IDC_MYCURSOR); /* L y ki u n n c a s có s n trong h th ng */ HBRUSH c = (HBRUSH)GetStockObject(GRAY_BRUSH); /* Đăng ký l p c a s vào h th ng */ LPCSTR d = AfxRegisterWndClass(NULL,b,c,a); /* T o c a s m i theo l p đã đăng ký */ Create(d , "Chuong trinh vi du 06 - Chapter 03"); }

Trong hàm tạo của lớp cửa sổ chúng ta sử dụng đối tượng ứng dụng (theApp) cùng các hàm tương ứng để nạp các biểu tượng và đăng ký lớp cửa sổ, tiếp theo tạo cửa sổ như thông thường. Vì trong hàm tạo của lớp cửa sổ có sử dụng đối tượng theApp nên nó phải được tạo ra trước đó, như trong ví dụ trên, ngay sau khai báo lớp. Chương trình khi chạy thử sẽ như sau:

81

3.3.2. Nạp và sử dụng ảnh bitmap
Tài nguyên ảnh bitmap được sử dụng để tạo các giao diện của chương trình, như đặt cho các nút lệnh, chức năng của thực đơn, hoặc có thể hiện lên cửa sổ. Để nạp và sử dụng tài nguyên ảnh bitmap trong chương trình chúng ta sử dụng đối tượng của lớp CBitmap để thực hiện với các bước sau: Bước 1: Nạp ảnh bitmap vào máy
BOOL CBitmap :: LoadBitmap( tên_số_hiệu_ảnh );

Bước 2: Tạo vùng nhớ sẽ lưu giữ ảnh bitmap cho việc hiển thị tương ứng với cửa sổ cần hiển thị ảnh, sử dụng đối tượng của lớp CDC
BOOL CDC :: CreateCompatibleDC( CDC* pDC );

Trong đó pDC là địa chỉ của ngữ cảnh cửa sổ cần hiển thị ảnh sẽ lấy được qua hàm GetDC(). Bước 3: Đặt ảnh bitmap đã nạp vào vùng nhớ tạo được ở bước 2
CBitmap* CDC :: SelectObject( CBitmap* pBitmap );

Trong đó pBitmap là địa chỉ của đối tượng CBitmap đã tạo ra và nạp ảnh ở bước 1. Hàm này trả về ảnh cũ đã đặt trước đó trong vùng nhớ. Bước 4: Chép nội dung hình ảnh từ đối tượng bitmap vào ngữ cảnh cửa sổ để hiển thị
BOOL CDC :: BitBlt ( int x, int y, int nWidth, int nHeight, CDC* SrcDC, int xSrc, int ySrc, DWORD dwRop );

Trong đó x,y là tọa độ xác định vị trí trên cửa sổ cần chép ảnh đến, nWidth và nHeight xác định độ rộng, chiều cao của ảnh cần chép. SrcDC là địa chỉ của ngữ cảnh bộ nhớ đã tạo và đặt lưu ảnh ở bước 2 và bước 3. xSrc và ySrc xác định tọa độ góc trái trên của ảnh sẽ chép lên cửa sổ và dwRop là chế độ chép, bao gồm: SRCAND, SRCCOPY, SRCINVERT, SRCPAINT. Thông thường sử dụng chế độ chép đè (SRCCOPY).
82

Ví dụ 3.7
Lập trình tạo cửa sổ và đặt một số ảnh bitmap lên các chức năng của thực đơn. Xử lý bấm nút trái chuột để hiện ảnh bitmap lên cửa sổ tại vị trí nhấn chuột. Chúng ta phải khai báo các đối tượng kiểu CBitmap trong lớp cửa sổ để lưu các ảnh bitmap khi đặt lên chức năng của thực đơn.
/* T p ch a đ nh nghĩa các tên h ng - resource.h */ #define IDB_BITMAP1 101 #define IDB_BITMAP2 102 #define IDB_BITMAP3 103 #define IDB_BITMAP4 104 #define IDM_ITEM1 1011 #define IDM_ITEM2 1022 #define IDM_ITEM3 1033 /* T p đ nh nghĩa các tài nguyên nh bitmap - main.rc */ #include "resource.h" IDB_BITMAP1 BITMAP DISCARDABLE "bitmap1.bmp" IDB_BITMAP2 BITMAP DISCARDABLE "bitmap2.bmp" IDB_BITMAP3 BITMAP DISCARDABLE "bitmap3.bmp" IDB_BITMAP4 BITMAP DISCARDABLE "bitmap4.bmp" /* T p chương trình chính - main.cpp */ #include<afxwin.h> #include"resource.h" class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { private: CBitmap bmp[3]; CMenu m; public: CMyWin(); afx_msg void OnLButtonDown(UINT nFLags, CPoint point); DECLARE_MESSAGE_MAP() };

83

CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { HICON a = theApp.LoadStandardIcon(IDI_QUESTION); HCURSOR b = theApp.LoadStandardCursor(IDC_CROSS); HBRUSH c = (HBRUSH)GetStockObject(WHITE_BRUSH); LPCSTR d = AfxRegisterWndClass(NULL,b,c,a); Create( d , "Chuong trinh vi du 07 - Chapter 03" ); //Create & set menu to the window m.CreateMenu(); bmp[0].LoadBitmap(IDB_BITMAP1); m.AppendMenu(MF_BYCOMMAND, IDM_ITEM1, &bmp[0]); bmp[1].LoadBitmap(IDB_BITMAP2); m.AppendMenu(MF_BYCOMMAND, IDM_ITEM2, &bmp[1]); bmp[2].LoadBitmap(IDB_BITMAP3); m.AppendMenu(MF_BYCOMMAND, IDM_ITEM3, &bmp[2]); SetMenu(&m); } afx_msg void CMyWin::OnLButtonDown(UINT nFLags, CPoint point) { CBitmap a; a.LoadBitmap(IDB_BITMAP4); CDC d,*c=GetDC(); d.CreateCompatibleDC(c); d.SelectObject(&a); c->BitBlt(point.x, point.y, 160, 120, &d, 0, 0, SRCCOPY); }

84

Kết quả chương trình sau khi bấm chuột trái vào các điểm trên cửa sổ sẽ có như sau:

3.4. Hộp hội thoại (dialog box) và lớp CDialog
3.4.1. Cơ bản về hộp thoại
Hộp thoại (dialog) là một kiểu cửa sổ tạo nên tương tác giữa người dùng và chương trình một cách mềm dẻo. Nhìn chung các hộp thoại cho phép người dùng lựa chọn hoặc điền các thông tin mà không thể hoặc rất khó thực hiện bằng thực đơn (menu). Hộp thoại có mặt hầu hết trong mọi ứng dụng, là một phần rất lớn trong lập trình các ứng dụng. Với khuôn khổ tài liệu này chúng tôi chỉ đề cập đến các hộp thoại dạng đơn giản. Trên hộp thoại có các điều khiển (controls) để người dùng tương tác, một điều khiển cũng là một kiểu cửa sổ đặc biệt để nhập hoặc hiện dữ liệu. Các điều khiển đều được chứa trong một cửa sổ mẹ, đó là hộp thoại. Một số điều khiển đơn giản như sau: nút bấm (push button), hộp kiểm tra (check box), nút chọn (radio button), ô nhập liệu (text box),...
85

Một ví dụ về hộp thoại chọn phông chữ cửa ứng dụng Word:

Trên Windows có hai loại hộp thoại: loại modal có đặc trưng là phải được kết thúc trước khi tiếp tục trên cửa sổ mẹ, hầu hết chúng ta dùng loại này; loại modeless không cần phải kết thúc, tức là có thể tiếp công việc trên cửa sổ mẹ mà không cần đóng nó. Sau đây là một số vấn đề cơ bản về hộp thoại: i) Cách định nghĩa tài nguyên hộp thoại Hộp thoại tồn tại dưới dạng tài nguyên của chương trình, được định nghĩa bên trong tệp tài nguyên (*.rc) của chương trình. Mẫu khai báo một hộp thoại như sau:
86

tên-hộp-thoại CAPTION STYLE {

DIALOG

[tùy-chọn]

X, Y, Width, Height

tiêu-ñề-hộp-thoại kiểu-hộp-thoại

ñịnh-nghĩa-các-ñiều-khiển-trên-hộp-thoại }

Trong đó kiểu hộp thoại (STYLE) gồm các giá trị sau:
Giá tr ki u DS_MODALFRAME WS_BORDER WS_CAPTION WS_CHILD WS_HSCROLL WS_MAXIMIZEBOX WS_MINIMIZEBOX WS_SYSMENU WS_TABSTOP WS_VISIBLE WS_VSCROLL Ý nghĩa H p tho i có khung ki u modal Có vi n Có tiêu đ Là c a s con Có thanh cu n ngang Có nút ch n phóng to Có nút ch n thu nh Có th c đơn h th ng Có th dùng Tab trên h p tho i H p tho i s th y ngay khi hi n Có thanh cu n d c

Khai báo này đặt trong tệp tài nguyên *.RC của chương trình (cùng với thực đơn,...). Định nghĩa các điều khiển sẽ trình bày phần tiếp sau. ii) Lớp CDialog để làm việc với hộp thoại Trong chương trình để sử dụng và quản lý hộp thoại chúng ta sử dụng lớp CDialog của thư viện MFC, được dẫn xuất từ lớp CWnd. Lớp CDialog có 3 mẫu hàm tạo như sau:
CDialog ( LPCSTR lpszDName, CWnd *Owner = NULL); //mẫu1 CDialog ( UINT CDialog ( ); ID, CWnd *Owner = NULL); // mẫu 2 // mẫu 3

87

Trong mẫu 1 và 2 dùng để tạo đối tượng, đồng thời gắn với hộp thoại có tên lpszDName hoặc số hiệu ID định nghĩa ở tài nguyên, tham số Owner là con trỏ kiểu CWnd xác định cửa sổ mẹ chứa hộp thoại. Mẫu 3 dùng để tạo hộp thoại dạng Modeless sẽ trình bày sau. iii) Xử lý thông điệp cho hộp thoại Kỹ thuật xử lý thông điệp trên hộp thoại tương tự với cửa sổ, mỗi điều khiển trên hộp thoại có một số hiệu (ID) nên khi chúng ta - người dùng tác động sẽ phát sinh thông điệp WM_COMMAND gửi tới chương trình, vì vậy chúng ta sử dụng macro ON_COMMAND để ánh xạ việc xử ký thông điệp. Đi kèm với thông điệp WM_COMMAND có các tham số như số hiệu của điều khiển tác động, kiểu tác động của người dùng,... Chúng ta nên định nghĩa một lớp mới dẫn xuất từ lớp CDialog để thực hiện việc ánh xạ và xử lý các thông điệp cần thiết cho hộp thoại được tạo ra. iv) Hiện và đóng hộp thoại Đối với hộp thoại kiểu Modal chúng ta sử dụng hàm thành viên sau của lớp CDialog để hiện hộp thoại:
virtual int CDialog :: DoModal();

Hàm trả về giá trị mã kết thúc của hộp thoại khi đóng, hoặc trả về -1 nếu hiện không thành công, hoặc trả về IDABORT nếu bị lỗi sau khi hiện. Trong trường hợp bình thường thì chỉ khi đóng hộp thoại lệnh DoModal() trên mới trả về giá trị. Để đóng hộp thoại khi cần chúng ta dùng hàm thông điệp sau (ứng với hai nút lệnh Ok và Cancel) được xây dựng sẵn trong lớp CDialog:
void CDialog :: OnOk();

hoặc
void CDialog :: OnCancel();

Khi chọn nút Close (ở góc phải trên hộp thoại) thì máy sẽ tự động thực hiện hàm OnCancel().

88

3.4.2. Định nghĩa các điều khiển trên hộp thoại
i) Điều khiển Static text dùng để hiện văn bản tĩnh lên hộp thoại được khai báo như sau:
CTEXT " xâu-ký-tự " , số-hiệu , X, Y, Width, Height , kiểu

thay từ khóa CTEXT bằng LTEXT hoặc RTEXT để được các text căn chỉnh giữa, trái, phải. ii) Điều khiển Push button là các nút lệnh chọn trên hộp thoại, được khai báo như sau:
PUSHBUTTON kiểu " xâu-hiển-thị " , số-hiệu , X , Y , Width , Height ,

hoặc thay bằng từ khóa DEFPUSHBUTTON ở đầu để đặt mặc định. iii) Điều khiển Editbox để nhập dữ liệu trên hộp thoại, khai báo là:
EDITTEXT số-hiệu , X , Y , Width , Height , kiểu

iv) Điều khiển List/Combobox dùng để hiện thị danh sách chọn, khai báo là:
LISTBOX số-hiệu , X , Y , Width , Height , kiểu

hoặc thay LISTBOX bằng COMBOBOX. v) Điều khiển Checkbox để tạo các hộp đánh dấu kiểm tra, khai báo:
CHECKBOX "xâu", số-hiệu, X, Y, Width, Height, kiểu

vi) Điều khiển GROUPBOX để nhóm các điều khiển khác thành nhóm
GROUPBOX "xâu" , số-hiệu , X, Y, Width, Height, kiểu

vii) Điều khiển RADIOBUTTON để tạo các nút chọn
AUTORADIOBUTTON "xâu", số-hiệu, X, Y, Width, Height, kiểu

hoặc dùng khai báo sau:
CONTROL "xâu", số-hiệu, X, Y, Width, Height, kiểu

với tham số kiểu là BS_AUTORADIOBUTTON. viii) Điều khiển SCROLLBAR để tạo các thanh cuộn
SCROLLBAR số-hiệu, X, Y, Width, Height, kiểu

89

Trong các tham số ở trên có: "xâu" là tiêu đề hiển thị trên điều khiển, X và Y là tọa độ của điều khiển trên hộp thoại, Width & Height là độ rộng và chiều cao của điều khiển, kiểu để xác định các tham số khác cho điều khiển và có thể có hoặc không. Mỗi điều khiển phải có một số hiệu để khi xử lý trong lập trình đều thông qua số hiệu này mới tác động lên các điều khiển. Tham số kiểu bao gồm các giá trị của kiểu cửa sổ như trên và một số giá trị khác sau:
Giá tr LBS_NOTIFY SBS_VERT SBS_HORZ CBS_DROPDOWN CBS_SORT ES_MULTILINE Ý nghĩa Cho phép x lý các thông đi p Ki u thanh cu n d c Ki u thanh cu n ngang Ki u danh sách th Có s p các m c ch n Editbox có nhi u dòng

Tuy nhiên để dễ dàng trong việc tạo hộp thoại và các điều khiển chúng ta sẽ dùng công cụ của Visual C sẽ được trình bày ở phần cuối chương.

Ví dụ 3.8
Lập trình tạo một cửa sổ có thực đơn để hiện hộp thoại theo mẫu sau:

Chúng ta định nghĩa hộp thoại với các điều khiển trong tệp tài nguyên như sau:
90

IDR_MENU1 MENU DISCARDABLE BEGIN MENUITEM "Dialog", IDM_DIALOG END IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 237, 148 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Hop thoai don gian" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,58,120,50,14 PUSHBUTTON "Cancel",IDCANCEL,125,120,50,14 CTEXT "THONG TIN CA NHAN",IDC_STATIC,67,20,103,12 LTEXT "Ho va ten",IDC_STATIC,25,47,36,12 EDITTEXT IDC_EDIT1,65,47,145,14,ES_AUTOHSCROLL LTEXT "Ngay sinh",IDC_STATIC,25,63,36,12 EDITTEXT IDC_EDIT2,65,63,145,14,ES_AUTOHSCROLL LTEXT "Gioi tinh",IDC_STATIC,25,80,36,12 CONTROL "Nam",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,101,80,33, 13 CONTROL "Nu",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,65,80,33,13 LTEXT "Que quan",IDC_STATIC,25,96,36,12 COMBOBOX IDC_COMBO1,65,96,80,15,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP END

Lập chương trình như sau:
#include<afxwin.h> #include"resource.h" class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); void OnDialog(); DECLARE_MESSAGE_MAP() };

91

CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_COMMAND( IDM_DIALOG , OnDialog ) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { CMenu m; Create(NULL,"Chuong trinh vi du 08 - Chapter 03"); m.LoadMenu(IDR_MENU1); SetMenu(&m); } void CMyWin::OnDialog() { CDialog d(IDD_DIALOG1 , this); int kq=d.DoModal(); if (kq==IDOK) { MessageBox("Ban da chon nut Ok"); } else { MessageBox("Ban da chon nut Cancel"); } }

Trong hàm OnDialog để xử lý thông điệp chọn thực đơn IDM_DIALOG của lớp CMyWin, chúng ta khai báo đối tượng kiểu CDialog và sử dụng hộp thoại với số hiệu IDD_DIALOG1, sau đó gọi hàm DoModal() sẽ hiện hộp thoại và làm việc. Khi kết thúc máy trả về IDOK hoặc IDCANCEL phụ thuộc chọn nút lệnh trên hộp thoại. Kết quả giao diện chương trình khi chạy là:
92

3.5. Xử lý các điều khiển trên hộp thoại
Muốn tác động lên các điều khiển trên hộp thoại chúng ta phải định nghĩa lớp dẫn xuất từ CDialog và ánh xạ thông điệp tới các hàm tương ứng. Sau đây là một số hàm thực hiện trên các điều khiển của hộp thoại. Mỗi điều khiển có thể được quản lý bởi một đối tựơng thuộc lớp tượng ứng, trong chương trình có thể tạo các lớp dẫn xuất làm việc cho từng loại điều khiển hoặc sử dụng lớp sẵn có, bao gồm:
Tên l p CEdit CButton CListBox CComboBox CScrollBar CStatic C a đi u khi n Cho editbox Cho nút l nh, h p ki m tra, nút l a ch n Cho listbox Cho combobox Cho thanh cu n Cho ô hi n văn b n

Mỗi điều khiển sẽ hoạt động như một cửa sổ, các lớp trên đều dẫn xuất từ lớp CWnd. Để xác định điều khiển cần thao tác trên hộp thoại ta dùng hàm sau:
CWnd *CWnd :: GetDlgItem ( số-hiệu-ñiều-khiển ) const;

93

Hàm trả về con trỏ kiểu CWnd nên khi sử dụng chúng ta nên ép thành kiểu lớp tương ứng làm việc cho thuận tiện. Tuy nhiên chúng ta có thể tạo và sử dụng các điều khiển trên cửa sổ bằng cách tạo các đối tượng trong cửa sổ tương ứng. Sử dụng các hàm tạo trên từng đối tượng của lớp điều khiển.

3.5.1. Edit box
Editbox là một điều khiển cho phép nhập và hiện dữ liệu, được hiển thị bằng một hình chữ nhật. Điều khiển này được bao gói bởi lớp CEdit với các hàm thành viên sau: Hàm tạo điều khiển Editbox:
BOOL CEdit::Create( DWORD st, RECT& rt , CWnd* wp, UINT nID );

Trong đó: + st quy định kiểu điều khiển Editbox thường bao gồm WS_VISIBLE, WS_BORDER, WS_CHILD và các kiểu dành riêng cho editbox:
Ki u ES_PASSWORD ES_LEFT/RIGHT/CENTER ES_MULTILINE ES_AUTOVSCROLL/AUTOHSCROLL ES_READONLY Ý nghĩa T o h p gõ m t kh u Căn ch nh văn b n gõ vào Cho phép gõ nhi u dòng T đ ng cu n d li u Ch cho phép đ c, không s a

+ rt quy định kích thước và vị trí của điều khiển trên hộp thoại/cửa sổ. Tham số này có kiểu RECT gồm 4 thành phần {left, top, bottom, right}, + wp để quy định cửa sổ hoặc hộp thoại chứa điều khiển tạo ra, được xác định bằng con trỏ kiểu CWnd, + nID là số hiệu của điều khiển để phân biệt giữa các điều khiển với nhau trong cửa sổ hoặc hộp thoại. Hàm Create() ở trên sẽ trả về giá trị TRUE nếu thành công và ngược lại trả về FALSE nếu thất bại. Các hàm đặt dữ liệu, lấy dữ liệu dạng văn bản trên Editbox theo mẫu sau:
94

int

CWnd :: GetWindowText( LPSTR ñịa-chỉ-xâu, int Max );

void CWnd :: SetWindowText( LPSTR ñịa-chỉ-xâu );

Hàm GetWindowText() trả về số ký tự lấy thành công. Chúng ta sử dụng hàm này trên điều khiển editbox qua lớp CEdit. Ngoài ra có một số hàm trong lớp CEdit để thao tác theo mẫu sau:
Thông đi p EM_GETLIMITTEXT EM_GETLINE EM_GETLINECOUNT EM_GETMODIFY EM_LIMITTEXT EM_LINELENGTH EM_REPLACESEL EM_SETLIMITTEXT EM_SETSEL EM_UNDO Tên hàm GetLimitText() GetLine( idx , xâu ) GetLineCount() GetModify() LimitText( đ -dài ) LineLength() ReplaceSel( văn-b n-m i, khôi-ph c ) SetLimitText( đ -dài ) SetSel(v -trí-đ u, v -trí-cu i, cu n-không) Undo() Ý nghĩa Cho gi i h n text Cho dòng văn b n Cho s dòng Cho bi t có thay đ i Gi i h n đ dài nh p Cho đ dài c a d li u Thay th văn b n ch n Gi i h n đ dài ch a Bôi đen văn b n Khôi ph c văn b n

3.5.2. Push button, Checkbox và Radio button
Ba điều khiển này đều được bao gói và xử lý bởi lớp CButton, các hàm thành viên trong lớp này được định nghĩa theo mẫu sau: Hàm tạo ra một điều khiển mới trên cửa sổ/hộp thoại:
BOOL CButton :: Create( LPCTSTR CWnd* wp, UINT id ); cap, DWORD st, RECT& rt,

Trong đó: + LPCSTR cap: là xâu ký tự hiện trên điều khiển nút lệnh hoặc hộp kiểm tra hoặc nút chọn, + DWORD st: là tham số quy định kiểu thường gồm WS_CHILD, WS_VISIBLE, WS_BORDER và một số kiểu riêng cho loại điều khiển này:

95

Giá tr BS_AUTOCHECKBOX BS_AUTORADIOBUTTON BS_AUTO3STATE BS_DEFPUSHBUTTON BS_GROUPBOX BS_LEFTTEXT

Ý nghĩa T o h p ki m tra và t đ ng thay đ i tr ng thái ch n khi b m chu t T o nút ch n và t đ ng thay đ i tr ng thái khi b m chu t Đi u khi n có 3 tr ng thái: ch n, không ch n, và tr ng thái m T o nút l nh m c đ nh T o nhóm đi u khi n Văn b n hi n bên trái đi u khi n

+ RECT rt: là kích thước và vị trí của điều khiển, + CWnd wp: là cửa sổ mẹ chứa điều khiển, + UINT id: là số hiệu của điều khiển cần tạo. Hàm đặt trạng thái hộp kiểm tra, nút chọn (checkbox, radiobutton):
void CButton :: SetCheck( int kiểu);

với giá trị kiểu là 1 sẽ bật hộp kiểm tra, ngược lại bằng 0. hoặc hàm lấy trạng thái:
int CButton :: GetCheck();

3.5.3. Combobox và Listbox
Điều khiển ComboBox thao tác qua đối tượng lớp CComboBox, điều khiển listbox thao tác qua lớp CListBox. Có thể thấy điều khiển ComboBox là kết hợp của hai điều khiển editbox và listbox. Để tạo hai điều khiển này lên cửa sổ/hộp thoại ta sử dụng lệnh sau:
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );

Trong đó các tham số của hàm Create() tương tự như các điều khiển trước gồm kiểu (dwStyle), kích thước (rect), cửa sổ mẹ (pParentWnd) và số hiệu (nID). Các kiểu của tham số dwStyle có thể dùng ngoài WS_CHILD, WS_VISIBLE và WS_BORDER gồm:

96

Tên ki u LBS_EXTENDEDSEL LBS_MULTICOLUMN LBS_SORT CBS_AUTOHSCROLL CBS_DROPDOWN CBS_DROPDOWNLIST CBS_SORT

Ý nghĩa Cho phép ch n nhi u m c Hi n th nhi u c t Có s p x p các m c T đ ng hi n thanh cu n d c Danh sách ch n th xu ng Danh sách ch n d ng list Có s p x p

Ngoài ra cả hai lớp này có chung một số hàm theo mẫu sau:
Tên hàm int AddString( xâu ); int GetCount(); int GetText( idx , xâu ); int GetCurSel(); int SetCurSel( idx ); Ý nghĩa Thêm m c ch n Cho s m c ch n Cho m c ch n v trí idx, b t đ u t 0

Cho v trí m c đang ch n Đ t m c đ ch n

3.5.4. Scrollbar
Điều khiển thanh cuộn (ngang, dọc) đều được quản lý bởi lớp CScrollBar, do vậy chúng ta sử dụng các hàm thành viên của lớp này để tác động lên thanh cuộn. Sau đây là mẫu hàm tạo thanh cuộn trực tiếp:
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );

Trong đó tham số dwStyle quy định kiểu đối tượng thanh cuộn, 3 tham số còn lại như các lệnh tạo ở trên. Giá trị của tham số kiểu có thể gồm WS_VISIBLE, WS_BORDER, WS_CHILD và:
Tên ki u SBS_BOTTOMALIGN SBS_HORZ SBS_LEFTALIGN SBS_RIGHTALIGN SBS_TOPALIGN SBS_VERT Ý nghĩa Thanh cu n n m dư i cùng Thanh cu n ngang Thanh cu n n m phía trái Thanh cu n n m phía ph i Thanh cu n n m phía trên Thanh cu n d c

97

i) Xử lý thông điệp thanh cuộn Khác với các điều khiển khác máy sẽ không phát sinh thông điệp WM_COMMAND gửi tới cửa sổ hộp thoại, sẽ phát sinh thông điệp WM_VSCROLL hoặc WM_HSCROLL khi chúng ta tác động lên thanh cuộn. Khi đó chúng ta phải ánh xạ xử lý hai thông điệp này với mẫu hàm khai báo như sau:
afx_msg void CWnd :: OnVScroll(UINT k, INT v, CScrollBar *s); afx_msg void CWnd :: OnHScroll(UINT k, INT v, CScrollBar *s);

Trong đó k là kiểu tác động lên thanh cuộn, v là vị trí của con chạy trên thanh cuộn, s là con trỏ lớp CScrollBar ứng với thanh cuộn bị tác động. Nếu là thanh cuộn mặc định trên cửa sổ thì giá trị s sẽ bằng NULL. Kiểu tác động lên thanh cuộn có thể là một trong các giá trị sau:
Giá tr ki u SB_LINEUP/LINEDOWN SB_LINELEFT/LINERIGHT SB_PAGEUP/PAGEDOWN SB_PAGELEFT/PAGERIGHT SB_THUMBTRACK SB_THUMBPOSITION SB_TOP SB_BOTTOM Ý nghĩa D ch con ch y lên ho c xu ng D ch con ch y sang trái ho c ph i D ch con ch y lên/xu ng m t trang D ch trái/ph i m t trang Kéo rê con ch y trên thanh cu n V trí m i sau khi kéo rê con ch y Chuy n lên đ u Chuy n xu ng cu i

ii) Miền giới hạn vị trí của con chạy trên thanh cuộn Hàm đặt giới hạn có mẫu sau:
void rd); CWnd :: SetScrollRange( int w, int min, int max, BOOL

Trong đó w là giá trị SB_VERT hoặc SB_HORZ ứng với thanh cuộn dọc hoặc ngang, min và max là giá trị giới hạn dưới và trên của con chạy, rd là TRUE (mặc định) hoặc FALSE xác định thanh cuộn được vẽ lại sau khi đặt. Hàm trên chỉ thực hiện trên thanh cuộn của cửa sổ, để thực hiện trên điều khiển ScrollBar của hộp thoại ta dùng hàm của lớp CDialog sau:
98

void

CScrollBar :: SetScrollRange(int min, int max, BOOL rd);

Ngược lại hàm xác định giới hạn của thanh cuộn theo mẫu sau:
void void CWnd :: GetScrollRange( int w, LPINT CScrollBar :: GetScrollRange( LPINT min, LPINT max); min, LPINT max);

Hàm trả về hai giá trị min và max qua hai tham số tương ứng kiểu LPINT (địa chỉ biến kiểu int). iii) Vị trí con chạy của thanh cuộn Hai hàm thực hiện đặt vị trí con chạy trên thanh cuộn cửa sổ hoặc điều khiển trên hộp thoại theo mẫu sau:
int CWnd :: SetScrollPos( int w, int pos, BOOL ReDraw); int CScrollBar :: SetScrollPos( int pos, BOOL ReDraw);

Trong đó w là giá trị SB_VERT hoặc SB_HORZ, pos là vị trí mới cần đặt, ReDraw cho biết có vẽ lại hay không. Hai hàm trên trả về giá trị 1 hoặc 0 ứng với thành công hoặc không. Hàm xác định vị trí con chạy hiện thời là:
int CWnd :: GetScrollPos( int w ); int CScrollBar :: GetScrollPos();

hai hàm này sẽ trả về vị trí của con chạy. vi) Ngoài ra chúng ta có thể đặt hoặc lấy thông tin về thanh cuộn qua các hàm sau:
BOOL CWnd::SetScrollInfo( int w, LPSCROLLINFO if, BOOL rd); BOOL CScrollBar::SetScrollInfo( LPSCROLLINFO if, BOOL rd);

hoặc:
BOOL BOOL CWnd::GetScrollInfo( int w, LPSCROLLINFO if, UINT mk); CScrollBar :: GetScrollInfo( LPSCROLLINFO if, UINT mk);

Trong đó w là giá trị SB_VERT hoặc SB_HORZ của thanh cuộn trên cửa sổ, if là thông tin về thanh cuộn xác lập bởi cấu trúc SCROLLINFO và giá trị rd cho biết có vẽ lại hay không.
99

Cấu trúc SCROLLINFO được định nghĩa như sau:
typedef struct tagSCROLLINFO { UINT cbSize; UINT fMask; int nMin; int nMax; UINT nPage; int nPos; int nTrackPos; } SCROLLINFO, *LPSCROLLINFO;

Trong đó cbSize quy định kích thước cấu trúc, fMask quy định kiểu tham số của thanh cuộn bao gồm SIF_ALL, SIF_RANGE, SIF_POS, SIF_PAGE, SIF_TRACKPOS, nMin và nMax quy định giới hạn của con chạy, nPage quy định kích thước trang dịch con chạy, nPos quy định vị trí con chạy, nTrackPos bỏ qua trong hàm SetScrollInfo() này.

Ví dụ 3.9
Lập trình tạo một hộp thoại để nhập vào 3 số a,b,c. Thực hiện giải phương trình a.x2 + b.x + c = 0. Hộp thoại cũng là một cửa sổ do vậy ta sẽ lấy hộp thoại làm cửa sổ chính của chương trình, khi đó trong chương trình không cần lớp CMyWin như trước thay bằng lớp CMyDialog dẫn xuất từ lớp CDialog và trong hàm InitInstance() của lớp CMyApp ta tạo một hộp thoại và gọi lệnh DoModal() tương ứng để hiện hộp thoại. Khai báo hộp thoại trong tệp RC như sau:
IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 241, 114 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Vi du 09 - Chuong 03" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,101,93,50,14 LTEXT "He so a",IDC_STATIC,26,7,30,11

100

LTEXT "He so b",IDC_STATIC,26,22,30,11 LTEXT "He so c",IDC_STATIC,26,39,30,11 EDITTEXT IDC_EDIT1,61,7,41,13,ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,61,22,41,13,ES_AUTOHSCROLL EDITTEXT IDC_EDIT3,61,39,41,13,ES_AUTOHSCROLL PUSHBUTTON "Giai PT",IDC_BUTTON1,147,27,52,14 LTEXT "Nghiem cua phuong trinh",IDC_STATIC,26,58,88,11 EDITTEXT IDC_EDIT4,38,70,185,13,ES_AUTOHSCROLL END

Nội dung chương trình như sau:
#include<afxwin.h> #include<math.h> #include"resource.h" class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyDialog : public CDialog { public: CMyDialog(UINT ID, CWnd *owner=NULL) : CDialog(ID,owner) {} BOOL OnInitDialog(); afx_msg void OnGiaiPT(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyDialog , CDialog ) ON_COMMAND( IDC_BUTTON1, OnGiaiPT ) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyDialog(IDD_DIALOG1,NULL); int kq=((CMyDialog *)m_pMainWnd)->DoModal(); return TRUE; } BOOL CMyDialog::OnInitDialog()

101

{ return TRUE; } void CMyDialog::OnGiaiPT() { double a,b,c,d,x1,x2; char s[20]; CEdit *ed; ed=(CEdit*)GetDlgItem(IDC_EDIT1); ed->GetWindowText(s,20); a=atof(s); ed=(CEdit*)GetDlgItem(IDC_EDIT2); ed->GetWindowText(s,20); b=atof(s); ed=(CEdit*)GetDlgItem(IDC_EDIT3); ed->GetWindowText(s,20); c=atof(s); ed=(CEdit*)GetDlgItem(IDC_EDIT4); d=b*b-4*a*c; if (a==0) sprintf(s,"He so a bang 0, khong thuc hien"); else { if (d<0) sprintf(s,"Phuong trinh vo nghiem"); else { if (d==0) sprintf(s,"PT co nghiem kep x1=x2=%-0.5lf",-b/(a*2)); else { x1=(-b+sqrt(d))/(2*a); x2=(-b-sqrt(d))/(2*a); sprintf(s,"PT co 2 nghiem x1=%-0.5lf & x2=%-0.5lf",x1,x2); } } } ed->SetWindowText(s); }

102

Kết quả chương trình khi chạy thử là:

Ví dụ 3.10
Lập trình tạo một hộp thoại có 3 thanh cuộn chọn màu tổ hợp cho nền của một điều khiển text. Khai báo hộp thoại trong tệp RC như sau:
IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 248, 162 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Vi du 10 - Chuong 03" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,104,141,50,14 SCROLLBAR IDC_SCROLLBAR1,40,24,173,13 SCROLLBAR IDC_SCROLLBAR2,40,41,173,13 SCROLLBAR IDC_SCROLLBAR3,40,60,173,13 RTEXT "RED",IDC_STATIC,7,24,29,11 RTEXT "GREEN",IDC_STATIC,7,41,29,11 RTEXT "BLUE",IDC_STATIC,7,60,29,11 CTEXT "Color",IDC_STATIC1,40,78,194,55,WS_BORDER EDITTEXT IDC_EDIT1,217,24,18,13,ES_CENTER | ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,217,42,18,13,ES_CENTER | ES_AUTOHSCROLL EDITTEXT IDC_EDIT3,217,60,18,13,ES_CENTER | ES_AUTOHSCROLL END

Nội dung chính của chương trình như sau:

103

#include<afxwin.h> #include"resource.h" class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyDialog : public CDialog { private: int Red,Green,Blue; public: CMyDialog(UINT ID, CWnd *owner=NULL) : CDialog(ID,owner) {} afx_msg void OnHScroll(UINT Kieu,UINT Pos,CScrollBar *pS); BOOL OnInitDialog(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyDialog , CDialog ) ON_WM_HSCROLL() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyDialog(IDD_DIALOG1,NULL); int kq=((CMyDialog *)m_pMainWnd)->DoModal(); return TRUE; } BOOL CMyDialog::OnInitDialog() { CScrollBar *s; SCROLLINFO sif; sif.cbSize=sizeof(SCROLLINFO); sif.fMask=SIF_ALL; sif.nMax=255; sif.nMin=0; sif.nPage=10; sif.nPos=0; s = (CScrollBar*)GetDlgItem(IDC_SCROLLBAR1); s->SetScrollInfo(&sif);

104

s = (CScrollBar*)GetDlgItem(IDC_SCROLLBAR2); s->SetScrollInfo(&sif); s = (CScrollBar*)GetDlgItem(IDC_SCROLLBAR3); s->SetScrollInfo(&sif); Red=Green=Blue=0; return TRUE; } void CMyDialog::OnHScroll(UINT Kieu,UINT Pos,CScrollBar *pS) { HWND hwr,hwg,hwb; GetDlgItem(IDC_SCROLLBAR1,&hwr); GetDlgItem(IDC_SCROLLBAR2,&hwg); GetDlgItem(IDC_SCROLLBAR3,&hwb); switch (Kieu) { case SB_LEFT: if (hwr==pS->m_hWnd) Red=0; else if (hwg==pS->m_hWnd) Green=0; else if (hwb==pS->m_hWnd) Blue=0; break; case SB_RIGHT: if (hwr==pS->m_hWnd) Red=255; else if (hwg==pS->m_hWnd) Green=255; else if (hwb==pS->m_hWnd) Blue=255; break; case SB_LINELEFT: if (hwr==pS->m_hWnd) Red--; else if (hwg==pS->m_hWnd) Green--; else if (hwb==pS->m_hWnd) Blue--; break; case SB_LINERIGHT: if (hwr==pS->m_hWnd) Red++; else if (hwg==pS->m_hWnd) Green++; else if (hwb==pS->m_hWnd) Blue++; break;

105

case SB_PAGELEFT: if (hwr==pS->m_hWnd) Red-=10; else if (hwg==pS->m_hWnd) Green-=10; else if (hwb==pS->m_hWnd) Blue-=10; break; case SB_PAGERIGHT: if (hwr==pS->m_hWnd) Red+=10; else if (hwg==pS->m_hWnd) Green+=10; else if (hwb==pS->m_hWnd) Blue+=10; break; case SB_THUMBTRACK: case SB_THUMBPOSITION: if (hwr==pS->m_hWnd) Red=Pos; else if (hwg==pS->m_hWnd) Green=Pos; else if (hwb==pS->m_hWnd) Blue=Pos; break; } if (Red<0) Red=0; if (Red>255) Red=255; if (Green<0) Green=0; if (Green>255) Green=255; if (Blue<0) Blue=0; if (Blue>255) Blue=255; if (hwr==pS->m_hWnd) pS->SetScrollPos(Red); else if (hwg==pS->m_hWnd) pS->SetScrollPos(Green); else if (hwb==pS->m_hWnd) pS->SetScrollPos(Blue); char s[20]; sprintf(s,"%d",Red); ((CEdit*)GetDlgItem(IDC_EDIT1))->SetWindowText(s); sprintf(s,"%d",Green); ((CEdit*)GetDlgItem(IDC_EDIT2))->SetWindowText(s); sprintf(s,"%d",Blue); ((CEdit*)GetDlgItem(IDC_EDIT3))->SetWindowText(s); CStatic *c = (CStatic*)GetDlgItem(IDC_STATIC1); CDC *dc=c->GetDC(); RECT rt; CBrush br; br.CreateSolidBrush(RGB(Red,Green,Blue)); c->GetClientRect(&rt); dc->SelectObject(&br); dc->FillRect(&rt,&br); }

106

Để xử lý thanh cuộn chúng ta phải ánh xạ xử lý thông điệp WM_HSCROLL trong lớp CMyDialog, tùy theo giá trị tham số hàm ánh xạ để xác định vị trí mới của thanh cuộn và đặt ví trị con chạy mới đó. Hàm OnInitDialog() của lớp CMyDialog nạp chồng lớp cơ sở CDialog để thiết lập các giá trị ban đầu cho các điều khiển trên hộp thoại. Kết quả chương trình như sau:

3.5.5. Sử dụng hàm DDX và DDV trên các điều khiển
Các điều khiển trên hộp thoại cho phép người dùng tác động để thực hiện nhập dữ liệu, hoặc do chương trình thông báo kết quả. Tuy nhiên dữ liệu trên các điều khiển không được lưu trực tiếp vào biến nhớ, hơn nữa chúng ta không thể kiểm tra dữ liệu trực tiếp khi nhập. Lập trình MFC cung cấp một kỹ thuật thực hiện việc này gọi là các hàm DDX - Dialog Data Exchange và DDV - Dialog Data Validation. Hàm DDX có thể thực hiện trên nhiều điều khiển còn hàm DDV chỉ thực hiện trên một số điều khiển. Hàm DDX và DDV thực hiện trên các biến nhớ phải được định nghĩa trong lớp hộp thoại.

107

Một số hàm DDX và DDV có thể thực hiện,
Tên hàm DDX_Check DDX_Text DDX_Radio DDX_Scroll DDV_MaxChars DDV_MinMaxDouble DDV_MinMaxDWord DDV_MinMaxInt DDV_MinMaxLong DDV_MinMaxUnsigned Ý nghĩa Qu n lý bi n c a checkbox Qu n lý bi n xâu c a textbox Qu n lý bi n c a nút ch n radio Qu n lý bi n c a thanh cu n Ki m tra đ dài xâu Ki m tra giá tr double Ki m tra giá tr DWORD Ki m tra giá tr integer Ki m tra giá tr long integer Ki m tra giá tr unsigned integer

Để sử dụng các hàm DDX và DDV chúng ta phải nạp chồng hàm DoDataExchange() trong lớp hộp thoại, nó là một thành viên hàm ảo được khai báo theo mẫu sau:
void CWnd :: DoDataExchange ( CDataExchange *Data );

Trong đó Data là dữ liệu được sử dụng bởi hàm DDX và DDV, tham số này có kiểu CDataExchange và chúng ta không thể sử dụng trực tiếp nó. Trong hàm nạp chồng này ngoài việc gọi hàm cơ sở chúng ta sẽ gọi các hàm DDX và DDV mong muốn. Mỗi hàm DDX và DDV sẽ liên kết một thành viên biến nhớ của lớp hộp thoại với một điều khiển trên đó, thực hiện qua mẫu khai báo của hàm, ví dụ hai hàm sau:
void AFXAPI DDX_Check(CDataExchange *Data,int ID,int &var); void AFXAPI DDV_MaxChars( CDataExchange* pDX, CString const& value, int nChars );

Ví dụ 3.11
Lập trình tạo hộp thoại có hai điều khiển Checkbox và Textbox, thực hiện sử dụng hàm DDX và DDV để kiểm tra và lưu dữ liệu vào biến nhớ tương ứng. Khai báo hộp thoại trong tệp RC như sau:
108

IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 228, 106 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Vi du 11 - Chuong 03" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,87,85,50,14 CONTROL "Check1",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,61,20,74,16 EDITTEXT IDC_EDIT1,94,48,106,16,ES_AUTOHSCROLL END

Trong chương trình sẽ nạp chồng hàm OnOk() để hiện nội dung các biến nhớ sau khi nhập xong. Tệp nội dung chương trình sẽ là:
#include<afxwin.h> #include<math.h> #include"resource.h" class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyDialog : public CDialog { private: int chk; char txt[15]; public: CMyDialog(UINT ID, CWnd *owner=NULL) : CDialog(ID,owner) {} BOOL OnInitDialog(); void DoDataExchange(CDataExchange *pDX); void OnOk(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; int check; char text[15]; BEGIN_MESSAGE_MAP( CMyDialog , CDialog ) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() {

109

m_pMainWnd = new CMyDialog(IDD_DIALOG1,NULL); ((CMyDialog *)m_pMainWnd)->DoModal(); return TRUE; } BOOL CMyDialog::OnInitDialog() { CEdit *ed=(CEdit*)GetDlgItem(IDC_EDIT1); ed->LimitText(10); chk=0; txt[0]='\0'; return TRUE; } void CMyDialog::DoDataExchange(CDataExchange *pDX) { CDialog::DoDataExchange(pDX); DDX_Check(pDX,IDC_CHECK1,chk); DDX_Text(pDX,IDC_EDIT1,txt,15); } void CMyDialog::OnOk() { CDialog::OnOK(); check = chk; sprintf(text,"%s",txt); EndDialog(IDOK); }

3.6. Công cụ tạo các tài nguyên của Visual C
Các tài nguyên được trình bày trong chương này có thể tạo bằng hai cách, theo khai báo hoặc sử dụng công cụ của Visual C. Với cách khai báo như đã được thực hiện ở phần trước, phần này trình bày cách tạo sử dụng công của VisualC với các bước như sau: Bước 1: Tạo tệp tài nguyên (Resource files) Chọn chức năng File New chọn thẻ [Files] chọn mục [Resource Script] nhập tên tệp tài nguyên tương ứng vào ô [File name] đánh dấu vào ô [Add to project] Ok.

110

Bước 2: Thêm các tài nguyên Mở tệp tài nguyên đã tạo ở bước 1, bấm chuột phải vào tệp tài nguyên sau khi mở chọn chức năng Insert,

chọn kiểu tài nguyên cần thêm và chọn New. Bước 3: Thực hiện chỉnh sửa tài nguyên theo giao diện tương ứng - Tạo thực đơn

111

- Tạo ảnh cursor

- Tạo ảnh icon

112

- Tạo ảnh bitmap

- Tạo hộp thoại

3.7. Bài tập
Bài tập 3.1
Lập trình tạo thực đơn trên cửa sổ theo cấu trúc sau:

113

Bài tập 3.2
Lập trình tạo một thực đơn ngữ cảnh và hiện khi bấm chuột phải lên cửa sổ, thực đơn có cấu trúc sau:

Bài tập 3.3
Lập trình tạo một biểu tượng icon, một con trỏ chuột cursor và đặt lên cửa sổ của chương trình.

Bài tập 3.4
Lập trình tạo một ảnh bitmap bất kỳ, cho hiện lên cửa sổ khi bấm chuột phải.

Bài tập 3.5
Lập trình tạo hộp thoại nhập các thông tin họ tên, năm sinh, quê quán, điểm thi của một học sinh.

Bài tập 3.6
Lập trình tạo hộp thoại bàn cờ 8x8 như sau:

Bài tập 3.7
Mở rộng bài 3.6 ở trên cho phép người sử dụng bấm chuột vào một ô sẽ hiện các dấu nhân X hoặc chữ O luân phiên nhau trên bàn cờ.

114

Chương 4 XỬ LÝ ĐỒ HỌA
4.1. Cơ chế đồ họa và hệ tọa độ cửa sổ
Windows là hệ điều hành có giao diện đồ họa, lập trình trên windows là phải làm việc với đồ họa trên mọi thành phần của chương trình. Thư viện các hàm đồ họa cung cấp cho người lập trình rất lớn và khá mềm dẻo, với khuôn khổ tài liệu này chúng tôi cung cấp một số cách làm việc đơn giản. Hệ tọa độ đồ họa trên cửa sổ được xác lập bởi gốc ở góc trái trên, trục ngang là chiều X tăng từ trái sang phải và trục dọc là chiều Y tăng từ trên xuống. Đơn vị chia trên trục X và Y là đơn vị lôgic, tương đương với điểm ảnh (pixels), tuy nhiên có thể thay đổi đơn vị lôgic này.

Trên cửa sổ đồ họa luôn tồn tại một điểm vẽ hiện thời, điểm này có thể thay đổi bởi các hàm vẽ. Lúc hiển thị cửa sổ điểm này đặt tại gốc 0,0 và nó không được hiển thị trên cửa sổ.

115

4.2. Nét bút và mẫu tô
Các thành phần hiện trên cửa sổ đều được thực hiện bằng phương pháp vẽ và tô các hình ảnh, có hai đối tượng quan trọng khi vẽ đồ họa là nét bút (pens) và mẫu tô (brushes). Nét bút là đối tượng tạo ra từ lớp CPen dùng để quy định nét vẽ cho các lệnh vẽ, mỗi đối tượng nét bút bao gồm màu sắc, độ rộng nét, kiểu nét liền, chấm, đứt quãng,... Mẫu tô là đối tượng tạo ra từ lớp CBrush dùng để quy định mẫu tô cho các lệnh tô nền hình vẽ, mỗi mẫu tô có kiểu tô, màu kiểu tô.

4.2.1. Lớp CPen và nét vẽ
CPen :: CPen( int nPenStyle, int nWidth, COLORREF crColor );

- Hàm tạo nét bút với nPenStyle là kiểu nét bút, nWidth là độ rộng, crColor là màu nét bút. Màu nét bút có thể dùng macro sau để thiết lập màu tùy ý:
COLORREF RGB( Red , Green , Blue )

Kiểu nét bút bao gồm các giá trị sau:
Giá tr PS_SOLID PS_DASH PS_DOT Ý nghĩa Nét li n Nét g ch Nét ch m Giá tr PS_DASHDOT PS_DASHDOTDOT PS_NULL Ý nghĩa G ch ch m G ch hai ch m Không th y

ho c s d ng hàm t o nét bút thành viên sau:
BOOL CPen::CreatePen( int nPenStyle, int nWidth, COLORREF crColor );

4.2.2. Lớp CBrush và tô nền
CBrush( int nIndex, COLORREF crColor ); CBrush( COLORREF crColor ); CBrush( CBitmap* pBitmap );

- Các hàm tạo trên để thiết lập mẫu tô với các tham số nIndex quy định kiểu tô, màu được quy định bởi crColor và pBitmap quy định mẫu tô bằng một ảnh bitmap.
116

Kiểu tô bao gồm các giá trị sau:
Giá tr HS_BDIAGONAL HS_CROSS HS_DIAGCROSS HS_FDIAGONAL HS_HORIZONTAL HS_VERTICAL Ý nghĩa Nghiêng xu ng 45o Ch th p Ch th p nghiêng Nghiêng lên 45o K ngang K d c

- Hàm tạo mẫu tô đặc khai báo theo mẫu sau
BOOL CBrush :: CreateSolidBrush( COLORREF crColor );

- Hàm tạo mẫu tô theo kiểu có sẵn
BOOL CBrush :: CreateHatchBrush( int Index, COLORREF crColor );

Trong đó tham số Index quy định kiểu tô cần tạo, gồm các giá trị như đã trình bày ở trên HS_BDIAGONAL, HS_HORIZONTAL,...

4.2.3. Hộp thoại chọn màu của hệ thống
Windows cung cấp hộp thoại dùng chung để chọn màu từ hệ thống, hộp thoại này được bao gói và xử lý bởi lớp CColorDialog trong một lớp của thư viện MFC, chúng ta phải tạo đối tượng từ lớp tương ứng và gọi hàm DoModal() sau đó xác định giá trị màu được chọn qua hàm thành viên. Hàm tạo khai báo đối tượng là:
CColorDialog( COLORREF clrInit = 0, DWORD dwFlags = 0, CWnd* pParentWnd = NULL );

Các tham số là màu khởi tạo, kiểu chọn và cửa sổ chứa. Hàm lấy màu đã chọn là:
COLORREF CColorDialog :: GetColor( ) const;

Ví dụ sau sẽ hiển thị hộp thoại với màu chọn ban đầu là trắng, sau đó màu được chọn lấy ra lưu vào biến m.

117

CColorDialog a( RGB(255,255,255) ); if ( a.DoModal() == IDOK ) m = a.GetColor();

Hàm DoModal() sẽ hiện hộp thoại màu để chọn, trả về giá trị IDOK nếu chúng ta bấm nút lệnh Ok trên hộp thoại đó, ngược lại hàm trả về IDCANCEL.

4.3. Lớp CDC và các hàm vẽ
4.3.1. Giới thiệu lớp CDC
Như đã trình bày trong chương trước để vẽ lên cửa sổ chúng ta sử dụng các lệnh vẽ thông qua đối tượng lớp CDC (class device context). Đối tượng CDC có tác dụng xác lập bộ nhớ trên máy ứng với phần cửa sổ sẽ hiện trên màn hình, mọi thao tác vẽ đều thực hiện trên phần bộ nhớ đó gọi là bộ nhớ màn hình cho cửa sổ. Khi tạo cửa sổ chúng ta có ngay phần này, để vẽ ta chỉ cần lấy đối tượng CDC qua hàm thành viên của cửa sổ như sau:
CDC* CWnd :: GetDC( );

Trong lớp CDC có các lệnh vẽ như sau: - Chọn nét bút và mẫu tô
CPen* CDC :: SelectObject( CPen* pPen ); CBrush* CDC :: SelectObject( CBrush* pBrush );

Hai hàm trên cần tham số là đối tượng nét bút và mẫu tô, trả lại nét bút hoặc mẫu tô đang có.

4.3.2. Một số lệnh vẽ cơ bản
- Vẽ một điểm ảnh
COLORREF CDC::SetPixel( int x, int y, COLORREF crColor );

Phải đưa vào tọa độ x,y điểm cần đặt và màu cho điểm đó. Giá trị trả về là màu cũ của điểm vẽ. - Vẽ đường thẳng
BOOL CDC::LineTo( int x, int y );

118

- Dịch chuyển điểm vẽ hiện thời (sẽ không nhìn thấy điểm này)
CPoint CDC::MoveTo( int x, int y );

Hàm trả về giá trị điểm kiểu CPoint, đó là lớp bao của cấu trúc điểm khai báo theo mẫu sau:
typedef struct tagPOINT { LONG LONG x; y;

} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;

- Vẽ hình cung
BOOL Arc( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ); BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd );

Cung tức là một phần của elíp hoặc hình tròn, để vẽ hình cung chúng ta phải xác định các tham số gồm một hình chữ nhật ngoại tiếp elip hoặc hình tròn của cung đó, điểm xác định đường thẳng nối với tâm tạo thành góc chắn cung thứ nhất, điểm xác định đường thẳng nối với tâm tạo thành góc chắn cung thứ hai. Xác định theo hình vẽ sau:

Ngoài ra có lệnh vẽ cung tròn theo góc chắn cung như sau:
BOOL AngleArc( int x, int y, int nR, float fS, float fE );

119

Trong đó x,y là tâm của hình tròn chứa cung, nR là bán kính, fS là góc bắt đầu vẽ, fE là góc chắn cung cần vẽ.

- Vẽ hình chữ nhật
BOOL CDC::Rectangle( int x1, int y1, int x2, int y2 ); BOOL CDC::Rectangle( LPCRECT lpRect );

Vẽ hình chữ nhật được xác định tọa độ hai điểm ở hai góc đối diện, góc trái trên là x1,y1 và góc phải dưới là x2,y2. Có thể xác định bằng kiểu RECT ở mẫu 2. Hình chữ nhật sẽ được tự động tô theo kiểu tô đã chọn sẵn. Nếu cần vẽ hình chữ nhật có vát góc bằng cung elíp ta sử dụng lệnh sau:
BOOL RoundRect( int x1, int y1, int x2, int y2, int x3, int y3 ); BOOL RoundRect( LPCRECT lpRect, POINT point );

Với góc được vát xác định bằng độ rộng và độ cao của elíp cần thực hiện, đó là giá trị x3 và y3 hoặc tham số point ở mẫu 2. Ví dụ với mẫu vẽ bằng 2 lệnh sau:
dc->Rectangle(50,50,450,250); dc->RoundRect(50,50,450,250,300,150);

120

- V hình elíp
BOOL CDC::Ellipse( int x1, int y1, int x2, int y2 ); BOOL CDC::Ellipse( LPCRECT lpRect );

Trong đó x1,y1 và x2,y2 là tọa độ hai điểm xác định hình chữ nhật ngoại tiếp elíp cần vẽ, hoặc xác định qua cấu trúc kiểu RECT ở mẫu 2. Hình elíp sẽ được tô theo mẫu tô hiện thời. - Vẽ hình quạt
BOOL Pie( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ); BOOL Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd );

Trong đó x1,y1 và x2,y2 xác định tọa độ hình chữ nhật ngoại tiếp elíp chứa hình quạt, x3,y3 xác định điểm nối tâm của đường góc chắn cung thứ nhất và x4,y4 là góc chắn cung thứ 2 (các tham số giống lệnh vẽ cung Arc). Ở đây vẽ hình quạt nên sẽ có thêm hai đường góc chắn cung đồng thời được tô nền theo mẫu tô hiện thời.

Ví dụ 4.1
Lập trình cho phép chọn chức năng trên thực đơn để vẽ các hình cơ bản như các lệnh trình bày ở trên, cho phép chọn màu vẽ, màu tô và kiểu tô. Khai báo các hằng cung cấp cho tệp tài nguyên:
#define IDR_MENU1 101 #define ID_THOAT 40001 #define ID_CHONMAUVE_DO 40002 #define ID_CHONMAUVE_XANH 40003 #define ID_CHONMAUVE_VANG 40004 #define ID_CHONMAUVE_TIM 40005 #define ID_CHONMAUTO_DO 40006 #define ID_CHONMAUTO_XANH 40007 #define ID_CHONMAUTO_VANG 40008 #define ID_CHONMAUTO_TIM 40009 #define ID_CHONHINHVE_CHUNHAT 40010 #define ID_CHONHINHVE_TRON 40011 #define ID_CHONHINHVE_ELIP 40012 #define ID_CHONHINHVE_GOCTRON 40013 #define ID_CHONHINHVE_QUAT 40014 #define ID_CHONKIEUTO_DAC 40015

121

#define ID_CHONKIEUTO_GACHNGANG 40016 #define ID_CHONKIEUTO_GACHDOC 40017 #define ID_CHONKIEUTO_LUOIOVUONG 40018 #define ID_CHONKIEUTO_NGHIENG 40019

Khai báo tài nguyên thực đơn trong tệp RC như sau:
IDR_MENU1 MENU DISCARDABLE BEGIN POPUP "Chon mau ve" BEGIN MENUITEM "Do", ID_CHONMAUVE_DO MENUITEM "Xanh", ID_CHONMAUVE_XANH MENUITEM "Vang", ID_CHONMAUVE_VANG MENUITEM "Tim", ID_CHONMAUVE_TIM END POPUP "Chon mau to" BEGIN MENUITEM "Do", ID_CHONMAUTO_DO MENUITEM "Xanh", ID_CHONMAUTO_XANH MENUITEM "Vang", ID_CHONMAUTO_VANG MENUITEM "Tim", ID_CHONMAUTO_TIM END POPUP "Chon hinh ve" BEGIN MENUITEM "Chu nhat", ID_CHONHINHVE_CHUNHAT MENUITEM "Tron", ID_CHONHINHVE_TRON MENUITEM "Elip", ID_CHONHINHVE_ELIP MENUITEM "Goc tron", ID_CHONHINHVE_GOCTRON MENUITEM "Quat", ID_CHONHINHVE_QUAT END POPUP "Chon kieu to" BEGIN MENUITEM "Dac", ID_CHONKIEUTO_DAC MENUITEM "Gach ngang", ID_CHONKIEUTO_GACHNGANG MENUITEM "Gach doc", ID_CHONKIEUTO_GACHDOC MENUITEM "Luoi o vuong", ID_CHONKIEUTO_LUOIOVUONG MENUITEM "Nghieng", ID_CHONKIEUTO_NGHIENG END MENUITEM "Thoat", ID_THOAT END

122

Nội dung chương trình như sau:
#include<afxwin.h> #include"resource.h" class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { private: COLORREF mauve,mauto; int kieuto; public: CMyWin(); void OnChonmauve(UINT id); void OnChonmauto(UINT id); void OnChonhinhve(UINT id); void OnChonkieuto(UINT id); void OnThoat(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_COMMAND_RANGE(ID_CHONMAUVE_DO,ID_CHONMAUVE_TIM,OnChonmauv e) ON_COMMAND_RANGE(ID_CHONMAUTO_DO,ID_CHONMAUTO_TIM,OnChonmaut o) ON_COMMAND_RANGE(ID_CHONHINHVE_CHUNHAT,ID_CHONHINHVE_QUAT,On Chonhinhve) ON_COMMAND_RANGE(ID_CHONKIEUTO_DAC,ID_CHONKIEUTO_NGHIENG,OnC honkieuto) ON_COMMAND(ID_THOAT,OnThoat) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; }

123

CMyWin::CMyWin() { CMenu mn; Create(NULL,"Vi du 01 - Chuong 04"); mn.LoadMenu(IDR_MENU1); SetMenu(&mn); mauto=RGB(255,255,255); kieuto=0; mauve=RGB(0,0,0); } void CMyWin::OnChonmauve(UINT id) { switch (id) { case ID_CHONMAUVE_DO: mauve=RGB(255,0,0);break; case ID_CHONMAUVE_XANH: mauve=RGB(0,0,255); break; case ID_CHONMAUVE_VANG: mauve=RGB(255,255,0);break; case ID_CHONMAUVE_TIM: mauve=RGB(255,0,255);break; } } void CMyWin::OnChonmauto(UINT id) { switch (id) { case ID_CHONMAUTO_DO: mauto = RGB(255,0,0); break; case ID_CHONMAUTO_XANH: mauto = RGB(0,0,255); break; case ID_CHONMAUTO_VANG: mauto = RGB(255,255,0);break; case ID_CHONMAUTO_TIM: mauto = RGB(255,0,255);break; } } void CMyWin::OnChonhinhve(UINT id) { CBrush b;CPen p; CDC *dc=GetDC(); p.CreatePen(PS_SOLID,2,mauve); if (kieuto==0) b.CreateSolidBrush(mauto); else b.CreateHatchBrush(kieuto,mauto); dc->SelectObject(p); dc->SelectObject(b); switch (id) { case ID_CHONHINHVE_CHUNHAT:

124

dc->Rectangle(10,10,100,50); break; case ID_CHONHINHVE_TRON: dc->MoveTo(350,100); dc->AngleArc(300,100,50,0,360); break; case ID_CHONHINHVE_ELIP: dc->Ellipse(10,200,150,300); break; case ID_CHONHINHVE_GOCTRON: dc->RoundRect(250,200,400,300,100,70); break; case ID_CHONHINHVE_QUAT: dc->Pie(450,50,550,150,0,0,0,500); break; } b.DeleteObject(); p.DeleteObject(); } void CMyWin::OnChonkieuto(UINT id) { switch (id) { case ID_CHONKIEUTO_DAC: kieuto=0; break; case ID_CHONKIEUTO_GACHNGANG: kieuto=HS_HORIZONTAL; break; case ID_CHONKIEUTO_GACHDOC: kieuto=HS_VERTICAL; break; case ID_CHONKIEUTO_LUOIOVUONG: kieuto=HS_CROSS; break; case ID_CHONKIEUTO_NGHIENG: kieuto=HS_FDIAGONAL; break; } } // Hàm thoát kh i chương trình // void CMyWin::OnThoat() { DestroyWindow(); }

125

Trong chương trình này có ánh xạ xử lý thông điệp chọn thực đơn theo nhóm, sử dụng macro ON_COMMAND_RANGE() trong lớp cửa sổ CMyWin. Khai báo 3 thành viên biến nhớ trong lớp CMyWin để lưu các giá trị màu vẽ, màu tô, kiểu tô đã chọn cung cấp cho lệnh vẽ. Hàm thành viên xử lý thông điệp chọn các hình vẽ ta sử dụng hai đối tượng CPen và CBrush, tiếp theo tạo nét bút và mẫu tô tương ứng rồi đặt vào cửa sổ qua lệnh SelectObject() của lớp CDC. Sau khi thực hiện xong các lệnh vẽ ta phải xóa nét bút và mẫu tô đó để giải phóng bộ nhớ cho máy. Kết quả chương trình sau khi chọn một số chức năng:

4.4. Chế độ ánh xạ và khung nhìn trên cửa sổ
Các lệnh vẽ qua lớp CDC đều thực hiện trên các đơn vị tọa độ dạng lôgíc, mặc định đơn vị lôgíc tương đương với đơn vị vật lý (pixels). Tuy nhiên chúng ta có thể thay đổi tỷ lệ này cho phù hợp quá trình vẽ, ta gọi tỷ lệ này là chế độ ánh xạ (mapping modes). Ngoài ra trên cửa sổ theo mặc định sẽ có hệ trục tọa độ có gốc ở góc trái trên, chiều X là trái sang phải, chiều Y là trên xuống dưới. Các lệnh vẽ chỉ
126

thực hiện trên tọa độ nằm trong vùng cửa sổ nhìn thấy gọi là khung nhìn (viewport). Chúng ta có thể thay đổi kích thước khung nhìn bằng cách định nghĩa chiều rộng và chiều cao của cửa sổ, sau khi có khung nhìn mới thì các lệnh vẽ chỉ thực hiện được trong khung nhìn đó. Chế độ ánh xạ và khung nhìn được thực hiện qua lớp CDC của cửa sổ. - Đặt chế độ ánh xạ đơn vị vẽ
virtual int CDC::SetMapMode( int nMapMode );

Trong đó nMapMode quy định chế độ cần đặt bao gồm các giá trị sau:
Giá tr MM_HIENGLISH MM_HIMETRIC MM_ISOTROPIC MM_ANISOTROPIC MM_LOENGLISH MM_LOMETRIC MM_TEXT MM_TWIPS Ý nghĩa M t đơn v lôgic b ng 0.001 inch, tr c x dương sang ph i, tr c y âm xu ng dư i M t đơn v lôgic b ng 0.01 mm, tr c x dương sang ph i, tr c y âm xu ng dư i Đơn v lôgic b ng v i đơn v ngư i dùng gi ng nhau c hai tr c X và Y Theo đơn v ngư i dùng đ t nhưng khác nhau gi a hai tr c X và Y M t đơn v lôgic b ng 0.1 mm, tr c x dương sang ph i, tr c y âm xu ng dư i M t đơn v lôgic b ng 0.01 inch, tr c x dương sang ph i, tr c y âm xu ng dư i M t đơn v lôgic b ng m t đơn v v t lý pixels B ng 20 đi m máy in (1/1440 inch)

Hàm này trả về chế độ ánh xạ trước đó nếu đặt thành công, ngược lại trả về giá trị 0. Chúng ta thấy mặc định là chế độ MM_TEXT. - Định nghĩa cửa sổ mở rộng Chọn chế độ ánh xạ đơn vị là MM_ISOTROPIC cho phép chúng ta định nghĩa lại kích thước cửa sổ theo đơn vị lôgíc. Để định nghĩa độ dài trục X và độ cao trục Y cho cửa sổ ta sử dụng hàm theo mẫu sau:
virtual CSize CDC::SetWindowExt( int cx, int cy ); virtual CSize CDC::SetWindowExt( SIZE size );

127

Giá trị cx và cy quy định độ rộng và độ cao của hai trục X, Y tương ứng. Khi đó đơn vị vẽ lôgíc trên cửa sổ có độ chia theo hai trục ứng với giá trị cx và cy này. Hàm trả về kích thước trước đó của cửa sổ dưới kiểu CSize của cấu trúc SIZE như sau:
typedef struct tagSIZE { LONG cx; LONG cy; } SIZE;

Chú ý: rằng khi thay đổi kích thước cửa sổ bằng lệnh trên sẽ không làm thay đổi kích thước vật lý cửa sổ, chỉ thay đổi tỷ lệ giữa đơn vị lôgíc với đơn vị vật lý là pixels. Ví dụ cửa sổ có kích thước vật lý là 200x100, sau khi đặt kích thước lôgíc là 400x160 thì tỷ lệ sẽ là 200/400=0.5 và 100/160=0.625 (mỗi đơn vị lôgíc ứng với 0.5 pixel theo chiều ngang và 0.625 theo chiều dọc). - Định nghĩa khung nhìn trên cửa sổ
virtual virtual CSize CSize CDC::SetViewportExt( int cx, int cy ); CDC::SetViewportExt( SIZE size );

Giá trị cx và cy quy định độ rộng và độ cao của khung nhìn cần đặt, hoặc bởi tham số size theo mẫu 2. Hàm trả về giá trị khung nhìn trước đó. Lệnh này chỉ thực hiện được khi chế độ ánh xạ kiểu người dùng là MM_ISOTROPIC. Chúng ta có thể định nghĩa khung nhìn với kích thước bất kỳ và mọi lệnh vẽ chỉ thực hiện được với tọa độ trong khung nhìn đó. Trường hợp muốn lệnh vẽ chỉ thực hiện một phần trên cửa sổ thì thực hiện lệnh này để xác lập, lệnh này phải thực hiện sau lệnh đặt chế độ ánh xạ đơn vị. - Đặt gốc tọa độ lôgíc cho khung nhìn Mặc định gốc tọa độ lôgíc ở góc trái trên, tuy nhiên chúng ta có thể thay đổi gốc này bởi lệnh sau:
virtual CPoint CDC::SetViewportOrg( int x, int y );

128

virtual

CPoint

CDC::SetViewportOrg( POINT point );

Tọa độ gốc mới cần đặt quy định bởi tham số x và y hoặc giá trị point theo mẫu 2. Hàm trả về tọa độ gốc trước đó qua đối tượng CPoint. Chú ý: Các lệnh SetWindowExt() và SetViewportExt() chỉ có tác dụng khi chúng ta đặt chế độ ánh xạ là MM_ISOTROPIC hoặc MM_ANISOTROPIC, vì vậy sau khi đặt chế độ này chúng ta phải gọi hai hàm trên mới thực hiện các lệnh vẽ được. - Chuyển đổi giữa đơn vị lôgíc và vật lý Mặc định mọi lệnh vẽ đều thực hiện theo đơn vị lôgíc, tuy nhiên khi thực hiện trên thiết bị vật lý màn hình thì máy sẽ tự động chuyển thành đơn vị pixels. Trong một số trường hợp chúng ta muốn chuyển đổi giữa hai đơn vị này sử dụng hai mẫu hàm sau:
void void void void void void CDC::DPtoLP( LPPOINT lpPoints, int nCount = 1 ) const; CDC::DPtoLP( LPRECT lpRect ) const; CDC::DPtoLP( LPSIZE lpSize ) const; CDC::LPtoDP( LPPOINT lpPoints, int nCount = 1 ) const; CDC::LPtoDP( LPRECT lpRect ) const; CDC::LPtoDP( LPSIZE lpSize ) const;

Trong đó các tham số lpPoints, lpRect, lpSize xác định tọa độ điểm, tọa độ khung, kích thước cần chuyển. Tham số nCount cho biết cần chuyển bao nhiêu điểm trong tham số lpPoints. Các hàm DPtoLP() thực hiện chuyển từ đơn vị vật lý sang đơn vị lôgic (Device Point to Logical Point), ngược lại các hàm LPtoDP() chuyển từ lôgíc sang vật lý.

Ví dụ 4.2
Lập trình vẽ một hình chữ nhật, ở 4 góc vẽ 4 hình tròn nhỏ. Sau đó cho phép thay đổi ánh xạ đơn vị, thay đổi khung nhìn, thay đổi gốc tọa độ. Ta sử dụng một hộp thoại để chọn các tham số thay đổi theo yêu cầu ở trên. Các tài nguyên thực đơn, hộp thoại khai báo như sau:

129

// Dialog IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 249, 98 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Chon tham so de ve" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,63,69,50,14 PUSHBUTTON "Cancel",IDCANCEL,129,69,50,14 LTEXT "Che do anh xa don vi ve",IDC_STATIC,17,14,86,11 COMBOBOX IDC_COMBO1,104,14,95,20,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP LTEXT "Chon khung nhin CX=",IDC_STATIC,16,29,73,11 EDITTEXT IDC_EDIT1,89,29,26,12,ES_AUTOHSCROLL LTEXT "CY=",IDC_STATIC,121,29,16,11 EDITTEXT IDC_EDIT2,139,29,26,12,ES_AUTOHSCROLL LTEXT "Goc toa do X=",IDC_STATIC,16,43,73,11 EDITTEXT IDC_EDIT3,89,43,26,12,ES_AUTOHSCROLL LTEXT "Y=",IDC_STATIC,121,43,16,11 EDITTEXT IDC_EDIT4,139,43,26,12,ES_AUTOHSCROLL END // Menu IDR_MENU1 MENU DISCARDABLE BEGIN MENUITEM "Chon tham so", ID_CHONTHAMSO MENUITEM "Ve hinh", ID_VEHINH MENUITEM "Thoat", ID_THOAT END

Nội dung chương trình như sau:
#include<afxwin.h> #include"resource.h" char mapmodes[][30]={ "MM_HIMETRIC","MM_ISOTROPIC", "MM_LOENGLISH","MM_TEXT","MM_TWIPS"}; class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: int MM; int CX,CY,GX,GY;

130

public: CMyWin(); void OnChonthamso(); void OnVehinh(); void OnThoat(); DECLARE_MESSAGE_MAP() }; class CMyDialog : public CDialog { public: CMyDialog(UINT id,CWnd *ow) : CDialog(id,ow) {} BOOL OnInitDialog(); void OnOK(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_COMMAND(ID_CHONTHAMSO,OnChonthamso) ON_COMMAND(ID_VEHINH,OnVehinh) ON_COMMAND(ID_THOAT,OnThoat) END_MESSAGE_MAP() BEGIN_MESSAGE_MAP( CMyDialog , CDialog ) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } //implement of class for main's window CMyWin::CMyWin() { CMenu mn; Create(NULL,"Vi du 02 - Chuong 04"); mn.LoadMenu(IDR_MENU1); SetMenu(&mn); MM=3; RECT rt; GetClientRect(&rt); CX=rt.right-rt.left; CY=rt.bottom-rt.top;

131

GX=0;GY=0; } void CMyWin::OnChonthamso() { CMyDialog d(IDD_DIALOG1,this); d.DoModal(); } void CMyWin::OnVehinh() { CDC *dc=GetDC(); switch (MM) { case 0: dc->SetMapMode(MM_HIMETRIC);break; case 1: dc->SetMapMode(MM_ISOTROPIC);break; case 2: dc->SetMapMode(MM_LOENGLISH);break; case 3: dc->SetMapMode(MM_TEXT);break; case 4: dc->SetMapMode(MM_TWIPS);break; default: dc->SetMapMode(MM_TEXT);break; } dc->SetMapMode(MM); if (MM==1) dc->SetWindowExt(CX,CY); else dc->SetViewportExt(CX,CY); dc->SetViewportOrg(GX,GY); CPen p; p.CreatePen(PS_SOLID,3,RGB(255,0,0)); CBrush b; b.CreateSolidBrush(RGB(0,0,255)); dc->SelectObject(&p); dc->SelectObject(&b); dc->Rectangle(-50,-50,250,150); dc->MoveTo(-20,-50); dc->AngleArc(-50,-50,30,0,360); dc->MoveTo(280,-50); dc->AngleArc(250,-50,30,0,360); dc->MoveTo(-20,150); dc->AngleArc(-50,150,30,0,360); dc->MoveTo(280,150); dc->AngleArc(250,150,30,0,360); } void CMyWin::OnThoat() { DestroyWindow(); } //implement of class for dialog BOOL CMyDialog::OnInitDialog() { CMyWin *mw = (CMyWin*)GetParent(); CComboBox *cb = (CComboBox*)GetDlgItem(IDC_COMBO1); for (int i=0;i<5;i++) cb->AddString(mapmodes[i]);

132

cb->SetCurSel(mw->MM); char s[20]; CEdit *ed; ed = (CEdit*)GetDlgItem(IDC_EDIT1); sprintf(s,"%d",mw->CX); ed->SetWindowText(s); ed = (CEdit*)GetDlgItem(IDC_EDIT2); sprintf(s,"%d",mw->CY); ed->SetWindowText(s); ed = (CEdit*)GetDlgItem(IDC_EDIT3); sprintf(s,"%d",mw->GX); ed->SetWindowText(s); ed = (CEdit*)GetDlgItem(IDC_EDIT4); sprintf(s,"%d",mw->GY); ed->SetWindowText(s); return TRUE; } void CMyDialog::OnOK() { CMyWin *mw = (CMyWin*)GetParent(); CComboBox *cb = (CComboBox*)GetDlgItem(IDC_COMBO1); mw->MM = cb->GetCurSel(); char s[20]; CEdit *ed; ed = (CEdit*)GetDlgItem(IDC_EDIT1); ed->GetWindowText(s,20); mw->CX=atoi(s); ed = (CEdit*)GetDlgItem(IDC_EDIT2); ed->GetWindowText(s,20); mw->CY=atoi(s); ed = (CEdit*)GetDlgItem(IDC_EDIT3); ed->GetWindowText(s,20); mw->GX=atoi(s); ed = (CEdit*)GetDlgItem(IDC_EDIT4); ed->GetWindowText(s,20); mw->GY=atoi(s); CDialog::OnOK(); }

Hộp thoại cho phép chọn chế độ ánh xạ đơn vị, nhập kích thước khung nhìn và gốc tọa độ. Nếu chọn chế độ ánh xạ là MM_ISOTROPIC thì kích thước khung nhìn thành kích thước lôgíc cửa sổ. Ba tham số này lưu trong các thành viên biến nhớ của lớp CMyWin, do vậy trong lớp CMyDialog phải sử dụng lệnh GetParent() trả về cửa sổ chương trình để truy nhập các thành viên đó.

133

Nếu chọn tham số như sau:

Kết quả chương trình sẽ là:

Ví dụ 4.3
Lập trình vẽ đồ thị hàm số sin lên cửa sổ, cho phép phóng to thu nhỏ bằng cách bấm chuột trái và phải. Nội dung chương trình sẽ là:
#include<afxwin.h> #include<math.h> class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd

134

{ private: double Tyle,DX; public: CMyWin(); afx_msg void OnPaint(); afx_msg void OnLButtonDown(UINT nFlags,CPoint point); afx_msg void OnRButtonDown(UINT nFlags,CPoint point); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_PAINT() ON_WM_LBUTTONDOWN() ON_WM_RBUTTONDOWN() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 03 - Chuong 04"); Tyle=5; DX=1; } void CMyWin::OnPaint() { CDC *dc = GetDC(); CPen p; p.CreatePen(PS_SOLID,1,RGB(255,0,0)); int GX,GY; RECT rt; GetClientRect(&rt); GX=(rt.right-rt.left)/2; GY=(rt.bottom-rt.top)/2; dc->SetViewportOrg(GX,GY); dc->SelectObject(&p);

135

dc->MoveTo(0,GY); dc->LineTo(0,-GY); dc->LineTo(-5,-GY+7); dc->MoveTo(0,-GY); dc->LineTo(5,-GY+7); dc->MoveTo(-GX,0); dc->LineTo(GX,0); dc->LineTo(GX-5,7); dc->MoveTo(GX,0); dc->LineTo(GX-5,-7); p.DeleteObject(); p.CreatePen(PS_SOLID,1,RGB(0,0,255)); dc->SelectObject(&p); double x=-GX; dc->MoveTo(x*Tyle,-sin(x)*Tyle); while (x<GX) { x+=DX; dc->LineTo(x*Tyle,-sin(x)*Tyle); } p.DeleteObject(); } void CMyWin::OnLButtonDown(UINT nFlags,CPoint point) { Tyle+=0.5; DX=DX*0.9; Invalidate(); } void CMyWin::OnRButtonDown(UINT nFlags,CPoint point) { Tyle-=0.5; DX=DX*1.1; Invalidate(); }

Trong chương trình này có sử dụng thông điệp WM_PAINT để vẽ hình lên cửa sổ, thông điệp được sinh ra khi có sự tác động thay đổi trên cửa sổ và chúng ta sẽ vẽ lại đồ thị sin. Hàm xử lý thông điệp là OnPaint() trong lớp CMyWin. Lớp CMyWin có thêm 2 thành viên Tyle và DX quy định tỷ lệ vẽ và bước tăng của giá trị x khi vẽ đồ thị. Hai giá trị này sẽ thay đổi ngược chiều nhau trong 2 hàm xử lý thông điệp WM_LBUTTONDOWN và WM_RBUTTONDOWN tương ứng là OnLButtonDown() và OnRButtonDown().

136

Kết quả khi thu nhỏ đồ thị sin:

Kết quả khi phóng to:

4.5. Xử lý đồ họa cao cấp và cửa sổ ảo
Các lệnh vẽ lên cửa sổ qua CDC sẽ thực hiện trực tiếp trên cửa sổ và chúng ta có thể thực hiện các lệnh vẽ này ở bất kỳ thời điểm sự kiện nào trên cửa sổ. Tuy nhiên mọi sự thay đổi của cửa sổ sẽ làm mất các hình vẽ trên đó bởi khi đó thông điệp WM_PAINT được gửi tới và thực hiện hàm OnPaint(). Có hai phương pháp để khôi phục lại các hình vẽ trên cửa sổ đó là: thực hiện mọi thao tác vẽ vào trong hàm OnPaint() (như ví dụ ở trên) hoặc sử dụng cửa sổ ảo (virtual window). Cửa sổ ảo là sử dụng một vùng bộ nhớ tương ứng với cửa sổ hiển thị, mọi thao tác vẽ được thực hiện trên vùng bộ nhớ đó và chép vào cửa sổ khi cần (thường là trong hàm OnPaint()). Các bước thực hiện tạo cửa sổ ảo như sau:
137

Bước 1: Khai báo các biến nhớ
CDC m_memDC; CBitmap m_bmp; CBrush m_bkbrush;

Đối tượng m_memDC kiểu CDC lưu ngữ cảnh thiết bị của cửa sổ ảo cần tạo, m_bmp kiểu CBitmap lưu ảnh bitmap cho cửa sổ ảo, m_bkbrush kiểu CBrush quy định kiểu tô cho cửa sổ ảo. Bước 2: Tạo ảnh bitmap cho việc lưu cửa sổ ảo
maxX = GetSystemMetrics(SM_CXSCREEN); maxY = GetSystemMetrics(SM_CYSCREEN); CClientDC DC(this); m_memDC.CreateCompatibleDC(&DC); m_bmp.CreateCompatibleBitmap(&DC, maxX, maxY); m_memDC.SelectObject(&m_bmp);

Hai lệnh đầu xác định độ lớn của màn hình theo chiều ngang và dọc lưu vào hai biến maxX và maxY, hai giá trị này cung cấp cho lệnh tạo ảnh bitmap sau đó. Lệnh CClientDC DC(this) phải thực hiện trong lớp cửa sổ cần tạo cửa sổ ảo tương ứng để tạo đối tượng DC thuộc lớp CClientDC, với tham số hàm tạo là this cho biết xác định DC của cửa sổ hiện hành. Lệnh tạo ngữ cảnh thiết bị cửa sổ ảo thực hiện theo mẫu sau:
virtual BOOL CDC::CreateCompatibleDC( CDC* pDC );

Vì cửa sổ ảo luôn gắn với một cửa sổ thật nên lệnh tạo này cần phải xác định một CDC của cửa sổ. Lệnh tạo ảnh bitmap ứng với cửa sổ ảo có mẫu sau:
BOOL CreateCompatibleBitmap( CDC* pDC, int nWidth, int nHeight );

Tham số pDC quy định ngữ cảnh thiết bị cửa sổ thật, nWidth và nHeight quy định kích thước độ rộng và chiều cao để tạo bitmap, kích thước này tính bằng pixels. Chúng ta đưa vào hai giá trị maxX và maxY như trên sẽ tạo ảnh bitmap có kích thước tối đa bằng cả màn hình.

138

Cuối cùng lệnh đặt ảnh bitmap vào trong ngữ cảnh thiết bị cửa sổ ảo như sau:
CBitmap* CDC :: SelectObject( CBitmap* pBitmap );

Hàm trả về giá trị ảnh bitmap trước đó, tham số pBitmap quy định ảnh bitmap mới cần đặt cho ngữ cảnh thiết bị. Bước 3: Tạo nền cho cửa sổ ảo
m_bkbrush.CreateStockObject(WHITE_BRUSH); m_memDC.SelectObject(&m_bkbrush); m_memDC.PatBlt(0,0,maxX,maxY,PATCOPY);

Lệnh đầu để tạo một mẫu nền tô màu trắng, sử dụng hàm tạo các đối tượng dạng GDI (graphic device interface) như CPen, CBrush, CFont,... đã được định nghĩa trong hệ thống. Mẫu khai báo hàm như sau:
BOOL CGdiObject :: CreateStockObject( int nIndex );

Trong đó tham số nIndex quy định kiểu đối tượng sẽ tạo ra như WHITE_BRUSH, BLACK_PEN, BLACK_BRUSH, SYSTEM_FONT,... Sau khi tạo được màu nền tô chúng ta đặt vào ngữ cảnh thiết bị cửa sổ ảo bằng lệnh SelectObject(). Cuối cùng tạo mẫu bit trên ngữ cảnh thiết bị, mẫu tạo ra được kết hợp giữa mẫu nền tô đã chọn và mẫu có sẵn trên ngữ cảnh đó.
BOOL PatBlt( int x, int y, int nWidth, int nHeight, DWORD dwRop );

Giá trị dwDrop quy định kiểu kết hợp giữa hai mẫu bao gồm:
Giá tr PATCOPY PATINVERT DSTINVERT BLACKNESS WHITENESS Ý nghĩa Chép m u t i nh bitmap S d ng phép XOR Đ o ngư c nh bitmap Đ t nh màu đen Đ t nh màu tr ng

Chú ý: Mọi thao tác vẽ đều thực hiện trên cửa sổ ảo m_memDC, để hiện lên cửa sổ thật nhìn thấy chúng ta thực hiện trong hàm OnPaint() của thông điệp

139

WM_PAINT của lớp cửa sổ. Lệnh chép ảnh từ cửa sổ ảo sang cửa sổ thật như sau:
BOOL BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop );

Trong đó x,y là tọa độ đích góc trái trên cần chép ảnh tới, nWidth và nHeight là kích thước ảnh cần chép, pSrcDC là ngữ cảnh cửa sổ ảo, xSrc,ySrc là tọa độ ảnh trên cửa sổ ảo cần thực hiện chép, dwDrop là chế độ chép bao gồm:
Giá tr BLACKNESS DSINVERT MERGECOPY MERGEPAINT NOTSRCCOPY PATCOPY PATINVERT SRCAND SRCCOPY Ý nghĩa Đ t nh chép t i là màu đen Đ o ngư c nh bitmap đích S d ng phép AND S d ng phép OR Chép nh đ o ngư c c a nh g c Chép m u t i nh đích S d ng phép XOR gi a m u v i nh đích S d ng phép AND Chép nh bitmap g c vào nh đích

Ví dụ 4.4
Lập trình cho phép vẽ các hình cơ bản trên cửa sổ bằng chuột, có thực đơn để chọn màu vẽ, chọn màu tô, chọn hình để vẽ. Khi chọn chức năng vẽ hình và xử lý thông điệp chuột để vẽ hình mong muốn lên cửa sổ thật, sau khi vẽ xong hình chúng ta mới thực hiện vẽ vào cửa sổ ảo, cuối cùng chép cửa sổ ảo lên cửa sổ thật. Nội dung tệp khai báo thực đơn:
IDR_MENU1 MENU DISCARDABLE BEGIN POPUP "Chon mau ve" BEGIN MENUITEM "Do", MENUITEM "Xanh", MENUITEM "Vang", MENUITEM "Tim", END

ID_CHONMAUVE_DO ID_CHONMAUVE_XANH ID_CHONMAUVE_VANG ID_CHONMAUVE_TIM

140

POPUP "Chon mau to" BEGIN MENUITEM "Do", MENUITEM "Xanh", MENUITEM "Vang", MENUITEM "Tim", END POPUP "Chon hinh ve" BEGIN MENUITEM "Chu nhat", MENUITEM "Elip", MENUITEM "Duong thang", MENUITEM "Xoa tat ca", END MENUITEM "Thoat", END

ID_CHONMAUTO_DO ID_CHONMAUTO_XANH ID_CHONMAUTO_VANG ID_CHONMAUTO_TIM

ID_CHONHINHVE_CHUNHAT ID_CHONHINHVE_ELIP ID_CHONHINHVE_DUONGTHANG ID_CHONHINHVE_XOATATCA ID_THOAT

Nội dung tệp chương trình:
#include<afxwin.h> #include "resource.h" class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { private: CDC m_memDC; CBitmap m_bmp; CBrush m_bkbrush; int maxX,maxY,hinhve; CPoint oldPoint; CPen p; CBrush b; public: CMyWin(); afx_msg void OnPaint(); afx_msg void OnLButtonDown(UINT nFlags,CPoint point);

141

afx_msg void OnLButtonUp(UINT nFlags,CPoint point); afx_msg void OnMouseMove(UINT nFlags,CPoint point); void OnChooseCommand(UINT id); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_PAINT() ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_MOUSEMOVE() ON_COMMAND_RANGE( ID_CHONMAUVE_DO , ID_THOAT , OnChooseCommand ) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { CMenu mn; Create(NULL,"Vi du 04 - Chuong 04"); mn.LoadMenu(IDR_MENU1); SetMenu(&mn); p.CreatePen(PS_SOLID,1,RGB(0,0,0)); b.CreateSolidBrush(RGB(0,0,0)); maxX = GetSystemMetrics(SM_CXSCREEN); maxY = GetSystemMetrics(SM_CYSCREEN); CClientDC DC(this); m_memDC.CreateCompatibleDC(&DC); m_bmp.CreateCompatibleBitmap(&DC,maxX,maxY); m_memDC.SelectObject(&m_bmp); m_bkbrush.CreateStockObject(WHITE_BRUSH); m_memDC.SelectObject(&m_bkbrush);

142

m_memDC.PatBlt(0,0,maxX,maxY,PATCOPY); hinhve=3; //duong thang } void CMyWin::OnPaint() { CPaintDC DC(this); DC.BitBlt(0,0,maxX,maxY,&m_memDC,0,0,SRCCOPY); } void CMyWin::OnLButtonDown(UINT nFlags,CPoint point) { oldPoint=point; } void CMyWin::OnLButtonUp(UINT nFlags,CPoint point) { m_memDC.SelectObject(&b); m_memDC.SelectObject(&p); switch(hinhve) { case 1: m_memDC.Rectangle(oldPoint.x,oldPoint.y,point.x,point.y); break; case 2: m_memDC.Ellipse(oldPoint.x,oldPoint.y,point.x,point.y); break; case 3: m_memDC.MoveTo(oldPoint.x,oldPoint.y); m_memDC.LineTo(point.x,point.y); break; } InvalidateRect(NULL); } void CMyWin::OnChooseCommand(UINT id) { switch (id) { case ID_THOAT: DestroyWindow(); break; case ID_CHONMAUVE_DO: p.DeleteObject(); p.CreatePen(PS_SOLID,1,RGB(255,0,0)); break;

143

case ID_CHONMAUVE_XANH: p.DeleteObject(); p.CreatePen(PS_SOLID,1,RGB(0,0,255)); break; case ID_CHONMAUVE_VANG: p.DeleteObject(); p.CreatePen(PS_SOLID,1,RGB(255,255,0)); break; case ID_CHONMAUVE_TIM: p.DeleteObject(); p.CreatePen(PS_SOLID,1,RGB(255,0,255)); break; case ID_CHONMAUTO_DO: b.DeleteObject(); b.CreateSolidBrush(RGB(255,0,0)); break; case ID_CHONMAUTO_XANH: b.DeleteObject(); b.CreateSolidBrush(RGB(0,0,255)); break; case ID_CHONMAUTO_VANG: b.DeleteObject(); b.CreateSolidBrush(RGB(255,255,0)); break; case ID_CHONMAUTO_TIM: b.DeleteObject(); b.CreateSolidBrush(RGB(255,0,255)); break; case ID_CHONHINHVE_CHUNHAT: hinhve=1; break; case ID_CHONHINHVE_ELIP: hinhve=2; break; case ID_CHONHINHVE_DUONGTHANG: hinhve=3; break; case ID_CHONHINHVE_XOATATCA: m_memDC.SelectObject(&m_bkbrush);

144

m_memDC.PatBlt(0,0,maxX,maxY,PATCOPY); InvalidateRect(NULL); break; } } void CMyWin::OnMouseMove(UINT nFlags,CPoint point) { if (nFlags!=MK_LBUTTON) return; CDC *dc=GetDC(); dc->SelectObject(&p); dc->SelectObject(&b); dc->MoveTo(oldPoint); switch (hinhve) { case 1: dc->Rectangle(oldPoint.x,oldPoint.y,point.x,point.y); break; case 2: dc->Ellipse(oldPoint.x,oldPoint.y,point.x,point.y); break; case 3: dc->LineTo(point); break; } }

Kết quả chương trình sau khi thực hiện vẽ đường thẳng, chữ nhật, và đang vẽ hình elip:

145

4.6. Bài tập
Bài tập 4.1
Lập trình vẽ lên cửa sổ bàn cờ vua 8x8 ô có màu đỏ.

Bài tập 4.2
Lập trình vẽ lên cửa sổ lưới ô vuông kích thước mỗi ô là 20x20 pixels, cho phép nhấn chuột vào một ô bất kỳ và vẽ các dấu X hoặc O luân phiên nhau (minh họa chơi cờ caro).

Bài tập 4.3
Lập trình vẽ lên cửa sổ đồ thị của hai hàm số cos có màu đỏ và sin có màu xanh. Vẽ trên đoạn [-5,5] với tỷ lệ tự tính để phóng to đầy cửa sổ.

Bài tập 4.4
Lập trình cho phép vẽ hình tự do theo bước di chuột, nếu giữ phím SHIFT cho phép vẽ đường thẳng. Có thể chọn màu vẽ qua thực đơn.

Bài tập 4.5
Lập trình vẽ một bánh xe đạp quay trên cửa sổ, có một hộp thoại để chọn vị trí của bánh xe, số nan hoa, tốc độ quay, màu sắc,...

146

Chương 5 XỬ LÝ VĂN BẢN
5.1. Cơ chế hiển thị văn bản và hệ tọa độ
Mọi thông tin hiển thị trên cửa sổ đều dưới dạng đồ họa kể cả văn bản, khi hiển thị văn bản chúng ta phải thực hiện vẽ các chữ lên cửa sổ. Tuy nhiên Windows cung cấp cho chúng ta hàm để thực hiện qua đối tượng CDC là hàm TextOut().
virtual BOOL TextOut( int x, int y, LPCTSTR lpszString, int nCount );

Lệnh TextOut() ở trên hiện xâu văn bản lpszString bắt đầu từ tọa độ x,y, số ký tự hiện là nCount. Hệ trục tọa độ có gốc ở góc trái trên, chiều ngang là trục X tăng từ trái sang phải, chiều dọc là trục Y tăng từ trên xuống dưới. Hệ thống đơn vị khi thực hiện là đơn vị lôgíc, theo các lệnh đặt như trong chương trước. Lệnh đặt chế độ căn chỉnh văn bản hiện ra so với điểm xuất phát đưa vào trong lệnh TextOut() như sau:
UINT CDC::SetTextAlign( UINT nFlags );

Trong đó giá trị nFlags quy định chế độ căn chỉnh bao gồm các giá trị sau:
Giá tr TA_CENTER TA_LEFT TA_RIGHT TA_BASELINE TA_BOTTOM Ý nghĩa Căn gi a trên văn b n Căn trái gi a Căn ph i gi a Theo dòng cơ s c a phông ch Căn trái dư i

147

TA_TOP TA_NOUPDATECP TA_UPDATECP

Căn trái trên Không thay đ i đi m v hi n th i Có thay đ i đi m v hi n th i

Ý nghĩa của căn chỉnh so với điểm và văn bản cần hiện được minh họa như sau:

5.2. Màu chữ và màu nền
Mặc định khi hiện văn bản bằng lệnh TextOut() sẽ có màu chữ đen và nền trắng, có thể đặt lại chế độ màu này bằng các lệnh như sau:
virtual COLORREF CDC::SetTextColor( COLORREF crColor );

Đặt màu chữ với giá trị màu là crColor có thể xác định màu qua macro RGB(red,green,blue). Tương tự hàm đặt màu nền văn bản là:
virtual COLORREF CDC::SetBkColor( COLORREF crColor );

Hai hàm trên đều trả về giá trị màu trước đó của cửa sổ. Khi hiện văn bản lên cửa sổ có thể thực hiện theo hai chế độ nền khác nhau, đó là chế độ OPAQUE (không trong suốt) hoặc chế độ TRANSPARENT (trong suốt). Ở chế độ OPAQUE văn bản hiện có màu nền quy định bằng lệnh SetBkColor(), còn ở chế độ TRANSPARENT văn bản hiện không có nền khi đó nền sẽ là màu nền cửa sổ. Lệnh đặt chế độ hiện văn bản khai báo theo mẫu sau:
int CDC::SetBkMode( int nBkMode );

Tham số nBkMode quy định chế độ cần đặt là OPAQUE hoặc TRANSPARENT. Hàm trả về giá trị chế độ trước đó. Mặc định nền cửa sổ hiện văn bản ở chế độ OPAQUE.
148

Ví dụ trên cho thấy chữ ABC hiện ra có màu nền với chế độ OPAQUE, số 123 hiện không có màu nền với chế độ TRANSPARENT.

5.3. Cấu trúc chữ để hiển thị (text metric)
Các chữ hiện lên cửa sổ có kích thước không giống nhau, hơn nữa khi có phông chữ thì điều này lại càng rõ. Ví dụ chữ "i" có độ rộng nhỏ hơn chữ "w", chữ "a" sẽ thấp hơn chữ "h",... Như chúng ta thấy trong soạn thảo văn bản Word, chế độ khoảng cách chữ, khoảng cách dòng,... rất khác nhau. Ngoài ra có kiểu chữ, cỡ chữ, phông chữ,... do đó hiện văn bản lên cửa sổ cho đẹp là phức tạp. Windows cung cấp cho chúng ta cơ chế kiểm soát các kích thước chữ để hiện văn bản lên cửa sổ, các tham số này được thiết lập trong một cấu trúc như sau:
typedef struct tagTEXTMETRIC { LONG tmHeight; LONG tmAscent; LONG tmDescent; LONG tmInternalLeading; LONG tmExternalLeading; LONG tmAveCharWidth; LONG tmMaxCharWidth; LONG tmWeight; LONG tmOverhang; LONG tmDigitizedAspectX; LONG tmDigitizedAspectY;

149

char tmFirstChar; char tmLastChar; char tmDefaultChar; char tmBreakChar; BYTE tmItalic; BYTE tmUnderlined; BYTE tmStruckOut; BYTE tmPitchAndFamily; BYTE tmCharSet; } TEXTMETRIC;

Cấu trúc chữ có một đường cơ sở (baseline) để xác lập độ cao và vị trí của chữ. Ví dụ đường cơ sở của dòng chữ có màu đỏ như sau:

Ý nghĩa của các thành phần trong cấu trúc trên được mô tả như sau: + tmHeight: là độ cao của chữ cả phần trên và dưới đường cơ sở, + tmAscent: độ cao phía trên đường cơ sở, + tmDescent: độ cao phía dưới đường cơ sở, + tmInternalLeading: khoảng cách phía trên chữ (thường bằng 0), + tmExternalLeading: khoảng cách giữa các dòng (thường bằng 0), + tmAveCharWidth: độ rộng trung bình của chữ, + tmMaxCharWidth: độ rộng tối đa của chữ, + tmWeight: độ đậm nhạt của nét chữ, + tmOverhang: độ rộng thêm vào với các kiểu phông đặc biệt, + tmDigitizedAspectX: mặt ngang của chữ, + tmDigitizedAspectY: mặt đứng của chữ, + tmFirstChar: ký tự đầu tiên trong phông chữ,

150

+ tmLastChar: ký tự cuối cùng trong phông chữ, + tmDefaultChar: ký tự mặc định, + tmBreakChar: ký tự để ngăn cách từ, + tmItalic: chữ in nghiên (khác không), + tmUnderlined: gạch chân (khác không), + tmStruckOut: nét gạch bỏ (khác không), + tmPitchAndFamily: độ nghiêng của phông và họ phông chữ, + tmCharSet: quy định bộ chữ. Hầu hết các giá trị trong cấu trúc này không sử dụng, có hai thành phần khá quan trọng bởi vì chúng được sử dụng để tính khoảng cách dọc giữa các dòng văn bản. Khác với màn hình dạng text chỉ có một kiểu phông với kích thước cố định, Windows có khá nhiều phông chữ và các kích thước rất khác nhau. Mỗi phông định nghĩa độ cao của chữ và khoảng cách dọc giữa các dòng, dựa vào đó để biết tọa độ hiển thị các dòng văn bản liên tiếp nhau. Để xác định cấu trúc này của chữ ta sử dụng hàm sau:
BOOL CDC::GetTextMetrics( LPTEXTMETRIC lpMetrics ) const;

Các giá trị thành phần của cấu trúc chữ được lưu trong tham số lpMetrics, để tính khoảng cách dòng ta cộng hai thành phần tmHeight và tmExternalLeading với nhau.

Ngoài ra khi hiển thị văn bản đòi hỏi người lập trình phải tính toán độ dài một dòng văn bản có vượt ngoài cửa sổ hay không, độ dài này phụ thuộc vào phông chữ, kích thước chữ,... Lệnh để xác định độ dài dòng chữ khai báo theo mẫu sau:
CSize CDC::GetTextExtent( LPCTSTR lpszString, int nCount ) const; CSize CDC::GetTextExtent( const CString& str ) const; 151

Hàm trả về kích thước lưu trong kiểu CSize gồm hai thành phần, cy là độ cao của văn bản, cx là độ dài của dòng văn bản đó. Dựa vào giá trị cx này chúng ta quyết định tọa độ hiện tiếp dòng hay xuống dòng trên cửa sổ. Có thể minh họa cấu trúc chữ hiển thị trên Windows qua các thành phần của TEXTMETRIC như sau:

Ngoài ra Windows cung cấp hàm xác định kích thước các thành phần hiển thị trên windows, hàm được khai báo như sau:
int GetSystemMetrics( int nIndex );

Trong đó tham số nIndex quy định thành phần cần xác định kích thước bao gồm các giá trị sau:
Giá tr SM_CXFULLSCREEN SM_CYFULLSCREEN SM_CXICON Ý nghĩa Đ r ng n n c a s khi phóng to Đ cao n n c a s khi phóng to Đ r ng bi u tư ng

152

SM_CYICON SM_CXSMICON SM_CYSMICON SM_CXSCREEN SM_CYSCREEN

Đ cao bi u tư ng Đ r ng bi u tư ng thu nh Đ cao bi u tư ng thu nh Đ r ng màn hình Đ cao màn hình

Ví dụ 5.1
Lập trình hiển thị các giá trị tham số của cấu trúc chữ và kích thước màn hình, kích thước cửa sổ. Nội dung chương trình là:
#include<afxwin.h> class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); afx_msg void OnLButtonDown(UINT nFlags,CPoint point); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vidu 01 - Chuong 05"); } void CMyWin::OnLButtonDown(UINT nFlags,CPoint point) { char s[200];

153

int kc,y=0; CSize size; CDC *dc=GetDC(); TEXTMETRIC tm; dc->GetTextMetrics(&tm); kc=tm.tmHeight+tm.tmExternalLeading; sprintf(s,"Do cao chu la : %ld",tm.tmHeight); dc->SetBkColor(RGB(0,0,255)); dc->SetTextColor(RGB(255,255,0)); dc->TextOut(0,y,s,strlen(s)); y=y+kc; sprintf(s,"Van ban tiep tren cung dong."); dc->TextOut(0,y,s,strlen(s)); size = dc->GetTextExtent(s,strlen(s)); sprintf(s,"Xau truoc co do dai la : %ld",size.cx); dc->TextOut(size.cx,y,s,strlen(s)); y=y+kc; size.cx = GetSystemMetrics(SM_CXSCREEN); size.cy = GetSystemMetrics(SM_CYSCREEN); sprintf(s,"Kich thuoc man hinh : %ldx%ld",size.cx,size.cy); dc->TextOut(0,y,s,strlen(s)); y=y+kc; sprintf(s,"Khoang cach giua cac dong van ban la : %ld",kc); dc->TextOut(0,y,s,strlen(s)); }

Kết quả chương trình là:

Chương trình xử lý phím trái chuột, thực hiện in lên cửa sổ khi bấm chuột trái vào cửa sổ.

5.4. Sử dụng phông chữ
Windows cung cấp khá nhiều phông chữ để hiển thị tùy vào việc thiết lập trong hệ thống phông chữ của máy. Chúng ta có thể sử dụng phông chữ sẵn có hoặc có thể tạo ra phông chữ mới tùy ý. Các phông chữ có sẵn là các đối tượng stock được tạo bởi lệnh sau:
BOOL 154 CreateStockObject( int nIndex );

Trong đó tham số nIndex quy định kiểu phông chữ cần tạo đối tượng tương ứng, gồm:
Giá tr ANSI_FIXED_FONT ANSI_VAR_FONT DEVICE_DEFAULT_FONT DEFAULT_GUI_FONT OEM_FIXED_FONT SYSTEM_FONT SYSTEM_FIXED_FONT Ý nghĩa Phông có đ d c c đ nh Phông có đ d c thay đ i Phông ch m c đ nh theo thi t b Phông ch c a h p tho i Phông ch OEM Phông ch c a Windows Phông ch Windows phiên b n cũ

Trong chương trình để chọn và sử dụng phông chữ có sẵn khá dễ dàng, chúng ta sử dụng lớp CFont để thực hiện. Trước hết tạo một đối tượng thuộc lớp đó, tiếp theo nạp phông chữ cần chọn bằng hàm CreateStockObject() như trên. Cần sử dụng phông chữ nào ta chọn vào cửa sổ bằng lệnh SelectObject() của lớp CDC.

Ví dụ 5.2
Lập trình hiện văn bản theo các kiểu phông chữ sẵn có trong Windows lên cửa sổ. Nội dung chương trình là:
#include<afxwin.h> // Đ nh nghĩa các h ng s cho m c ch n trên th c đơn ch n #define ID_ANSI_FIXED_FONT 1000 #define ID_ANSI_VAR_FONT 1001 #define ID_DEVICE_DEFAULT_FONT 1002 #define ID_DEFAULT_GUI_FONT 1003 #define ID_OEM_FIXED_FONT 1004 #define ID_SYSTEM_FONT 1005 #define ID_SYSTEM_FIXED_FONT 1006 #define ID_SHOW 1007 #define ID_THOAT 1008 // N i dung chính c a chương trình class CMyApp : public CWinApp { public: BOOL InitInstance(); };

155

class CMyWin : public CFrameWnd { private: CMenu mn,pp; CFont font; int y; public: CMyWin(); void OnCommand(UINT id); void ShowText(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_COMMAND_RANGE( ID_ANSI_FIXED_FONT, ID_THOAT, OnCommand ) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vidu 01 - Chuong 05"); mn.CreateMenu(); pp.CreatePopupMenu(); pp.AppendMenu(MF_STRING|MF_BYCOMMAND,ID_ANSI_FIXED_FONT,"Ansi fixed font"); pp.AppendMenu(MF_STRING|MF_BYCOMMAND,ID_ANSI_VAR_FONT,"Ansi var font"); pp.AppendMenu(MF_STRING|MF_BYCOMMAND,ID_DEVICE_DEFAULT_FONT,"Devi ce default font"); pp.AppendMenu(MF_STRING|MF_BYCOMMAND,ID_DEFAULT_GUI_FONT,"Default GUI font"); pp.AppendMenu(MF_STRING|MF_BYCOMMAND,ID_OEM_FIXED_FONT,"OEM fixed font"); pp.AppendMenu(MF_STRING|MF_BYCOMMAND,ID_SYSTEM_FONT,"System font");

156

pp.AppendMenu(MF_STRING|MF_BYCOMMAND,ID_SYSTEM_FIXED_FONT,"System fixed font"); mn.AppendMenu(MF_POPUP , (UINT)pp.m_hMenu ,"Chon font"); mn.AppendMenu(MF_STRING | MF_BYCOMMAND , ID_SHOW , "Xem"); mn.AppendMenu(MF_STRING | MF_BYCOMMAND , ID_THOAT , "Thoat"); SetMenu(&mn); font.CreateStockObject(SYSTEM_FONT); y=200; } void CMyWin::OnCommand(UINT id) { switch (id) { case ID_THOAT: DestroyWindow(); break; case ID_SHOW: ShowText(); break; case ID_ANSI_FIXED_FONT: font.DeleteObject(); font.CreateStockObject(ANSI_FIXED_FONT); y=0; break; case ID_ANSI_VAR_FONT: font.DeleteObject(); font.CreateStockObject(ANSI_VAR_FONT); y=40; break; case ID_DEVICE_DEFAULT_FONT: font.DeleteObject(); font.CreateStockObject(DEVICE_DEFAULT_FONT); y=80; break; case ID_DEFAULT_GUI_FONT: font.DeleteObject();

157

font.CreateStockObject(DEFAULT_GUI_FONT); y=120; break; case ID_OEM_FIXED_FONT: font.DeleteObject(); font.CreateStockObject(OEM_FIXED_FONT); y=160; break; case ID_SYSTEM_FONT: font.DeleteObject(); font.CreateStockObject(SYSTEM_FONT); y=200; break; case ID_SYSTEM_FIXED_FONT: font.DeleteObject(); font.CreateStockObject(SYSTEM_FIXED_FONT); y=240; break; } } void CMyWin::ShowText() { CDC *dc=GetDC(); dc->SetBkMode(OPAQUE); dc->SetBkColor(RGB(0,0,255)); dc->SetTextColor(RGB(255,255,0)); dc->SelectObject(&font); TEXTMETRIC tm; dc->GetTextMetrics(&tm); dc->TextOut(0,y,"Cong hoa xa hoi chu nghia viet nam",34); dc->TextOut(0,y+tm.tmHeight+tm.tmExternalLeading,"Doc lap - Tu do - Hanh phuc",27); }

Trong chương trình này sử dụng cách tạo thực đơn bằng câu lệnh, không qua định nghĩa tài nguyên nên chỉ cần một tệp CPP duy nhất. Tuy nhiên chúng ta định nghĩa các định danh mục chọn thông qua các tên bằng lệnh define như trên, không nên sử dụng số nguyên trực tiếp trong các lệnh.

158

Kết quả chương trình là:

Ngoài việc sử dụng các phông chữ có sẵn trên Windows chúng ta có thể tạo ra các phông chữ tùy ý. Sử dụng đối tượng lớp CFont để thực hiện bằng hàm sau:
BOOL CFont :: CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily, LPCTSTR lpszFacename );

Các tham số trong lệnh này có ý nghĩa như sau: + int nHeight: độ cao chữ, nếu bằng 0 máy sẽ lấy mặc định, + int nWidth: độ rộng trung bình của các chữ, + int nEscapement: góc nghiêng của chữ theo phương ngang theo độ, + int nOrientation: góc nghiêng của đường cơ sở với phương ngang,

159

+ int nWeight: độ đậm của chữ gồm một trong các giá trị sau:
FW_DONTCARE FW_ULTRALIGHT FW_REGULAR FW_DEMIBOLD FW_ULTRABOLD FW_THIN FW_LIGHT FW_MEDIUM FW_BOLD FW_BLACK FW_EXTRALIGHT FW_NORMAL FW_SEMIBOLD FW_EXTRABOLD FW_HEAVY

+ BYTE bItalic: chữ nghiêng, + BYTE bUnderline: chữ gạch chân, + BYTE cStrikeOut: chữ có nét gạch bỏ, + BYTE nCharSet: bộ kiểu chữ gồm các giá trị sau:
ANSI_CHARSET SHIFTJIS_CHARSET SYMBOL_CHARSET DEFAULT_CHARSET OEM_CHARSET

+ BYTE nOutPrecision: quy định độ chính xác chữ khi hiện ra, bao gồm các giá trị sau:
OUT_CHARACTER_PRECIS OUT_DEFAULT_PRECIS OUT_DEVICE_PRECIS OUT_RASTER_PRECIS OUT_STRING_PRECIS OUT_STROKE_PRECIS OUT_TT_PRECIS

+ BYTE nClipPrecision: gồm các giá trị sau:
CLIP_CHARACTER_PRECIS CLIP_DEFAULT_PRECIS CLIP_ENCAPSULATE CLIP_LH_ANGLES CLIP_MASK CLIP_STROKE_PRECIS CLIP_TT_ALWAYS

+ BYTE nQuality: quy định chất lượng chữ hiện ra, bao gồm các giá trị:
DEFAULT_QUALITY DRAF_QUALITY PROOF_QUALITY

+ BYTE nPitchAndFamily: bao gồm các giá trị:
DEFAULT_PITCH VARIABLE_PITCH FIXED_PITCH

+ LPCTSTR lpszFacename: tên phông chữ, nếu bằng NULL máy sẽ lấy mặt phông chữ thiết bị mặc định.

160

Ví dụ 5.3
Lập trình hiện một xâu "Ha noi - Hue - Sai gon" với các tham số của lệnh tạo phông chữ khác nhau trên các dòng khác nhau. Tệp khai báo tài nguyên RC là:
// Đ nh nghĩa các h ng s trong tài nguyên chương trình #define IDD_DIALOG1 101 #define IDR_MENU1 103 #define IDC_EDIT_HEIGHT 1000 #define IDC_EDIT_WIDHT 1001 #define IDC_EDIT_ESCAPEMENT 1002 #define IDC_EDIT_ORIENTATION 1003 #define IDC_COMBO_WEIGHT 1004 #define IDC_CHECK_ITALIC 1005 #define IDC_CHECK_UNDERLINE 1014 #define IDC_CHECK_STRIKEOUT 1015 #define IDC_COMBO_CHARSET 1016 #define IDC_COMBO_OUTPRECISION 1017 #define IDC_COMBO_CLIPPRECISION 1018 #define IDC_COMBO_QUALITY 1019 #define IDC_COMBO_PITCHFAMILY 1020 #define IDC_COMBO_FACENAME 1021 #define ID_CHONFONT 40001 #define ID_XEM 40002 #define ID_THOAT 40003 #define ID_XOA 40004 // Dialog - Khai báo h p tho i IDD_DIALOG1 DIALOGEX 0, 0, 325, 133 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Chon cac tham so tao phong chu" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,87,115,50,14 PUSHBUTTON "Cancel",IDCANCEL,165,115,50,14 LTEXT "Height",IDC_STATIC,13,11,49,11,0,WS_EX_STATICEDGE LTEXT "Width",IDC_STATIC,13,26,49,11,0,WS_EX_STATICEDGE EDITTEXT IDC_EDIT_HEIGHT,65,11,91,12,ES_AUTOHSCROLL | ES_NUMBER EDITTEXT IDC_EDIT_WIDHT,65,26,91,12,ES_AUTOHSCROLL | ES_NUMBER LTEXT "Escapement",IDC_STATIC,13,42,49,11,0,WS_EX_STATICEDGE EDITTEXT IDC_EDIT_ESCAPEMENT,65,42,91,12,ES_AUTOHSCROLL | ES_NUMBER LTEXT "Orientation",IDC_STATIC,13,58,49,11,0,WS_EX_STATICEDGE EDITTEXT IDC_EDIT_ORIENTATION,65,58,91,12,ES_AUTOHSCROLL |

161

ES_NUMBER "Weight",IDC_STATIC,13,74,49,11,0,WS_EX_STATICEDGE "Char set",IDC_STATIC,171,11,49,11,0,WS_EX_STATICEDGE "Out precision",IDC_STATIC,171,26,49,11,0, WS_EX_STATICEDGE LTEXT "Clip precision",IDC_STATIC,171,42,49,11,0, WS_EX_STATICEDGE LTEXT "Quality",IDC_STATIC,171,58,49,11,0,WS_EX_STATICEDGE LTEXT "Pitch && family",IDC_STATIC,171,74,49,11,0, WS_EX_STATICEDGE LTEXT "Face name",IDC_STATIC,171,90,49,11,0,WS_EX_STATICEDGE COMBOBOX IDC_COMBO_WEIGHT,65,74,91,12,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP CONTROL "Italic",IDC_CHECK_ITALIC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,90,31,12 CONTROL "Underline",IDC_CHECK_UNDERLINE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,55,90,44,12 CONTROL "Strike out",IDC_CHECK_STRIKEOUT,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,109,90,46,12 COMBOBOX IDC_COMBO_CHARSET,223,11,91,12,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP COMBOBOX IDC_COMBO_OUTPRECISION,223,26,91,12,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP COMBOBOX IDC_COMBO_CLIPPRECISION,223,42,91,12,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP COMBOBOX IDC_COMBO_QUALITY,223,58,91,12,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP COMBOBOX IDC_COMBO_PITCHFAMILY,223,74,91,12,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP COMBOBOX IDC_COMBO_FACENAME,223,90,91,12,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP END // Dialog Info - Chi ti t các tham s cho đi u khi n trên h p tho i IDD_DIALOG1 DLGINIT BEGIN IDC_COMBO_WEIGHT, 0x403, 12, 0 0x5746, 0x445f, 0x4e4f, 0x4354, 0x5241, 0x0045, IDC_COMBO_WEIGHT, 0x403, 8, 0 0x5746, 0x545f, 0x4948, 0x004e, LTEXT LTEXT LTEXT

162

IDC_COMBO_WEIGHT, 0x403, 14, 0 0x5746, 0x455f, 0x5458, 0x4152, 0x494c, 0x4847, 0x0054, IDC_COMBO_WEIGHT, 0x403, 14, 0 0x5746, 0x555f, 0x544c, 0x4152, 0x494c, 0x4847, 0x0054, IDC_COMBO_WEIGHT, 0x403, 9, 0 0x5746, 0x4c5f, 0x4749, 0x5448, "\000" IDC_COMBO_WEIGHT, 0x403, 10, 0 0x5746, 0x4e5f, 0x524f, 0x414d, 0x004c, IDC_COMBO_WEIGHT, 0x403, 11, 0 0x5746, 0x525f, 0x4745, 0x4c55, 0x5241, "\000" IDC_COMBO_WEIGHT, 0x403, 10, 0 0x5746, 0x4d5f, 0x4445, 0x5549, 0x004d, IDC_COMBO_WEIGHT, 0x403, 12, 0 0x5746, 0x535f, 0x4d45, 0x4249, 0x4c4f, 0x0044, IDC_COMBO_WEIGHT, 0x403, 12, 0 0x5746, 0x445f, 0x4d45, 0x4249, 0x4c4f, 0x0044, IDC_COMBO_WEIGHT, 0x403, 8, 0 0x5746, 0x425f, 0x4c4f, 0x0044, IDC_COMBO_WEIGHT, 0x403, 13, 0 0x5746, 0x455f, 0x5458, 0x4152, 0x4f42, 0x444c, "\000" IDC_COMBO_WEIGHT, 0x403, 13, 0 0x5746, 0x555f, 0x544c, 0x4152, 0x4f42, 0x444c, "\000" IDC_COMBO_WEIGHT, 0x403, 9, 0 0x5746, 0x425f, 0x414c, 0x4b43, "\000" IDC_COMBO_WEIGHT, 0x403, 9, 0 0x5746, 0x485f, 0x4145, 0x5956, "\000" IDC_COMBO_CHARSET, 0x403, 13, 0 0x4e41, 0x4953, 0x435f, 0x4148, 0x5352, 0x5445, "\000" IDC_COMBO_CHARSET, 0x403, 16, 0 0x4544, 0x4146, 0x4c55, 0x5f54, 0x4843, 0x5241, 0x4553, 0x0054, IDC_COMBO_CHARSET, 0x403, 15, 0 0x5953, 0x424d, 0x4c4f, 0x435f, 0x4148, 0x5352, 0x5445, "\000" IDC_COMBO_CHARSET, 0x403, 17, 0 0x4853, 0x4649, 0x4a54, 0x5349, 0x435f, 0x4148, 0x5352, 0x5445, "\000" IDC_COMBO_CHARSET, 0x403, 12, 0 0x454f, 0x5f4d, 0x4843, 0x5241, 0x4553, 0x0054, IDC_COMBO_OUTPRECISION, 0x403, 21, 0 0x554f, 0x5f54, 0x4843, 0x5241, 0x4341, 0x4554, 0x5f52, 0x5250, 0x4345, 0x5349, "\000" IDC_COMBO_OUTPRECISION, 0x403, 18, 0 0x554f, 0x5f54, 0x5453, 0x4952, 0x474e, 0x505f, 0x4552, 0x4943, 0x0053, IDC_COMBO_OUTPRECISION, 0x403, 20, 0 0x554f, 0x5f54, 0x4544, 0x4146, 0x4c55, 0x5f54, 0x5250, 0x4345, 0x5349,

163

0x0020, IDC_COMBO_OUTPRECISION, 0x403, 19, 0 0x554f, 0x5f54, 0x5453, 0x4f52, 0x454b, 0x505f, 0x4552, 0x4943, 0x2053, "\000" IDC_COMBO_OUTPRECISION, 0x403, 19, 0 0x554f, 0x5f54, 0x4544, 0x4956, 0x4543, 0x505f, 0x4552, 0x4943, 0x2053, "\000" IDC_COMBO_OUTPRECISION, 0x403, 15, 0 0x554f, 0x5f54, 0x5454, 0x505f, 0x4552, 0x4943, 0x2053, "\000" IDC_COMBO_OUTPRECISION, 0x403, 18, 0 0x554f, 0x5f54, 0x4152, 0x5453, 0x5245, 0x505f, 0x4552, 0x4943, 0x0053, IDC_COMBO_CLIPPRECISION, 0x403, 22, 0 0x4c43, 0x5049, 0x435f, 0x4148, 0x4152, 0x5443, 0x5245, 0x505f, 0x4552, 0x4943, 0x0053, IDC_COMBO_CLIPPRECISION, 0x403, 10, 0 0x4c43, 0x5049, 0x4d5f, 0x5341, 0x004b, IDC_COMBO_CLIPPRECISION, 0x403, 21, 0 0x4c43, 0x5049, 0x445f, 0x4645, 0x5541, 0x544c, 0x505f, 0x4552, 0x4943, 0x2053, "\000" IDC_COMBO_CLIPPRECISION, 0x403, 20, 0 0x4c43, 0x5049, 0x535f, 0x5254, 0x4b4f, 0x5f45, 0x5250, 0x4345, 0x5349, 0x0020, IDC_COMBO_CLIPPRECISION, 0x403, 16, 0 0x4c43, 0x5049, 0x545f, 0x5f54, 0x4c41, 0x4157, 0x5359, 0x0020, IDC_COMBO_CLIPPRECISION, 0x403, 16, 0 0x4c43, 0x5049, 0x4c5f, 0x5f48, 0x4e41, 0x4c47, 0x5345, 0x0009, IDC_COMBO_QUALITY, 0x403, 16, 0 0x4544, 0x4146, 0x4c55, 0x5f54, 0x5551, 0x4c41, 0x5449, 0x0059, IDC_COMBO_QUALITY, 0x403, 13, 0 0x5244, 0x4641, 0x515f, 0x4155, 0x494c, 0x5954, "\000" IDC_COMBO_QUALITY, 0x403, 14, 0 0x5250, 0x4f4f, 0x5f46, 0x5551, 0x4c41, 0x5449, 0x0059, IDC_COMBO_PITCHFAMILY, 0x403, 14, 0 0x4544, 0x4146, 0x4c55, 0x5f54, 0x4950, 0x4354, 0x0048, IDC_COMBO_PITCHFAMILY, 0x403, 15, 0 0x4156, 0x4952, 0x4241, 0x454c, 0x505f, 0x5449, 0x4843, "\000" IDC_COMBO_PITCHFAMILY, 0x403, 12, 0 0x4946, 0x4558, 0x5f44, 0x4950, 0x4354, 0x0048, 0 END // Menu - Th c đơn chương trình IDR_MENU1 MENU DISCARDABLE BEGIN

164

MENUITEM "Chon font", MENUITEM "Xem", MENUITEM "Xoa", MENUITEM "Thoat", END

ID_CHONFONT ID_XEM ID_XOA ID_THOAT

Nội dung tệp chương trình:
#include<afxwin.h> #include"resource.h" struct PARAMETERS { char nHeight[10],nWidth[10],nEscapement[10], nOrientation[10],nWeight[10]; BYTE bItalic,bUnderline,cStrikeOut; char nCharSet[100],nOutPrecision[100], nClipPrecision[100],nQuality[100],nPitchAndFamily[100], lpszFacename[100]; }; int GetValueConst(char *s); class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMenu mn; PARAMETERS pm; int y; public: CMyWin(); void OnChonFont(); void OnXem(); void OnXoa(); void OnThoat(); DECLARE_MESSAGE_MAP() }; class CMyDialog : public CDialog { public:

165

CMyDialog(UINT id,CWnd *ow) : CDialog(id,ow){} BOOL OnInitDialog(); void OnOK(); }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_COMMAND( ID_CHONFONT, OnChonFont ) ON_COMMAND( ID_XEM, OnXem ) ON_COMMAND( ID_XOA, OnXoa ) ON_COMMAND( ID_THOAT, OnThoat ) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vidu 03 - Chuong 05"); mn.LoadMenu(IDR_MENU1); SetMenu(&mn); pm.bItalic=0; pm.bUnderline=0; pm.cStrikeOut=0; strcpy(pm.lpszFacename,"Tahoma"); strcpy(pm.nCharSet,"ANSI_CHARSET"); strcpy(pm.nClipPrecision,"CLIP_DEFAULT_PRECIS"); strcpy(pm.nEscapement,"10"); strcpy(pm.nHeight,"10"); strcpy(pm.nOrientation,"10"); strcpy(pm.nOutPrecision,"OUT_DEFAULT_PRECIS"); strcpy(pm.nPitchAndFamily,"DEFAULT_PITCH"); strcpy(pm.nQuality,"DEFAULT_QUALITY"); strcpy(pm.nWeight,"FW_NORMAL"); strcpy(pm.nWidth,"10"); y=0; } void CMyWin::OnChonFont() { CMyDialog d(IDD_DIALOG1,this); d.DoModal();

166

} void CMyWin::OnXem() { char s[100]="Ha noi - Hue - Sai gon"; CDC *dc=GetDC(); RECT rt; TEXTMETRIC tm; CFont f; f.CreateFont(atoi(pm.nHeight),atoi(pm.nWidth),atoi(pm.nEscapement), atoi(pm.nOrientation),GetValueConst(pm.nWeight),pm.bItalic, pm.bUnderline,pm.cStrikeOut, GetValueConst(pm.nCharSet),GetValueConst(pm.nOutPrecision),GetValueConst(pm.n ClipPrecision), GetValueConst(pm.nQuality),GetValueConst(pm.nPitchAndFamily),pm.lpszFacename); dc->SelectObject(&f); dc->SetBkMode(TRANSPARENT); dc->SetTextColor(RGB(rand()%256,rand()%256,rand()%256)); dc->TextOut(0,y,s); GetClientRect(&rt); dc->GetTextMetrics(&tm); y=y+tm.tmExternalLeading+tm.tmHeight; f.DeleteObject(); } void CMyWin::OnXoa() { InvalidateRect(NULL); y=0; } void CMyWin::OnThoat() { DestroyWindow(); } BOOL CMyDialog::OnInitDialog() { CDialog::OnInitDialog(); PARAMETERS *p = &(((CMyWin*)GetParent())->pm); CComboBox *cb; CEdit *ed; CButton *chk; ed=(CEdit*)GetDlgItem(IDC_EDIT_HEIGHT); ed->SetWindowText(p->nHeight); ed=(CEdit*)GetDlgItem(IDC_EDIT_WIDHT);

167

ed->SetWindowText(p->nWidth); ed=(CEdit*)GetDlgItem(IDC_EDIT_ESCAPEMENT); ed->SetWindowText(p->nEscapement); ed=(CEdit*)GetDlgItem(IDC_EDIT_ORIENTATION); ed->SetWindowText(p->nOrientation); cb=(CComboBox*)GetDlgItem(IDC_COMBO_WEIGHT); cb->SetWindowText(p->nWeight); chk=(CButton*)GetDlgItem(IDC_CHECK_ITALIC); chk->SetCheck(p->bItalic); chk=(CButton*)GetDlgItem(IDC_CHECK_UNDERLINE); chk->SetCheck(p->bUnderline); chk=(CButton*)GetDlgItem(IDC_CHECK_STRIKEOUT); chk->SetCheck(p->cStrikeOut); cb=(CComboBox*)GetDlgItem(IDC_COMBO_CHARSET); cb->SetWindowText(p->nCharSet); cb=(CComboBox*)GetDlgItem(IDC_COMBO_OUTPRECISION); cb->SetWindowText(p->nOutPrecision); cb=(CComboBox*)GetDlgItem(IDC_COMBO_CLIPPRECISION); cb->SetWindowText(p->nClipPrecision); cb=(CComboBox*)GetDlgItem(IDC_COMBO_QUALITY); cb->SetWindowText(p->nQuality); cb=(CComboBox*)GetDlgItem(IDC_COMBO_PITCHFAMILY); cb->SetWindowText(p->nPitchAndFamily); cb=(CComboBox*)GetDlgItem(IDC_COMBO_FACENAME); cb->AddString("Tahoma"); cb->AddString("Arial"); cb->SetWindowText(p->lpszFacename); return TRUE; } void CMyDialog::OnOK() { CDialog::OnOK(); CMyWin *pr = (CMyWin*)GetParent(); PARAMETERS *tm=&(pr->pm); CEdit *ed; CComboBox *cb; CButton *chk; ed=(CEdit*)GetDlgItem(IDC_EDIT_HEIGHT); ed->GetWindowText(tm->nHeight,100); ed=(CEdit*)GetDlgItem(IDC_EDIT_WIDHT);

168

ed->GetWindowText(tm->nWidth,100); ed=(CEdit*)GetDlgItem(IDC_EDIT_ESCAPEMENT); ed->GetWindowText(tm->nEscapement,100); ed=(CEdit*)GetDlgItem(IDC_EDIT_ORIENTATION); ed->GetWindowText(tm->nOrientation,100); cb=(CComboBox*)GetDlgItem(IDC_COMBO_WEIGHT); cb->GetWindowText(tm->nWeight,100); chk=(CButton*)GetDlgItem(IDC_CHECK_ITALIC); tm->bItalic=chk->GetCheck(); chk=(CButton*)GetDlgItem(IDC_CHECK_UNDERLINE); tm->bUnderline=chk->GetCheck(); chk=(CButton*)GetDlgItem(IDC_CHECK_STRIKEOUT); tm->cStrikeOut=chk->GetCheck(); cb=(CComboBox*)GetDlgItem(IDC_COMBO_CHARSET); cb->GetWindowText(tm->nCharSet,100); cb=(CComboBox*)GetDlgItem(IDC_COMBO_OUTPRECISION); cb->GetWindowText(tm->nOutPrecision,100); cb=(CComboBox*)GetDlgItem(IDC_COMBO_CLIPPRECISION); cb->GetWindowText(tm->nClipPrecision,100); cb=(CComboBox*)GetDlgItem(IDC_COMBO_QUALITY); cb->GetWindowText(tm->nQuality,100); cb=(CComboBox*)GetDlgItem(IDC_COMBO_PITCHFAMILY); cb->GetWindowText(tm->nPitchAndFamily,100); cb=(CComboBox*)GetDlgItem(IDC_COMBO_FACENAME); cb->GetWindowText(tm->lpszFacename,100); } int GetValueConst(char *s) { if (strcmp(s,"FW_DONTCARE")==0) return FW_DONTCARE; else if (strcmp(s,"FW_THIN")==0) return FW_THIN; else if (strcmp(s,"FW_EXTRALIGHT")==0) return FW_EXTRALIGHT; else if (strcmp(s,"FW_ULTRALIGHT")==0) return FW_ULTRALIGHT; else if (strcmp(s,"FW_LIGHT")==0) return FW_LIGHT; else if (strcmp(s,"FW_NORMAL")==0) return FW_NORMAL; else if (strcmp(s,"FW_REGULAR")==0) return FW_REGULAR; else if (strcmp(s,"FW_MEDIUM")==0) return FW_MEDIUM; else if (strcmp(s,"FW_SEMIBOLD")==0) return FW_SEMIBOLD; else if (strcmp(s,"FW_DEMIBOLD")==0) return FW_DEMIBOLD; else if (strcmp(s,"FW_BOLD")==0) return FW_BOLD; else if (strcmp(s,"FW_EXTRABOLD")==0) return FW_EXTRABOLD;

169

else if (strcmp(s,"FW_ULTRABOLD")==0) return FW_ULTRABOLD; else if (strcmp(s,"FW_BLACK")==0) return FW_BLACK; else if (strcmp(s,"FW_HEAVY")==0) return FW_HEAVY; else if (strcmp(s,"ANSI_CHARSET")==0) return ANSI_CHARSET; else if (strcmp(s,"DEFAULT_CHARSET")==0) return DEFAULT_CHARSET; else if (strcmp(s,"SYMBOL_CHARSET")==0) return SYMBOL_CHARSET; else if (strcmp(s,"SHIFTJIS_CHARSET")==0) return SHIFTJIS_CHARSET; else if (strcmp(s,"OEM_CHARSET")==0) return OEM_CHARSET; else if (strcmp(s,"OUT_CHARACTER_PRECIS")==0) return OUT_CHARACTER_PRECIS; else if (strcmp(s,"OUT_STRING_PRECIS")==0) return OUT_STRING_PRECIS; else if (strcmp(s,"OUT_DEFAULT_PRECIS")==0) return OUT_DEFAULT_PRECIS; else if (strcmp(s,"OUT_STRING_PRECIS")==0) return OUT_STRING_PRECIS; else if (strcmp(s,"OUT_STROKE_PRECIS")==0) return OUT_STROKE_PRECIS; else if (strcmp(s,"OUT_DEVICE_PRECIS")==0) return OUT_DEVICE_PRECIS; else if (strcmp(s,"OUT_TT_PRECIS")==0) return OUT_TT_PRECIS; else if (strcmp(s,"OUT_RASTER_PRECIS")==0) return OUT_RASTER_PRECIS; else if (strcmp(s,"CLIP_CHARACTER_PRECIS")==0) return CLIP_CHARACTER_PRECIS; else if (strcmp(s,"CLIP_MASK")==0) return CLIP_MASK; else if (strcmp(s,"CLIP_DEFAULT_PRECIS")==0) return CLIP_DEFAULT_PRECIS; else if (strcmp(s,"CLIP_STROKE_PRECIS")==0) return CLIP_STROKE_PRECIS; else if (strcmp(s,"CLIP_TT_ALWAYS")==0) return CLIP_TT_ALWAYS; else if (strcmp(s,"CLIP_LH_ANGLES")==0) return CLIP_LH_ANGLES; else if (strcmp(s,"DEFAULT_QUALITY")==0) return DEFAULT_QUALITY; else if (strcmp(s,"DRAFT_QUALITY")==0) return DRAFT_QUALITY; else if (strcmp(s,"PROOF_QUALITY")==0) return PROOF_QUALITY; else if (strcmp(s,"DEFAULT_PITCH")==0) return DEFAULT_PITCH; else if (strcmp(s,"VARIABLE_PITCH")==0) return VARIABLE_PITCH; else if (strcmp(s,"FIXED_PITCH")==0) return FIXED_PITCH; else return 0; }

Chú ý: Trong ví dụ này có sử dụng hàm rand() để lấy một số ngẫu nhiên kiểu int, khi đó ta phải chia cho 256 để được một số từ 0 đến 255. Kết quả chương trình là:

170

5.5. Cửa sổ ảo cho việc hiện lại nội dung
Nội dung hiển thị trên cửa sổ cho dù văn bản hay hình ảnh đều bị mất đi khi có sự thay đổi hoặc tác động lên cửa sổ, vì khi đó máy đều gửi thông điệp WM_PAINT đến cửa sổ và thực hiện hàm OnPaint(). Để nội dung được cập nhật lại chúng ta phải thực hiện trong hàm OnPaint() này của lớp CMyWin. Như trong chương 4, phương pháp tốt nhất là sử dụng cửa sổ ảo. Chúng ta tạo một vùng nhớ cửa sổ ảo tương ứng với cửa sổ thật và hiện nội dung lên cửa sổ ảo đó, khi đó chỉ cần chép cửa sổ ảo lên cửa sổ thật trong hàm OnPaint(). Cửa sổ ảo đóng vai trò lưu nội dung cửa sổ cần hiện trên màn hình, nó là một vùng nhớ trên máy dưới dạng hình ảnh bitmap. Vì vậy cần sử dụng các đối tượng của các lớp sau: CDC, CBitmap, CBrush. Các bước làm giống như chương 4, sau đây là sơ đồ minh họa cho quy trình làm việc với cửa sổ ảo cho hiện nội dung:

171

Ngoài ra, trong các ứng dụng soạn thảo văn bản trên cửa sổ luôn có một thành phần con trỏ bàn phím dùng để xác định vị trí tác động của người dùng lên văn bản đó. Thành phần này gọi là caret, mặc định cửa sổ hiện lên không có caret nhưng chúng ta có thể tạo và hiển thị con trỏ này. Hàm tạo con trỏ bàn phím khai báo theo mẫu sau:
void void void CWnd::CreateCaret( CBitmap* pBitmap ); CWnd::CreateSolidCaret( int nWidth, int nHeight ); CWnd::CreateGrayCaret( int nWidth, int nHeight );

Trong đó con trỏ tạo ra có thể sử dụng một ảnh bitmap để làm biểu tượng hình ảnh hiện thị trên cửa sổ, ảnh này đưa vào hàm qua tham số pBitmap ở trên. Hai hàm sau tạo con trỏ có một màu hoặc đen hoặc xám, khi đó ta chỉ truyền vào độ rộng và chiều cao của con trỏ qua hai tham số nWidth và nHeight. Hàm hiện con trỏ lên cửa sổ:
void CWnd::ShowCaret( );

hoặc làm ẩn con trỏ

172

void

CWnd::HideCaret( );

Hàm đặt vị trí con trỏ trên cửa sổ:
static void PASCAL CWnd::SetCaretPos( POINT point );

hoặc lấy vị trí con trỏ nhấp nháy hiện tại
static CPoint PASCAL CWnd::GetCaretPos( );

Còn tốc độ nhấp nháy của con trỏ được quy định bởi hệ thống bằng cách thiết lập trong control panel.

Ví dụ 5.4
Lập trình hiện tọa độ của chuột lên cửa sổ qua cửa sổ ảo. Kết quả khi nhấn chuột lên cửa sổ ở kích thước nhỏ:

Kết quả khi phóng to:

173

Kết quả khi cửa sổ ở chế độ maximize:

5.6. Bài tập
Bài tập 5.1
Lập trình hiện một bài thơ tùy ý lên cửa sổ với chữ màu đỏ, không có nền.

Bài tập 5.2
Lập trình hiện dòng họ và tên của bạn nằm ở tâm cửa sổ trong mọi kích thước.

Bài tập 5.3
Lập trình cho phép gõ các chữ cái và hiện lên cửa sổ, khi bấm Enter phải viết xuống hàng. Các chữ cái viết liên tiếp nhau, không được đè lên nhau. Cho phép chọn phông chữ để hiển thị. Thực hiện qua cửa sổ ảo.

Bài tập 5.4
Lập trình cho hiện con trỏ bàn phím (caret) lên cửa sổ và dịch chuyển bằng các phím mũi tên, hoặc bấm chuột trái lên cửa sổ. Bước dịch chuyển bằng một đơn vị pixels.

174

Chương 6 CÁC ĐIỀU KHIỂN NÂNG CAO
6.1. Khởi động các điều khiển nâng cao
Trong chương này sẽ giới thiệu một phần khá đẹp đẽ đối với các ứng dụng trên Windows đó là các điều khiển thông dụng (common controls). Trong chương trước đã đề cập đến các điều khiển chuẩn của Windows như: button, checkbox, listbox,... Tuy nhiên từ Windows 95 trở đi có cung cấp một số điều khiển khá thuận tiện đối với người dùng, rất mềm dẻo và linh họat trong tương tác. Để lập trình sử dụng các điều khiển trong chương trình ta sử dụng thư viện MFC, các điều khiển có thể liệt kê ra như sau:
Đi u khi n Animation ComboBoxEx Date/Time Picker Drag List box Header Hot key Image list IP List View Month Calendar Pager Progress bar Property sheet Ý nghĩa Hi n th các t p AVI H p danh sách ch n m r ng Xác đ nh th i gian Danh sách ch n có th kéo m c ch n Tiêu đ c t T o phím nóng Danh sách hình nh Cung c p đ a ch IP Danh sách ch n có bi u tư ng Ch n ngày, tháng, năm Trang cu n các đi u khi n khác Thanh ch báo ti n trình th c hi n B ng thu c tính

175

Rebar Rich Edit Status Tab Toolbar Tooltip Trackbar Tree View Up-down(spin)

Thanh ch a các đi u khi n H p so n th o ph c t p Thanh tr ng thái Đi u khi n có các th ch n Thanh nút l nh Đi u khi n hư ng d n tr c ti p Thanh trư t B ng ch n dư i d ng cây Tăng/gi m

Mỗi điều khiển đều được định nghĩa qua một lớp trong MFC tương ứng, người lập trình phải nắm vững các lớp đó mới tạo và sử dụng được các điều khiển này. Trước hết chúng ta phải nạp tệp khai báo thư viện AFXCMN.H vào chương trình bằng lệnh:
#include<afxcmn.h>

Hơn nữa trong hệ thống phải có thư viện chương trình tương ứng là tệp COMCTL32.LIB. Các ứng dụng có sử dụng các điều khiển này phải gọi hàm API InitCommonControlsEx() một lần đầu tiên, hàm được khai báo như sau:
BOOL InitCommonControlsEx( LPINITCOMMONCONTROLSEX lpInitCtrls );

Hàm này thực hiện nạp thư viện động (DLL) vào hệ thống đang chạy để thực hiện cho các điều khiển, nếu thành công hàm trả lại giá trị TRUE và ngược lại. Trong đó tham số của hàm sẽ quy định điều khiển nào được khởi động, cấu trúc INITCOMMONCONTROLSEX của tham số có khai báo như sau:
typedef struct tagINITCOMMONCONTROLSEX { DWORD dwSize; DWORD dwICC; } INITCOMMONCONTROLSEX, *LPINITCOMMONCONTROLSEX; 176

Thành phần dwSize chứa kích thước của cấu trúc, giá trị dwICC quy định điều khiển nào được khởi động, là một trong các giá trị dưới đây:
Giá tr ICC_ANIMATE_CLASS ICC_BAR_CLASSES ICC_COOL_CLASSES ICC_DATE_CLASSES ICC_HOTKEY_CLASSES ICC_INTERNET_CLASSES ICC_LISTVIEW_CLASSES ICC_PAGESCROLLER_CLASS ICC_PROGRESS_CLASS ICC_TAB_CLASSES ICC_TREEVIEW_CLASSES ICC_UPDOWN_CLASS ICC_USEREX_CLASSES ICC_WIN95_CLASSES Ý nghĩa Hi n t p AVI Thanh tr ng thái, nút l nh, trư t và tooltip, thanh công c Thanh ch a các đi u khi n Ch n th i gian Phím nóng Đ a ch IP Danh sách bi u tư ng và tiêu đ c t Trang cu n các đi u khi n khác Thanh ch báo ti n trình Th ch n Cây hi n th Tăng/gi m H p so n th o Các đi u khi n c a Windows 95

Vậy đoạn lệnh để khởi động các điều khiển sẽ là:
INITCOMMONCONTROLSEX initCtrls; initCtrls.dwSize = sizeof(initCtrls); initCtrls.dwICC = ICC_BAR_CLASSES; InitCommonControlsEx(&initCtrls);

Thông thường chúng ta thực hiện khởi động các điều khiển này trong hàm khởi tạo ứng dụng InitInstance() của lớp ứng dụng CMyApp. Chú ý: Các điều khiển khi chúng ta làm việc qua các đối tượng của lớp tương ứng sẽ có vai trò như các cửa sổ và chúng sẽ là các cửa sổ con nằm bên trong cửa sổ chương trình. Tất cả các lớp ứng với các điều khiển đều dẫn xuất từ lớp CWnd, do đó chúng được kế thừa các hàm thành viên từ lớp CWnd.
177

6.2. Thanh công cụ và lớp CToolbarCtrl
Thanh công cụ luôn có trên mọi ứng dụng chương trình, nó là một cửa sổ chứa các nút lệnh vai trò tương ứng như các mục chọn trên thực đơn. Thanh công cụ được bao gói trong lớp CToolBarCtrl của MFC, tuy nhiên có thể sử dụng lớp CToolBar để thực hiện. Các bước tạo thanh công cụ: Bước 1: Tạo đối tượng CToolBarCtrl và gọi lệnh tạo Create() Bước 2: Thêm các nút lệnh và ảnh bitmap cho nút lệnh Bước 3: Thực hiện ánh xạ thông điệp chọn nút lệnh tương ứng

6.2.1. Tạo thanh công cụ
Để tạo một thanh công cụ, đầu tiên phải tạo một đối tượng kiểu lớp CToolBarCtrl và sau đó gọi lệnh Create() theo mẫu sau:
BOOL Create( DWORD st, RECT& rt, CWnd* pw, UINT ID );

Tham số st quy định kiểu thanh công cụ, kiểu này phải gồm giá trị WS_CHILD và có thể kết với các WS_BORDER, WS_VISIBLE. Một số kiểu khác như sau:
Giá tr TBSTYLE_TOOLTIPS TBSTYLE_WRAPABLE TBSTYLE_FLAT TBSTYLE_TRANSPARENT Ý nghĩa Thanh công c có tooltip Có th xu ng hàng n u quá dài Nút l nh không n i (W98/IE4) Thanh công c trong su t (W98/IE4)

và một số kiểu điều khiển chung có thể gắn vào cửa sổ như sau:
CCS_ADJUSTABLE CCS_BOTTOM CCS_LEFT CCS_NODIVIDER CCS_NOMOVEX CCS_NOMOVEY CCS_NOPARENTALIGN Có th đi u ch nh b i ngư i dùng N m N m v trí dư i cùng c a s bên trái

Không có đư ng chia v i c a s Không thay đ i kích thư c chi u ngang Không thay đ i kích thư c chi u d c Không t chuy n v trí trên/dư i c a s

178

CCS_NORESIZE CCS_RIGHT CCS_TOP CCS_VERT

Kích thư c và v trí c đ nh N m bên ph i N m phía trên

Căn theo chi u d c

Tham số rt thường được bỏ qua, quy định kích thước hình chữ nhật chứa thanh công cụ trong trường hợp kiểu có giá trị CCS_NORESIZE. Mặc định thanh công cụ nằm phía trên cửa sổ chứa, tự động điều chỉnh kích thước và không nên thay đổi các kiểu này. Tham số pw quy định cửa sổ mẹ chứa thanh công cụ và số hiệu là ID. Sau khi tạo xong chưa thể dùng thanh công cụ bởi chưa có các nút lệnh và các hình ảnh đặt trên nút lệnh.

6.2.2. Thêm nút lệnh vào thanh công cụ
BOOL CToolBarCtrl::AddButton(int num, LPTBBUTTON bts);

Trong đó num là số nút lệnh cần thêm nằm trong tham số bts, tham số bts chứa danh sách các nút lệnh có kiểu TBBUTTON khai báo theo mẫu sau:
typedef struct _TBBUTTON { int iBitmap; - chỉ sổ ảnh của nút lệnh int idCommand; - số hiệu chức năng khi ánh xạ thông ñiệp BYTE fsState; BYTE fsStyle; - trạng thái - kiểu nút lệnh - giá trị dành cho ứng dụng

DWORD dwData;

int iString; - số hiệu dòng chữ hiển thị cho nút lệnh } TBBUTTON;

Giá trị thành phần fsState có các giá trị sau:
Giá tr TBSTATE_CHECKED TBSTATE_ELLIPSES TBSTATE_ENABLE TBSTATE_HIDDEN Ý nghĩa Nút l nh b b m xu ng Hi n elíp khi ch nút l nh b c t Nút l nh có th b m Nút l nh d ng n

179

TBSTATE_INDETERMINATE TBSTATE_MARKED TBSTATE_PRESSED TBSTATE_WRAP

Nút l nh d ng m Nút l nh đư c đánh d u Nút l nh đang b b m xu ng Nút l nh ti p theo s xu ng hàng

Giá trị thành phần fsStyle có các giá trị sau:
Giá tr TBSTYLE_AUTOSIZE TBSTYLE_BUTTON TBSTYLE_CHECK TBSTYLE_SEP Ý nghĩa T đ ng đi u ch nh kích thư c so v i văn b n hi n th Nút l nh d ng chu n Nút l nh n i chìm khi b m nh Nút l nh d ng phân cách, khi đó tham s idCommand ph i b ng 0.

6.2.3. Thêm ảnh bitmap vào thanh công cụ
int int CToolBarCtrl :: AddBitmap( int num, UINT ID ); CToolBarCtrl :: AddBitmap( int num, CBitmap* bmp );

Tham số num quy định số ảnh bitmap cần thêm, ID quy định số hiệu tài nguyên ảnh bitmap, bmp quy định các đối tượng ảnh bitmap đã nạp vào chương trình. Ảnh bitmap trên nút lệnh thanh công cụ có kích thước mặc định là 16x15 và có thể tạo ra bởi công cụ tạo ảnh bitmap của Visual C.

6.2.4. Thay đổi kích thước
Khi thanh công cụ tạo ra kích thước sẽ được điều chỉnh cho phù hợp với cửa sổ chứa hiện tại, tuy nhiên khi cửa sổ chứa thay đổi kích thước thì thanh công cụ không tự động thay đổi theo do đó chúng ta phải gọi hàm này trong thông điệp WM_SIZE theo mẫu sau:
void AutoSize( );

6.2.5. Một số hàm liên quan khác
Lệnh đặt trạng thái bấm xuống cho nút lệnh:
BOOL PressButton( int id, BOOL press = TRUE );

Lệnh ẩn nút lệnh:

180

BOOL

HideButton( int id, BOOL hide = TRUE );

Lệnh bật/tắt kích hoạt nút lệnh:
BOOL EnableButton( int id, BOOL enable = TRUE );

Trong đó tham số id quy định số hiệu nút lệnh cần thực hiện, các tham số thứ hai quy định trạng thái cho nút lệnh.

Ví dụ 6.1
Lập trình tạo một thanh công cụ có 5 nút lệnh với các chức năng vẽ các hình cơ bản (chữ nhật, tròn, elíp, đường thẳng, quạt) lên cửa sổ. Xử lý thông điệp bấm chuột trái để vẽ. Bước 1: Tạo các ảnh bitmap hiển thị cho các nút lệnh như sau:

Bước 2: Nội dung chương trình:
#include<afxwin.h> #include<afxcmn.h> #include"resource.h" UINT idCmd[5]={201,202,203,204,205}; class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { private: CDC mDC; CBitmap mBM; CBrush mBR; CToolBarCtrl tb; int idChon,mx,my; void InitToolbar(); public:

181

CMyWin(); afx_msg void OnLButtonDown(UINT nFlags,CPoint p); afx_msg void OnSize(UINT nType,int cx,int cy); afx_msg void OnPaint(); void OnCmd(UINT id); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_LBUTTONDOWN() ON_WM_SIZE() ON_WM_PAINT() ON_COMMAND_RANGE(idCmd[0],idCmd[4],OnCmd) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { INITCOMMONCONTROLSEX ic; ic.dwSize = sizeof(ic); ic.dwICC = ICC_BAR_CLASSES; InitCommonControlsEx(&ic); m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 01 - Chuong 06"); mx=GetSystemMetrics(SM_CXSCREEN); my=GetSystemMetrics(SM_CYSCREEN); CClientDC dc(this); mDC.CreateCompatibleDC(&dc); mBM.CreateCompatibleBitmap(&dc,mx,my); mDC.SelectObject(&mBM); mBR.CreateSolidBrush(WHITE_BRUSH); mDC.PatBlt(0,0,mx,my,PATCOPY); InitToolbar();

182

idChon=-1; } void CMyWin::InitToolbar() { RECT rt; rt.left=rt.top=rt.right=rt.bottom=0; tb.Create(WS_CHILD|WS_BORDER|WS_VISIBLE,rt,this,1); TBBUTTON b[5]; for (int i=0;i<5;i++) { b[i].fsStyle = TBSTYLE_BUTTON; b[i].fsState = TBSTATE_ENABLED; b[i].iBitmap=i; b[i].idCommand=idCmd[i]; b[i].dwData = 0; b[i].iString = 0; } tb.AddButtons(5,b); tb.AddBitmap(5,IDB_BITMAP1); } void CMyWin::OnLButtonDown(UINT nFlags,CPoint p) { CBrush b; CPen p1; p1.CreatePen(PS_SOLID,1,RGB(0,0,255)); b.CreateHatchBrush(HS_CROSS,RGB(255,0,255)); mDC.SelectObject(&p1); mDC.SelectObject(&b); switch (idChon) { case 0: mDC.MoveTo(0,0); mDC.LineTo(p); break; case 1: mDC.Rectangle(p.x,p.y,p.x+100,p.y+50); break; case 2:

183

mDC.Arc(p.x,p.y,p.x+100,p.y+70,p.x,p.y,p.x,p.y); break; case 3: mDC.Ellipse(p.x,p.y,p.x+70,p.y+120); break; case 4: mDC.Pie(p.x,p.y,p.x+100,p.y+150,p.x,p.y,p.x+150,p.y); break; } p1.DeleteObject(); b.DeleteObject(); InvalidateRect(NULL); } void CMyWin::OnCmd(UINT id) { this->idChon = id - idCmd[0]; } void CMyWin::OnSize(UINT nType,int cx,int cy) { tb.AutoSize(); } void CMyWin::OnPaint() { RECT rt; CPaintDC dc(this); tb.GetWindowRect(&rt); dc.BitBlt(0,rt.bottom-rt.top,mx,my,&mDC,0,0,SRCCOPY); }

Trong chương trình các số hiệu của nút lệnh được lưu trong mảng idCmd với 5 phần tử, việc tạo thanh công cụ và nạp các nút lệnh được thực hiện trong hàm InitToolbar(). Thao tác vẽ được thực hiện trên cửa sổ ảo sau đó chép vào cửa sổ thật qua hàm xử lý thông điệp WM_PAINT. Kết quả chương trình sau khi chọn một số chức năng để vẽ như sau:

184

6.2.6. Thêm Tooltip vào nút lệnh, lớp CTooltipCtrl
Chúng ta thấy trên các ứng dụng của Windows các thanh công cụ có các mẫu văn bản nhỏ xuất hiện khi đưa chuột qua trên đó ta gọi là Tooltip. Để thêm Tooltip vào thanh công cụ ta thực hiện các phần như sau: Bước 1: Khi tạo thanh công cụ bằng lệnh Create ta phải thêm giá trị kiểu là TBSTYLE_TOOLTIPS. Bước 2: Trong hàm cửa sổ ánh xạ xử lý thông điệp WM_NOTIFY bằng macro sau:
ON_NOTIFY_EX( TTN_GETDISPINFO , 0 , OnTooltip)

Hàm xử lý thông điệp tương ứng là OnTooltip được khai báo theo mẫu sau:
afx_msg BOOL OnTooltip (UINT id,NMHDR *hdr, LRESULT *rs);

Trong đó id là số hiệu của điều khiển có tooltip, hai tham số rs và id không được sử dụng trong hàm xử lý thông điệp này. Các thông tin liên quan đến thông điệp được lưu trong tham số hdr có kiểu MNHDR, chúng ta sử dụng tham số này để xác định văn bản cho tooltip. Khi xử lý chúng ta phải ép con trỏ hdr thành con trỏ kiểu NMTTDISPINFO, được khai báo như sau:

185

typedef

struct

tagNMTTDISPINFO {

NMHDR LPTSTR char

hdr; lpszText; - văn bản hiển thị cho tooltip

szText[80]; - vùng nhớ lưu văn bản tooltip - số hiệu chương trình

HINSTANCE hinst; UINT uFlags;

} NMTTDISPINFO, FAR *LPNMTTDISPINFO;

Thành phần đầu hdr có kiểu cấu trúc MNHDR được khai báo như sau:
typedef struct tagNMHDR { HWND hwndFrom; - số hiệu cửa sổ chứa ñiều khiển UINT idFrom; - số hiệu ñiều khiển UINT code; - mã thông báo } NMHDR;

Trong hàm OnTooltip xử lý thông điệp WM_NOTIFY chúng ta đưa vào văn bản tooltip hiển thị cho điều khiển nút lệnh trên thanh công cụ qua tham số hdr, dựa vào giá trị idFrom của hdr để biết số hiệu nút lệnh.

Ví dụ 6.2
Lập trình tạo một thanh công cụ như ví dụ trước và có thêm tooltip hiển thị cho từng nút lệnh. Cho phép ẩn hiện toolbar bằng thực đơn. Tài nguyên ảnh bitmap cho các nút lệnh được tạo như ví dụ trước và nội dung chương trình như sau:
#include<afxwin.h> #include<afxcmn.h> #include"resource.h" UINT idCmd[5]={201,202,203,204,205}; class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd

186

{ private: CMenu mn; CToolBarCtrl tb; public: CMyWin(); afx_msg BOOL OnTTip(UINT id,NMHDR *hdr,LRESULT *rs); void OnCmd(UINT id); void InitToolbar(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_NOTIFY_EX(TTN_GETDISPINFO,0,OnTTip) ON_COMMAND_RANGE(ID_SHOW,ID_HIDE,OnCmd) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { INITCOMMONCONTROLSEX ic; ic.dwSize = sizeof(ic); ic.dwICC = ICC_BAR_CLASSES; InitCommonControlsEx(&ic); m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 02 - Chuong 06"); mn.CreateMenu(); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,ID_SHOW,"Show toolbar"); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,ID_HIDE,"Hide toolbar"); SetMenu(&mn); InitToolbar(); }

187

void CMyWin::InitToolbar() { RECT rt; rt.left=rt.top=rt.right=rt.bottom=0; tb.Create(WS_CHILD|WS_BORDER|WS_VISIBLE|TBSTYLE_TOOLTIPS,rt,this,1); TBBUTTON b[5]; for (int i=0;i<5;i++) { b[i].fsStyle = TBSTYLE_BUTTON; b[i].fsState = TBSTATE_ENABLED; b[i].iBitmap=i; b[i].idCommand=idCmd[i]; b[i].dwData = 0; b[i].iString = 0; } tb.AddButtons(5,b); tb.AddBitmap(5,IDB_BITMAP1); } afx_msg BOOL CMyWin::OnTTip(UINT id,NMHDR *hdr,LRESULT *rs) { switch (hdr->idFrom-idCmd[0]) { case 0: ((NMTTDISPINFO *)hdr)->lpszText = "Ve duong thang"; break; case 1: ((NMTTDISPINFO *)hdr)->lpszText = "Ve hinh chu nhat"; break; case 2: ((NMTTDISPINFO *)hdr)->lpszText = "Ve hinh tron"; break; case 3: ((NMTTDISPINFO *)hdr)->lpszText = "Ve hinh elip"; break; case 4: ((NMTTDISPINFO *)hdr)->lpszText = "Ve hinh quat"; break; } return TRUE; } void CMyWin::OnCmd(UINT id)

188

{ if (id==ID_SHOW) { tb.ShowWindow(SW_RESTORE); } else { tb.ShowWindow(SW_HIDE); } }

Kết quả chương trình là:

Ngoài ra chúng ta có thể dùng lớp CToolTipCtrl trong thư viện MFC để tạo và sử dụng tooltip cho các điều khiển khác thanh công cụ trên cửa sổ cũng như hộp thoại. Các bước thực hiện tạo và đặt tooltip sử dụng lớp CToolTipCtrl như sau: Bước 1: Tạo một đối tượng CToolTipCtrl. Bước 2: Gọi hàm tạo Create() theo mẫu sau:
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = 0 );

Bước 3: Thêm các điều khiển vào thực hiện tooltip bằng cách gọi hàm:
BOOL id); AddTool( CWnd* wnd, LPCTSTR txt, LPCRECT rt, UINT

Bước 4: Kích hoạt tooltip:
void Activate( BOOL bActivate );

189

Hàm tạo Create() có tham số pParentWnd quy định cửa sổ chứa đối tượng tooltip, dwStyle quy định kiểu cửa sổ tooltip bao gồm các giá trị TTS_ALWAYSTIP, WS_POPUP, WS_EX_TOOLWINDOW. Hàm thêm AddTool() có tham số wnd quy định cửa sổ chứa điều khiển cần thêm tooltip, txt là văn bản hiển thị cho tooltip, rt quy định vùng chữ nhật giới hạn tác động chuột cho hiện tooltip và id là số hiệu điều khiển cần thêm. Hàm Activate() bật hoặc tắt kích hoạt của tooltip, nếu tham số bActivate là TRUE thì bật và ngược lại.

6.3. Điều khiển Up-Down và Spin
Một điều khiển cho phép người dùng nhập dữ liệu dạng số bằng cách bấm chuột vào mũi tên lên xuống để tăng giảm giá trị, gọi là điều khiển spin. Điều khiển này có dạng như thanh cuộn nhưng chỉ có hai mũi tên, không có thanh trượt bar và con chạy. Sử dụng điều khiển này theo hai dạng, một là dưới dạng giống như một thanh cuộn scrollbar, hai là kết hợp với một đối tượng khác như editbox để hiện giá trị của điều khiển. Khi sử dụng kết hợp thì điều khiển được kết hợp gọi là buddy window. Điều khiển spin được bao gói bởi lớp CSpinButtonCtrl, do đó trước hết bạn phải tạo một đối tượng từ lớp này, ví dụ:
CSpinButtonCtrl spin;

6.3.1. Tạo điều khiển spin
BOOL Create( DWORD st, RECT& rt, CWnd* pw, UINT nID );

Tham số st quy định kiểu của spin, thông thường gồm các giá trị WS_CHILD, WS_VISIBLE, WS_BORDER. Tham số rt quy định kích thước và vị trí của spin. Cửa sổ mẹ chứa điều khiển là pw và nID là số hiệu của spin. Hàm tạo điều khiển spin trên (Create()) sẽ trả về giá trị TRUE nếu thành công, ngược lại trả về FALSE nếu bị lỗi.

190

6.3.2. Xử lý thông điệp của spin
Khi người dùng tác động lên spin sẽ phát sinh thông điệp WM_VSCROLL gửi tới cửa sổ mẹ. Bạn phải ánh xạ xử lý thông điệp này để thực hiện các xử lý tương ứng, như lấy giá trị của spin để hiện lên cửa sổ chẳng hạn. Hàm ánh xạ xử lý thông điệp này có mẫu khai báo như sau:
void OnVScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar );

Trong đó: + nSBCode chứa kiểu của spin khi họat động, bao gồm các giá trị sau:
Giá tr UDS_ALIGNLEFT UDS_ALIGNRIGHT UDS_ARROWKEYS UDS_AUTOBUDDY UDS_HORZ UDS_NOTHOUSANDS UDS_SETBUDDYINT Ý nghĩa Spin n m bên trái đi u khi n k t h p (buddy) Spin n m bên ph i buddy Cho phép s d ng phím mũi tên C a s buddy n m sau đi u khi n spin Spin chi u ngang (m c đ nh là chi u d c) Không s d ng d u ngăn cách 3 ch s T đ ng hi n th giá tr spin lên c a s buddy

Tuy nhiên tham số nSBCode này thường được đặt tự động, do vậy bạn không phải chọn giá trị cho tham số này. + nPos chứa giá trị của spin ứng với thông điệp gửi tới, + pScrollBar là con trỏ tới thanh cuộn tương ứng, giá trị này có ý nghĩa khi có nhiều spin trên cửa sổ và chúng ta phải dựa vào tham số này để biết giá trị của spin nào.

6.3.3. Một số hàm khác của spin
- Đặt hoặc lấy miền giới hạn của spin
int int CSpinButtonCtrl :: SetRange( int min, int max ); CSpinButtonCtrl :: GetRange( int &min, int &max );

- Đặt hoặc lấy giá trị của spin
int int CSpinButtonCtrl :: SetPos( int pos ); CSpinButtonCtrl :: GetPos(); 191

Trong đó min và max là giá trị nhỏ nhất và lớn nhất của spin, giá trị pos trong hàm SetPos() là giá trị cần đặt cho spin, hàm GetPos() trả về giá trị của spin.

Ví dụ 6.3
Lập trình tạo một spin theo kiểu độc lập trên cửa sổ và xử lý thông điệp WM_VSCROLL để hiển thị giá trị của spin lên cửa sổ. Nội dung chương trình như sau:
#include<afxwin.h> #include<afxcmn.h> #define IDD_SPIN 174 class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { private: CSpinButtonCtrl spin; public: CMyWin(); void OnVScroll(UINT nSBCode,UINT nPos,CScrollBar *pSB); void InitControl(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_VSCROLL() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin()

192

{ Create(NULL,"Vi du 03 - Chuong 06"); InitControl(); } void CMyWin::InitControl() { INITCOMMONCONTROLSEX ic; ic.dwSize = sizeof(ic); ic.dwICC = ICC_UPDOWN_CLASS; InitCommonControlsEx(&ic); RECT rt; rt.left = 150; rt.top = 50; rt.right = rt.bottom = rt.top+50; spin.Create(WS_VISIBLE|WS_CHILD|WS_BORDER,rt,this,IDD_SPIN); spin.SetRange(0,99); spin.SetPos(50); } void CMyWin::OnVScroll(UINT nSBCode,UINT nPos,CScrollBar *pSB) { if (pSB!=(CScrollBar*)&spin) return; CDC *dc=GetDC(); char s[20]; sprintf(s,"%d ",LOWORD(spin.GetPos())); dc->TextOut(100,60,s,strlen(s)); }

Trong hàm xử lý thông điệp WM_VSCROLL có sử dụng macro LOWORD để xác định giá trị 2 byte thấp của giá trị trả về từ hàm GetPos() trên spin. Kết quả của chương trình là:

193

6.3.4. Tạo spin kết hợp với editbox
Như chúng ta thấy trong ví dụ trên giá trị của spin được hiển thị lên cửa sổ trong hàm ánh xạ thông điệp OnVScroll(), để giá trị này tự động cập nhật khi tác động lên spin ta sử dụng kết hợp với editbox. Trên cửa sổ chúng ta phải tạo hai điều khiển editbox và spin, có thể sử dụng lớp CEdit để tạo một điều khiển editbox trên cửa sổ bằng lệnh Create() khai báo theo mẫu sau:
BOOL CEdit :: Create( DWORD st, RECT& rt, CWnd* pw, UINT nID );

Tham số st quy định kiểu editbox, rt quy định hình chữ nhật ứng với kích thước và vị trí editbox, pw là cửa sổ chứa và nID là số hiệu của editbox. Điều khiển spin tạo ra phải có thêm giá trị kiểu UDS_SETBUDDYINT và có thể các giá trị khác như bảng trên. Sau đó đặt kết hợp spin với editbox ta sử dụng hàm thành viên của lớp CSpinButtonCtrl như sau:
CWnd* SetBuddy( CWnd* pWndBuddy );

Tham số pWndBuddy quy định cửa sổ điều khiển sẽ được kết hợp, ở đây sẽ là điều khiển editbox. Hàm trả về cửa sổ kết hợp trước đó. Sau khi kết hợp editbox với spin thì giá trị của spin sẽ được cập nhật tự động khi có tác động thay đổi lên spin.

Ví dụ 6.4
Lập trình tạo spin kết hợp với editbox để hiện giá trị lên cửa sổ. Nội dung chương trình sẽ là:
#include<afxwin.h> #include<afxcmn.h> #define #define IDD_SPIN 174 IDD_EDIT 142

class CMyApp : public CWinApp { public: BOOL InitInstance(); };

194

class CMyWin : public CFrameWnd { private: CSpinButtonCtrl spin; CEdit edit; public: CMyWin(); void InitControl(); }; CMyApp theApp; BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 04 - Chuong 06"); InitControl(); } void CMyWin::InitControl() { INITCOMMONCONTROLSEX ic; ic.dwSize = sizeof(ic); ic.dwICC = ICC_UPDOWN_CLASS; InitCommonControlsEx(&ic); RECT rt; rt.left=100; rt.top=50; rt.right=rt.left+50; rt.bottom=rt.top+30; edit.Create(WS_VISIBLE|WS_CHILD|WS_BORDER,rt,this,IDD_EDIT); rt.left = 10; rt.top = 10; rt.right = rt.bottom = rt.top+40; spin.Create(WS_VISIBLE|WS_CHILD|WS_BORDER|UDS_SETBUDDYINT| UDS_ALIGNRIGHT,rt,this,IDD_SPIN); spin.SetBuddy(&edit); spin.SetRange(0,99); spin.SetPos(50); }

195

Kết quả chương trình sẽ là:

6.4. Điều khiển Slider và Progress Bar
Một điều khiển khác tương tự thanh cuộn gọi là thanh trượt (trackbar). Điều khiển này có một thanh trượt được chia theo thanh đơn vị từ giá trị min đến giá trị max, trên đó có một con chạy dịch chuyển theo từng đơn vị đó. Điều khiển slider được bao gói bởi lớp CSliderCtrl.

6.4.1. Tạo điều khiển Slider
BOOL CSliderCtrl :: Create(DWORD st,RECT& rt,CWnd* pw,UINT nID);

Trong đó tham số st quy định kiểu điều khiển slider thường gồm các giá trị WS_VISIBLE, WS_CHILD, WS_BORDER. Ngoài ra bạn nên đặt thêm giá trị TBS_AUTOTICKS vào tham số này để điều khiển có hiển thị thang chia, một số giá trị khác trong bảng sau. Kích thước và vị trí được quy định bởi tham số rt, pw là cửa số mẹ chứa điều khiển cần tạo và nID là số hiệu điều khiển.
Ki u đi u khi n slider TBS_AUTOTICKS TBS_TOOLTIPS TBS_NOTICKS TBS_HORZ TBS_VERT TBS_BOTTOM TBS_TOP Ý nghĩa T đ ng hi n th thang chia Có tooltip hư ng d n Không có thang chia Slider n m ngang Slider n m d c Thang chia n m phía dư i slider Thang chia n m phía trên

196

TBS_LEFT TBS_RIGHT TBS_BOTH

Thang chia n m phía trái Thang chia n m phía ph i Thang chia n m c hai phía

6.4.2. Xử lý thông điệp Slider
Khi người dùng tác động lên điều khiển slider máy sẽ phát sinh thông điệp WM_HSCROLL gửi tới cửa sổ chứa nó, bạn phải ánh xạ xử lý thông điệp này bằng hàm OnHScroll() khai báo theo mẫu sau:
void CSliderCtrl::OnHScroll( UINT nSB, UINT nPos, CScrollBar* pSB );

Tham số nSB chứa trạng thái của slider, bao gồm các giá trị trong bảng sau. nPos chứa giá trị của con chạy trên slider và pSB con trỏ tới điều khiển có thông điệp tương ứng. Nếu pSB là NULL thì điều khiển có thông điệp chính là thanh cuộn trên cửa sổ, ngược lại bạn dùng biến trỏ này so sánh với điều khiển đã tạo ra để xử lý yêu cầu.
Tr ng thái slider TB_BOTTOM TB_ENDTRACK TB_LINEDOWN TB_LINEUP TB_PAGEDOWN TB_PAGEUP TB_THUMBPOSITION TB_THUMBTRACK TB_TOP Ý nghĩa B m phím end, chuy n v giá tr min Sau khi nh phím end Nh n phím mũi tên ph i ho c xu ng Nh n phím mũi tên lên ho c trái Nh n phím pagedown Nh n phím pageup Chuy n con trư t b ng chu t Kéo th con trư t b ng chu t Phím home đư c nh n, chuy n t i giá tr max

6.4.3. Các hàm khác của lớp Slider
- Miền giới hạn của slider
void void SetRange( int nMin, int nMax, BOOL bRedraw = FALSE ); GetRange( int& nMin, int& nMax ) const;

- Giá trị con trượt trên slider
void SetPos( int nPos );

197

int

GetPos( ) const;

Tham số nMin và nMax quy định giá trị nhỏ nhất và lớn nhất trên thanh trượt, nPos là giá trị hiện tại cần đặt cho thanh trượt. Hàm GetPos() trả về giá trị hiện tại, tương tự hàm GetRange() trả về giá trị giới hạn thanh trượt.

Ví dụ 6.5
Lập trình tạo thanh trượt trên cửa sổ và xử lý thông điệp WM_HSCROLL để hiện giá trị con trượt lên cửa sổ. Nội dung chương trình là:
#include<afxwin.h> #include<afxcmn.h> #define IDD_SLIDER 174 class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { private: CSliderCtrl slider; public: CMyWin(); void InitControl(); afx_msg void OnHScroll(UINT nSB, UINT nPos, CScrollBar* pSB); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_HSCROLL() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin()

198

{ Create(NULL,"Vi du 05 - Chuong 06"); InitControl(); } void CMyWin::InitControl() { INITCOMMONCONTROLSEX ic; ic.dwSize = sizeof(ic); ic.dwICC = ICC_UPDOWN_CLASS; InitCommonControlsEx(&ic); RECT rt; rt.left=50; rt.top=50; rt.right=rt.left+250; rt.bottom=rt.top+30; slider.Create(WS_VISIBLE|WS_CHILD|WS_BORDER|TBS_AUTOTICKS, rt,this,IDD_SLIDER); slider.SetRange(0,50); slider.SetPos(15); } void CMyWin::OnHScroll(UINT nSB, UINT nPos, CScrollBar* pSB) { if (pSB!=(CScrollBar*)&slider) return; CDC *dc=GetDC(); char s[20]; sprintf(s,"%d ",LOWORD(slider.GetPos())); dc->TextOut(150,30,s,strlen(s)); }

Chương trình này thực hiện đăng ký lớp điều khiển mở rộng sử dụng tham số kiểu ICC_UPDOWN_CLASSES. Lệnh tạo điều khiển slider có kiểu gồm các giá trị thông thường WS_VISIBLE, WS_CHILD, WS_BORDER và có thêm kiểu TBS_AUTOTICKS để hiện thang chia đơn vị. Kết quả chạy chương trình là:

199

6.4.4. Điều khiển Progress bar
Điều khiển Progress bar là một thanh tiến trình, nó thường thể hiện cho một tiến trình công việc thực hiện trên máy tính. Thanh tiến trình được bao gói bởi lớp CProgressCtrl. - Tạo điều khiển thanh tiến trình
BOOL nID ); Create( DWORD st, RECT& rt, CWnd* pw, UINT

Trong đó tham số st quy định kiểu của thanh tiến trình thường là WS_CHILD, WS_VISIBLE, WS_BORDER. Kích thước và vị trí được quy định bởi tham số rt với kiểu RECT, pw là cửa sổ mẹ chứa điều khiển và nID là số hiệu của thanh tiến trình. Ngoài ra bạn có thể tạo thanh tiến trình có kiểu đứng và dạng trơn bằng cách thêm các giá trị kiểu sau: PBS_SMOOTH và PBS_VERTICAL. Tham số rt quy định kích thước và vị trí điều khiển thông thường chúng ta sử dụng hàm tạo lớp CRect để tạo trực tiếp theo cách sau:
CRect( left , top , right , bottom )

- Đặt và lấy miền giới hạn thanh tiến trình
void void void SetRange( short nLower, short nUpper ); SetRange32( int nLower, int nUpper ); GetRange( int& nLower, int& nUpper );

Tham số nLower là số giới hạn dưới (nhỏ nhất), nUpper là số giới hạn trên (lớn nhất). - Đặt và lấy giá trị hiện tại của thanh tiến trình
int int SetPos( int nPos ); GetPos( );

- Đặt bước nhảy và thực hiện bước nhảy
int int SetStep( int nStep ); StepIt( );

200

Tham số nStep quy định độ lớn bước nhảy của thanh tiến trình. Hàm StepIt() thực hiện một bước nhảy, tăng giá trị hiện tại của thanh tiến trình thêm một lượng đúng bước nhảy. Khi thực hiện bước nhảy thanh tiến trình sẽ được cập nhật bởi dãy màu thể hiện tăng dần. Nếu thực hiện bước nhảy vượt quá giá trị giới hạn lớn nhất thì nó sẽ tự động chuyển về đầu.

Ví dụ 6.6
Lập trình tạo một thanh tiến trình trên cửa sổ và một nút lệnh cho phép bấm chuột để thực hiện bước nhảy, khi hết một tiến trình thì tự động đóng cửa sổ.
#include<afxwin.h> #include<afxcmn.h> #define IDD_PRG 174 #define IDD_PROGRESS 142 class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { private: CProgressCtrl prg; CButton bt; public: CMyWin(); void InitControl(); void OnProgress(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_COMMAND( IDD_PROGRESS , OnProgress ) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow();

201

return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 06 - Chuong 06"); InitControl(); } void CMyWin::InitControl() { INITCOMMONCONTROLSEX ic; ic.dwSize = sizeof(ic); ic.dwICC = ICC_PROGRESS_CLASS; InitCommonControlsEx(&ic); prg.Create(WS_VISIBLE|WS_CHILD|WS_BORDER, CRect(50,50,300,80),this,IDD_PRG); prg.SetRange(0,100); prg.SetStep(10); prg.SetPos(0); bt.Create("Progress",WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON ,CRect(130,100,210,130),this,IDD_PROGRESS); } void CMyWin::OnProgress() { int mi,ma; prg.StepIt(); prg.GetRange(mi,ma); if (prg.GetPos()>=ma) DestroyWindow(); }

Chương trình tạo hai điều khiển thanh tiến trình sử dụng lớp CProgressCtrl và nút lệnh sử dụng lớp CButton. Ánh xạ xử lý thông điệp bấm nút lệnh để thực hiện bước nhảy cho thanh tiến trình. Lệnh tạo nút bấm từ lớp CButton như sau:
BOOL CButton::Create( LPCTSTR ct, DWORD st, RECT& rt, CWnd* pw, UINT nID );

Trong đó ct là tiêu đề hiển thị cho nút bấm, st là kiểu nút bấm gồm có WS_CHILD, WS_VISIBLE và BS_PUSHBUTTON, rt là tham số quy định kiểu và vị trí, pw là cửa sổ mẹ và nID là số hiệu cửa sổ.

202

Kết quả chương trình là:

6.5. Thanh trạng thái và lớp CStatusBar
Thanh trạng thái (status bar) thường có ở mọi cửa sổ ứng dụng nó hiển thị các tình trạng làm việc của ứng dụng và được gắn vào dưới cửa sổ ứng dụng. Thanh trạng thái đựơc bao gói bởi lớp CStatusBarCtrl, tuy nhiên chúng ta có thể sử dụng lớp CStatusBar thay thế.

6.5.1. Tạo thanh trạng thái
Trước hết bạn phải tạo đối tượng từ lớp CStatusBarCtrl và thực hiện hàm tạo điều khiển này theo mẫu sau:
BOOL Create( DWORD st, RECT& rt, CWnd* pw, UINT nID );

Tham số st quy định kiểu thanh trạng thái thường gồm WS_CHILD, WS_VISIBLE, WS_BORDER. Ngoài ra còn có các giá trị có thể dùng cho kiểu này ở bảng sau. Tham số rt sẽ bỏ qua, nếu đặt kiểu là CCS_NORESIZE thì vị trí và kích thước được quy định trong tham số rt. Cửa sổ mẹ chứa thanh trạng thái quy định bởi pw và nID là số hiệu thanh trạng thái.
Ki u thanh tr ng thái CCS_BOTTOM CCS_NODIVIDER CCS_NOHILITE CCS_NOMOVEY Ý nghĩa Thanh tr ng thái n m phía dư i c a s Không có đư ng chia gi a thanh tr ng thái v i c a s Không sáng lên khi n m phía trên c a s Không thay đ i chi u d c

203

CCS_NOPARENTALIGN CCS_NORESIZE CCS_TOP

Không t đ ng căn ch nh theo c a s Không thay đ i kích thư c N m phía trên c a s

6.5.2. Đặt các phần trên thanh trạng thái
Thông thường thanh trạng thái hiển thị nhiều thông tin về trạng thái của chương trình, mối thông tin được hiển thị trong một ô gọi là part. Sau khi tạo thanh trạng thái sẽ tự động chia làm hai ô, tuy nhiên có thể đặt lại theo hàm sau:
BOOL SetParts( int nParts, int* pWidths );

Trong đó tham số nParts quy định số ô được chia trên thanh trạng thái và pWidths là mảng có số phần tử bằng số ô được chia, mỗi phần tử chứa tọa độ y quy định kích thước độ rộng của ô tương ứng.

6.5.3. Đặt văn bản hiển thị cho các ô
Để hiện văn bản vào một ô của thanh trạng thái sử dụng hàm sau:
BOOL SetText( LPCTSTR lpszText, int nPane, int nType );

Trong đó lpszText là văn bản cần hiển thị, nPane là thứ tự ô và nType quy định kiểu không sử dụng và đặt tham số này là 0. Thứ tự ô sẽ bắt đầu từ 0. Các bước thực hiện tạo và sử dụng thanh trạng thái: Bước 1: Tạo thanh trạng thái cho cửa sổ chương trình. Bước 2: Đặt số ô chia trên thanh trạng thái. Bước 3: Đặt hiển thị văn bản vào từng ô. Bước 4: Cập nhật văn bản hiển thị khi có sự thay đổi trong chương trình.

Ví dụ 6.7
Lập trình tạo thanh trạng thái trên cửa sổ và chia làm 3 ô hiển thị trạng thái ba phím CapsLock, NumLock, Insert. Xử lý thông điệp WM_KEYDOWN để kiểm tra trạng thái phím và hiển thị lại trạng thái đó. Nội dung chương trình sẽ như sau:
204

#include<afxwin.h> #include<afxcmn.h> #define IDD_STATUS 142 class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { private: CStatusBarCtrl st; int ins; public: CMyWin(); void InitControl(); afx_msg void OnKeyDown(UINT nChar,UINT nRep,UINT nFlags); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_WM_KEYDOWN() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 07 - Chuong 06",WS_OVERLAPPEDWINDOW, CRect(100,100,500,300)); InitControl(); ins=1; } void CMyWin::InitControl() { INITCOMMONCONTROLSEX ic; ic.dwSize = sizeof(ic); ic.dwICC = ICC_BAR_CLASSES;

205

InitCommonControlsEx(&ic); int y[3]={70,150,250}; st.Create(WS_VISIBLE|WS_CHILD|CCS_BOTTOM|SBARS_SIZEGRIP, CRect(0,0,0,0),this,IDD_STATUS); st.SetParts(3,y); OnKeyDown(0,0,0); } void CMyWin::OnKeyDown(UINT nChar,UINT nRep,UINT nFlags) { SHORT k = GetKeyState(VK_CAPITAL); if (k&1) st.SetText("CAP : ON",0,0); else st.SetText("CAP : OFF",0,0); k = GetKeyState(VK_NUMLOCK); if (k&1) st.SetText("NUM : ON",1,0); else st.SetText("NUM : OFF",1,0); if (nChar==VK_INSERT) ins=-ins; if (ins>0) st.SetText("INS : ON",2,0); else st.SetText("INS : OFF",2,0); }

Chương trình trên tạo một cửa sổ có kích thước được quy định bởi hàm tạo CRect(100,100,500,300) trong lệnh Create(). Trong hàm InitControl() của lớp CMyWin thực hiện khởi tạo lớp ICC_BAR_CLASSES cho việc tạo thanh trạng thái. Thanh trạng thái tạo ra được chia làm 3 ô và để hiển thị trạng thái 3 phím theo yêu cầu. Hàm OnKeyDown() thực hiện lấy trạng thái các phím và hiện vào từng ô trạng thái, sử dụng hàm lấy trạng thái phím đó là GetKeyState() có mẫu như sau:
SHORT GetKeyState( int nVirtKey );

Hàm trên của thư viện API và tham số nVirtKey quy định phím cần kiểm tra như VK_INSERT, VK_CAPITAL, VK_NUMLOCK,... hàm trả về giá trị kiểu SHORT, trong đó bít thấp nhất chứa trạng thái bật, tắt của phím kiểm tra. Với phím Insert chúng ta khai báo biến trạng thái ins tương ứng, khởi đầu đặt ins=1, sau mỗi lần nhấn phím này thì sẽ đảo dấu bằng lệnh ins=-ins. Dựa vào giá trị ins là âm hay dương sẽ hiển thị trạng thái tắt hay bật.

206

Kết quả chương trình là:

6.6. Điều khiển Tab
Điều khiển tab là một cửa sổ có nhiều thẻ để hiện các thông tin khác nhau với những giao tác khác nhau, mỗi thẻ gọi là item. Tại một thời điểm chỉ một thẻ được chọn và làm việc. Điều khiển này được bao gói bởi lớp CTabCtrl.

6.6.1. Tạo điều khiển Tab
Trước hết bạn phải tạo một đối tượng từ lớp CTabCtrl, sau đó thực hiện lệnh tạo bằng hàm khai báo theo mẫu sau:
BOOL Create( DWORD st, RECT& rt, CWnd* pw, UINT nID );

Tham số st quy định kiểu tab gồm các giá trị WS_CHILD, WS_VISIBLE và các giá trị ở bảng sau:
Giá tr TCS_BUTTONS TCS_FIXEDWIDTH TCS_MULTILINE TCS_TOOLTIPS TCS_SINGLELINE Ý nghĩa S d ng nút b m, không s d ng th T t c các th tab có kích thư c như nhau Th tab có nhi u dòng Có hi n th tooltip Th tab có m t dòng

Tham số rt quy định vị trí và kích thước của tab, cửa sổ mẹ chứa tab là pw và nID là số hiệu điều khiển tab.

207

6.6.2. Thiết lập các thẻ Tab
Sau khi tạo điều khiển Tab bạn phải thiết lập các thẻ tab, thực hiện chèn thêm các thẻ tab bằng một trong các mẫu hàm sau:
BOOL BOOL BOOL InsertItem( int idx, TCITEM* pItem ); InsertItem( int idx, LPCTSTR sItem ); InsertItem( int idx, LPCTSTR sItem, int nImage );

BOOL InsertItem( UINT nMask, int nItem, LPCTSTR sItem, int nImage, LPARAM lParam );

Trong đó tham số idx quy định số thứ tự của thẻ tab được thêm vào, thẻ tab bắt đầu từ số 0. pItem có kiểu TCITEM quy định các tham số của thẻ tab cần thêm, cấu trúc TCITEM được khai báo như sau:
typedefstruct tagTCITEM { UINT DWORD DWORD mask; dwState; dwStateMask;

LPSTR pszText; int int cchTextMax; iImage; lParam;

LPARAM } TCITEM;

Thành phần mask quy định thẻ tab sẽ hiển thị văn bản, hình ảnh hay lưu trong phần lParam, nó bao gồm một trong các giá trị sau:
Giá tr TCIF_IMAGE TCIF_PARAM TCIF_STATE TCIF_TEXT Ý nghĩa Thành ph n iImage ch a d li u c a th tab lParam ch a d li u dwState ch a d li u pszText ch a d li u

208

Thành phần mask có thể thêm TCIF_RTLREADING để quy định văn bản hiển thị trên thẻ tab từ phải sang trái. Thành phần iImage chứa số thứ tự hình ảnh hiển thị trên thẻ tab, các ảnh này được lưu trong đối tượng CImageList và được kết hợp với điều khiển tab. Tuy nhiên thông thường chúng ta sử dụng thẻ tab dạng văn bản, do đó bạn chỉ cần sử dụng mẫu 2 trong các mẫu trên:
BOOL InsertItem( int idx, LPCTSTR sItem );

Tham số sItem là văn bản cần hiển thị cho thẻ tab được thêm.

6.6.3. Các hàm khác của lớp CTabCtrl
- Thẻ tab hiện thời đang chọn Để đặt thẻ tab hiện thời hoặc xác định thẻ tab đang chọn ta sử dụng mẫu hàm sau:
int int SetCurSel( int nItem ); GetCurSel( ) const;

Trong đó nItem là số thứ tự thẻ tab cần đặt, hàm GetCurSel() trả về số thứ tự thẻ tab hiện thời đang chọn. - Thông tin về thẻ tab Để xác định thông tin về thẻ tab ta sử dụng hàm GetItem(), hoặc đặt thông tin cho thẻ tab sử dụng hàm SetItem(), hai hàm được khai báo theo mẫu sau:
BOOL BOOL SetItem( int nItem, TCITEM* pTabCtrlItem ); GetItem( int nItem, TCITEM* pTabCtrlItem ) const;

Trong đó nItem là số thứ tự thẻ tab, pTabCtrlItem chứa thông tin về thẻ tab có cấu trúc TCITEM như đã mô tả phần trước. - Nếu muốn điều chỉnh lại kích thước của tab ta sử dụng hàm sau:
void AdjustRect( BOOL bLarger, LPRECT lpRect );

Trong đó bLarger quy định đặt hay lấy kích thước của tab, nếu bằng 0 thì lệnh này trả về cấu trúc lpRect chứa kích thước, ngược lại bằng 1 sẽ đặt tab theo kích thước trong tham số lpRect.
209

6.6.4. Xử lý thông điệp liên quan đến Tab
Khi người dùng tác động lên điều khiển Tab, thông điệp WM_NOTIFY được sinh ra và gửi đến cửa sổ chứa nó. Điều khiển tab sinh ra hai mã thông điệp thay đổi lựa chọn thẻ tab gồm TCN_SELCHANGE và TCN_SELCHANGING, tương ứng với hai thời điểm đã chọn xong hoặc đang chọn thẻ tab. Để ánh xạ xử lý thông điệp cho điều khiển tab bạn sử dụng macro theo mẫu sau:
ON_NOTIFY ( mã-thông-ñiệp , số-hiệu , tên-hàm )

Trong đó mã thông điệp là hai giá trị như trên (TCN_SELCHANGE hoặc TCN_SELCHANGING), tiếp theo là số hiệu của điều khiển tab tương ứng khi tạo ra và tên hàm ánh xạ xử lý thông điệp. Mẫu khai báo hàm ánh xạ xử lý thông điệp WM_NOTIFY như sau:
afx_msg void tên-hàm ( NMHDR *hdr, LRESULT *rs);

Với mã thông điệp là TCN_SELCHANGE thì tham số rs không sử dụng, với mã là TCN_SELCHANGING thì chúng ta đặt giá trị rs là 0 nếu đồng ý thay đổi lựa chọn từ người dùng, ngược lại đặt khác 0.

Ví dụ 6.8
Lập trình tạo một điều khiển tab trên cửa sổ, xử lý thông điệp TCN_SELCHANGING để hỏi người sử dụng có đồng ý thay đổi lựa chọn thẻ tab hay không. Nội dung chương trình là:
#include<afxwin.h> #include<afxcmn.h> #define IDD_TAB 174 class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { private: CTabCtrl tab;

210

public: CMyWin(); void InitControl(); afx_msg void OnSelChanging(NMHDR *hdr,LRESULT *rs); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_NOTIFY( TCN_SELCHANGING, IDD_TAB, OnSelChanging ) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 08 - Chuong 06",WS_OVERLAPPEDWINDOW, CRect(100,100,700,500)); InitControl(); } void CMyWin::InitControl() { INITCOMMONCONTROLSEX ic; ic.dwSize = sizeof(ic); ic.dwICC = ICC_TAB_CLASSES; InitCommonControlsEx(&ic); RECT rt; GetClientRect(&rt); tab.Create(WS_VISIBLE|WS_CHILD,rt,this,IDD_TAB); tab.InsertItem(0,"One"); tab.InsertItem(1,"Two"); tab.InsertItem(2,"Three"); } void CMyWin::OnSelChanging(NMHDR *hdr,LRESULT *rs) { if (MessageBox("Dong y khong?","Hoi ban",MB_YESNO)==IDYES) *rs=0; else *rs=1; }

211

Kết quả thực hiện khi bạn chọn một thẻ tab sẽ là:

6.7. Điều khiển Tree View
Tree view là một điều khiển cho phép hiển thị thông tin lên cửa sổ dưới dạng cây, ví dụ như cửa sổ cây thư mục của Window Explore. Điều khiển này có rất nhiều tham số lựa chọn khi tạo và làm việc, tuy nhiên chúng ta chỉ tham khảo một số phần cơ bản. Lớp CTreeCtrl sẽ làm việc trên điều khiển này, tuy nhiên chúng ta có thể dùng lớp CTreeView sẽ đa dạng hơn.

6.7.1. Tạo điều khiển Tree View
Trước hết bạn phải tạo một đối tượng từ lớp CTreeCtrl và thực hiện hàm Create() theo mẫu sau:
BOOL Create( DWORD st, RECT& rt, CWnd* pw, UINT nID );

Trong đó st quy định kiểu của tree view bao gồm WS_CHILD, WS_VISIBLE và có thêm các giá trị sau:
Giá tr TVS_EDITLABELS TVS_HASLINES TVS_LINESATROOT TVS_HASBUTTONS Ý nghĩa Ngư i dùng có th thay đ i nhãn trên tree view Có đư ng n i gi a các nhánh trên tree view Có đư ng n i gi a nút g c v i các nhánh Có nút b m đ m r ng (+) hay thu h p (-) các nhánh

Tham số rt quy định kích thước và vị trí của điều khiển, pw là cửa sổ mẹ chứa điều khiển và nID là số hiệu cho điều khiển tạo ra.

212

6.7.2. Thêm các nút vào điều khiển Tree View
Sau khi tạo xong điều khiển tree view thì nó chưa có các mục (cây rỗng), bạn phải thêm các mục bằng hàm sau:
HTREEITEM InsertItem( TVINSERTSTRUCT *item );

Hoặc các hàm nạp chồng tương tự nhưng với các tham số được quy định khác nhau như sau:
HTREEITEM InsertItem( UINT nMask, LPCTSTR lpszItem, int nImage, int nSelectedImage, UINT nState, UINT nStateMask, LPARAM lParam, HTREEITEM hParent, HTREEITEM hInsertAfter ); HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST ); HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);

Tham số item của mẫu một là một cấu trúc TVINSERTSTRUCT lưu các thông tin của mục sẽ được thêm vào cây, cấu trúc có khai báo như sau:
typedefstruct tagTVINSERTSTRUCT { HTREEITEM HTREEITEM union { hParent; hInsertAfter; item;

TVITEMEX TVITEM

item;

} } TVINSERTSTRUCT;

213

Còn các mẫu hàm sau có tham số tương tự như các thành phần của cấu trúc ở trên. Trong đó hParent là định danh của mục mẹ, nếu mục cần thêm là gốc thì thành phần này phải có giá trị là TVI_ROOT. Giá trị hInsertAfter quy định mục thêm vào sẽ nằm sau mục nào trong cây (tree view), hoặc có thể một trong các giá trị sau:
Giá tr TVI_FIRST TVI_LAST TVI_SORT Ý nghĩa M c thêm vào s n m đ u tiên M c thêm vào n m cu i cùng Các m c thêm vào s đư c s p x p

Thành phần item trong cấu trúc TVINSERTSTRUCT ở trên lưu thông tin về mục trong cây, nó là một cấu trúc TVITEM bao gồm thành phần được khai báo như sau:
typedef UINT struct tagTVITEM { mask; hItem;

HTREEITEM UINT UINT LPSTR int int int int

state; stateMask; pszText;

cchTextMax; iImage; iSelectedImage; cChildren; lParam;

LPARAM } TVITEM;

Trong đó giá trị mask quy định mục trên cây có dữ liệu ở dạng nào, nó bao gồm các giá trị sau:

214

Giá tr TVIF_HANDLE TVIF_STATE TVIF_TEXT TVIF_IMAGE TVIF_SELECTEDIMAGE TVIF_CHILDREN TVIF_PARAM

Ý nghĩa hItem s ch a d li u state và stateMask s ch a d li u pszText và cchTextMax s ch a d li u iImage s ch a d li u iSelectedImage s ch a d li u cChildren s ch a d li u lParam s ch a d li u

Thông thường nội dung mục trên cây là văn bản nên mask có giá trị TVIF_TEXT và pszText lưu nội dung văn bản đó cùng với cchTextMax lưu độ tối đa của văn bản. Thành phần state của cấu trúc trên quy định mục tương ứng ở trạng thái nào, gồm có:
Giá tr TVIS_BOLD TVIS_DROPHILITED TVIS_EXPANDED TVIS_EXPANDEDONCE TVIS_SELECTED Ý nghĩa M c đư c in đ m M c đư c làm sáng lên khi kéo th M r ng toàn b các nhánh bên trong c a m c Ch m r ng nhánh hi n t i c a m c M c đang đư c ch n

Thành phần stateMask quy định giá trị trạng thái trong phần state sẽ đặt vào hay lấy ra. Nếu bạn cần đặt hình ảnh vào mục của cây, giá trị iImage chứa chỉ số ảnh trong đối tượng CImageList có liên kết với cây. Nếu không giá trị này đặt bằng -1 và iImageSelected lưu chỉ số ảnh khi mục được lựa chọn. Khi bạn lấy thông tin của một mục, thành phần cChildren chứa số mục con của mục đó trên cây. lParam chứa thông tin do ứng dụng tự định nghĩa. Bạn phải nhớ rằng mỗi lần chèn một mục vào cây phải lưu giá trị định danh của mục đó để sử dụng cho các việc khác.

215

6.7.3. Mở rộng và thu hẹp cây
Sau khi tạo cây và chèn các mục, bạn có thể hiển thị cây đó lên cửa sổ. Người sử dụng có thể bấm chuột vào một mục để mở rộng hoặc thu hẹp mục đó trên cây. Tuy nhiên chúng ta có thể sử dụng hàm thành viên thực hiện theo mẫu sau:
BOOL Expand( HTREEITEM hItem, UINT nCode );

Trong đó hItem lưu định danh của mục cần mở rộng, nCode quy định kiểu tác động lên mục bao gồm:
Giá tr TVE_COLLAPSE TVE_COLLAPSERESET TVE_EXPAND TVE_TOGGLE Ý nghĩa Thu h p cây con k t m c th c hi n hItem Thu h p cây con và xóa b nó M r ng cây con Thay đ i tr ng thái hi n t i c a cây con

6.7.4. Một số hàm khác của Tree View
- Xác định mục đang chọn
HTREEITEM GetSelectedItem( );

Hàm trả về giá trị kiểu HTREEITEM chứa thông tin mô tả về mục đang được chọn hiện tại. Cấu trúc này được mô tả ở phần trên. Hàm trả về giá trị NULL nếu không có mục nào được chọn. - Đặt mục chọn hiện thời
BOOL Select( HTREEITEM hItem, UINT nCode );

Thực hiện chọn mục có định danh là hItem. nCode quy định kiểu tác động lên mục đó khi chọn, bao gồm: TVGN_CARET chỉ thực hiện chọn mục, TVGN_DROPHILITE mục sẽ được đánh dấu khi kéo thả, TVGN_FIRSTVISIBLE cây sẽ cuộn lên hoặc xuống cho phép nhìn thấy lần đầu trên cửa sổ. - Lấy thông tin một mục bất kỳ trên cây
BOOL GetItem( TVITEM* pItem );

216

Trong đó pItem trỏ tới cấu trúc kiểu TVITEM như đã mô tả, trước khi thực hiện hàm này bạn phải đặt giá trị định danh của mục cần lấy vào thành phần hItem của cấu trúc này và mask phải có giá trị để quy định thông tin cần lấy, bao gồm: TVIF_HANDLE, TVIF_STATE, TVIF_TEXT,... - Hàm đặt thông tin cho một mục trên cây
BOOL SetItem( TVITEM* pItem );

Giống với hàm lấy thông tin GetItem(), bạn phải đặt các giá trị thông tin vào các thành phần hItem, mask và các thành phần khác tương ứng với giá trị đã đặt trong thành phần mask. - Xóa một mục trên cây
BOOL DeleteItem( HTREEITEM hItem );

Trong đó hItem chứa định danh của mục cần xóa. Khi xóa một mục thì tất cả các mục con bên trong đều bị xóa theo, vậy nếu bạn cần xóa hết cây thì đặt hItem bằng TVI_ROOT.

6.7.5. Xử lý các thông điệp liên quan đến Tree View
Khi truy nhập vào điều khiển cây sẽ phát sinh thông điệp WM_NOTIFY và gửi đến cửa sổ chứa nó. Có một số mã thông điệp ứng với việc truy xuất cây như sau:
Mã thông đi p TVN_DELETEITEM TVN_ITEMEXPANDING TVN_ITEMEXPANDED TVN_SELCHANGING TVN_SELCHANGED Ý nghĩa s ki n

Có m t m c b xóa M t nhánh đang đư c m hay đóng M t nhánh đã m hay đóng M t m c đang đư c ch n M t m c đã đư c ch n

Hàm xử lý thông điệp WM_NOTIFY được khai báo như sau:
afx_msg void func_name (NMHDR *hdr, LRESULT *rs);

Trong đó func_name là tên của hàm ánh xạ để xử lý thông điệp, tham số hdr sẽ trỏ tới cấu trúc NMTREEVIEW, cấu trúc này khai báo như sau:

217

typedef struct tagNMTREEVIEW { NMHDR hdr; UINT action; TVITEM itemOld; TVITEM itemNew; POINT ptDrag; } NMTREEVIEW, FAR *LPNMTREEVIEW;

Thành phần hdr là cấu trúc chuẩn NMHDR, mã thông điệp được lưu trong thành phần code của cấu trúc hdr. Định danh của cây phát sinh thông điệp tương ứng lưu trong thành phần hwndFrom của cấu trúc hdr. Giá trị action ở cấu trúc trên quy định thông tin tương ứng với thông điệp. Cấu trúc itemOld lưu thông tin của mục đã chọn trước đó và itemNew lưu thông tin mục đang được chọn. Giá trị tọa độ của chuột khi có thông điệp lưu ở thành phần ptDrag. Khi mã thông điệp là TVN_SELCHANGING và TVN_CHANGED thì itemOld chứa thông tin mục chọn trước đó và itemNew chứa thông tin mục đang chọn. Với thông điệp TVN_ITEMEXPANDING và TVN_ITEMEXPANDING thì itemNew chứa thông tin về mục mẹ của cây con được mở rộng. Với thông điệp TVN_DELETEITEM thì itemOld chứa thông tin của mục đã bị xóa. Trong trường hợp thông điệp là TVN_SELCHANGING và TVN_ITEMEXPANDING chúng ta có thể hủy bỏ công việc tương ứng bằng cách gán giá trị tham số rs là 0 ở trong hàm ánh xạ, ngược lại gán 1.

Ví dụ 6.9
Lập trình tạo một cây lên cửa sổ, cho phép thêm, sửa, xóa mục của cây bằng các chức năng của thực đơn. Chương trình sẽ có một hộp thoại để nhập tên mục, có một thực đơn, xử lý thông điệp chọn mục trên cây,... Khai báo tài nguyên chương trình:
218

// Các đ nh nghĩa h ng đ khai báo tài nguyên : resource.h #define IDD_TREE 174 #define IDR_MENU1 101 #define IDD_DIALOG1 102 #define IDC_EDIT1 1000 #define ID_NEW 40001 #define ID_RENAME 40002 #define ID_DELETE 40003 // Khai báo các tài nguyên g m : h p tho i và th c đơn IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 186, 82 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Information of item" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,37,61,50,14 PUSHBUTTON "Cancel",IDCANCEL,98,61,50,14 LTEXT "Enter name of item :",IDC_STATIC,17,20,68,11 EDITTEXT IDC_EDIT1,47,34,117,15,ES_AUTOHSCROLL END // Menu IDR_MENU1 MENU DISCARDABLE BEGIN MENUITEM "New", ID_NEW MENUITEM "Rename", ID_RENAME MENUITEM "Delete", ID_DELETE END

Nội dung chương trình là:
#include<afxwin.h> #include<afxcmn.h> #include"resource.h" class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CTreeCtrl tree; char iText[50]; CMenu mn; public: CMyWin();

219

void InitControl(); afx_msg void OnSelChanged(NMHDR *hdr,LRESULT *rs); void OnCmd(UINT id); DECLARE_MESSAGE_MAP() }; class CMyDialog : public CDialog { public: CMyDialog(UINT id, CWnd *ow) : CDialog(id,ow){} BOOL OnInitDialog(); void OnOK(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_NOTIFY( TVN_SELCHANGED, IDD_TREE, OnSelChanged ) ON_COMMAND_RANGE( ID_NEW , ID_DELETE , OnCmd ) END_MESSAGE_MAP() BEGIN_MESSAGE_MAP( CMyDialog, CDialog ) ON_COMMAND( IDOK, OnOK ) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 09 - Chuong 06",WS_OVERLAPPEDWINDOW, CRect(100,100,500,500)); mn.LoadMenu(IDR_MENU1); SetMenu(&mn); InitControl(); } void CMyWin::InitControl() { INITCOMMONCONTROLSEX ic; ic.dwSize = sizeof(ic); ic.dwICC = ICC_TREEVIEW_CLASSES;

220

InitCommonControlsEx(&ic); RECT rt; GetClientRect(&rt); tree.Create(WS_VISIBLE|WS_CHILD|TVS_HASLINES|TVS_LINESATROOT| TVS_HASBUTTONS,rt,this,IDD_TREE); //Init root item on tree tree.InsertItem("LHLK"); } void CMyWin::OnSelChanged(NMHDR *hdr,LRESULT *rs) { char str1[50],str2[100]; TVITEM tvi; tvi.hItem = (((NMTREEVIEW*)hdr)->itemNew).hItem; tvi.pszText = str1; tvi.cchTextMax = sizeof(str1)-1; tvi.mask = TVIF_TEXT | TVIF_HANDLE; tree.GetItem(&tvi); sprintf(str2,"Current item is %s CDC *dc=GetDC(); dc->SetTextColor(RGB(255,0,0)); dc->TextOut(10,300,str2,strlen(str2)); } void CMyWin::OnCmd(UINT id) { CMyDialog md(IDD_DIALOG1,this); TVINSERTSTRUCT tvs; TVITEM tvi; tvi.hItem = tree.GetSelectedItem(); switch (id) { case ID_NEW: iText[0]=NULL; md.DoModal(); if (strlen(iText)<=0) return; tvi.mask = TVIF_TEXT; tvi.pszText = iText; tvs.hInsertAfter = TVI_LAST; tvs.hParent = (tvi.hItem!=NULL)?tvi.hItem:TVI_ROOT; tvs.item = tvi; ",tvi.pszText);

221

tree.InsertItem(&tvs); break; case ID_RENAME: if (tvi.hItem==NULL) { MessageBox("Ban chua chon muc !"); return; } tvi.mask = TVIF_TEXT | TVIF_HANDLE; tvi.pszText = iText; tvi.cchTextMax = 50; tree.GetItem(&tvi); md.DoModal(); if (strlen(iText)<=0) return; tvi.pszText = iText; tvi.mask = TVIF_TEXT | TVIF_HANDLE; tree.SetItem(&tvi); break; case ID_DELETE: tree.DeleteItem(tvi.hItem); break; } } BOOL CMyDialog::OnInitDialog() { CDialog::OnInitDialog(); CMyWin *mw = (CMyWin*)GetParent(); CEdit *ed = (CEdit*)GetDlgItem(IDC_EDIT1); ed->SetWindowText(mw->iText); return TRUE; } void CMyDialog::OnOK() { CMyWin *mw = (CMyWin*)GetParent(); CEdit *ed = (CEdit*)GetDlgItem(IDC_EDIT1); ed->GetWindowText(mw->iText,50); CDialog::OnOK(); }

Chúng ta thực hiện tạo cây trong hàm InitControl() của lớp CMyWin, trong đó chỉ tạo một mục gốc của cây.

222

Hàm OnSelChanged() ánh xạ để xử lý lựa chọn mục của thông điệp TVN_SELCHANGED, lấy thông tin của mục đang chọn và hiện lên qua CDC của cửa sổ. Hàm OnCmd() xử lý thông điệp thực đơn thực hiện các chức năng thêm một mục mới, đổi tên một mục hoặc xóa một mục. Với hai chức năng thêm mới hoặc đổi tên mục đều phải nhập tên mục qua hộp thoại tạo ra từ lớp CMyDialog. Lớp hộp thoại có viết đè hàm OnInitDialog() để lấy tên mục đang chọn và điền vào hộp nhập tên mục khi sửa tên, hàm OnOK() để lấy nội dung tên mục đã nhập trong điều khiển editbox đưa vào thành viên iText của lớp cửa sổ. Kết quả chương trình là:

223

6.8. Điều khiển Month Calendar
Điều khiển month calendar cho phép người dùng lựa chọn thông tin ngày tháng qua một giao diện rất thuận tiện, như bạn đã thấy hầu hết các ứng dụng trên Windows. Mỗi thời điểm nó hiển thị các ngày và tuần của một hay nhiều tháng từ đó bạn chỉ nhấn chuột vào ngày cần chọn. Điều khiển này được kết hợp từ rất nhiều điều khiển khác như popup-menu, up-down,... Điều khiển month calendar được bao gói bởi lớp CMonthCalCtrl.

6.8.1. Tạo điều khiển Month Calendar
Trước hết bạn phải tạo một đối tượng từ lớp CMonthCalCtrl và gọi hàm Create theo mẫu sau:
BOOL BOOL Create( DWORD st, RECT& rt, CWnd* pw, UINT nID ); Create( DWORD st, POINT& pt, CWnd* pw, UINT nID );

Trong đó tham số st quy định kiểu của điều khiển month calendar, nó bao gồm các giá trị chuẩn WS_VISIBLE, WS_CHILD, WS_BORDER và các giá trị trong bảng sau:
Giá tr MCS_DAYSTATE MCS_MULTISELECT MCS_NOTODAY MCS_NOTODAYCIRCLE MCS_WEEKNUMBERS Ý nghĩa Đi u khi n s yêu c u ngày đánh d u đ c bi t Cho phép ch n nhi u ngày trong kho ng Ngày hi n t i không hi n th Ngày hi n t i không đư c quay vòng S th t tu n đư c hi n th

Tham số pw quy định cửa sổ mẹ chứa điều khiển này và nID là số hiệu của điều khiển sẽ tạo ra, như các điều khiển trước. Ở trên có hai mẫu khai báo, mẫu thứ nhất yêu cầu tham số thứ hai là rt có kiểu RECT để quy định kích thước và vị trí của điều khiển, tuy nhiên bạn có thể thực hiện mẫu hai chỉ yêu cầu vị trí của điều khiển và kích thước sẽ tự động lựa chọn, đó là tham số pt có kiểu POINT. Chú ý: Điều khiển month calendar yêu cầu chương trình phải nạp thư viện <afxdtctl.h>.
224

6.8.2. Các hàm khác của CMonthCalCtrl
- Xác định ngày đang lựa chọn
BOOL BOOL GetCurSel( CTime& refDateTime ) const; GetCurSel( LPSYSTEMTIME pDateTime ) const;

Hàm trả về dữ liệu ngày đang chọn trên điều khiển month calendar, hai mẫu có kiểu tham số khác nhau. Hoặc bạn chọn kiểu trả về là CTime hoặc là cấu trúc SYSTEMTIME có khai báo như sau:
typedef struct _SYSTEMTIME { WORD wYear; - năm WORD wMonth; - tháng (1 ñến 12) WORD wDayOfWeek; - ngày trong tuấn (0 ñến 6) WORD wDay; - ngày trong tháng (1 ñến 31) WORD wHour; - giờ WORD wMinute; - phút WORD wSecond; - giây WORD wMilliseconds; - phần nghìn giây } SYSTEMTIME, *PSYSTEMTIME;

- Đặt lựa chọn ngày Tương tự hàm trên ta có hàm chọn ngày như sau:
BOOL BOOL SetCurSel( const CTime& refDateTime ); SetCurSel( const LPSYSTEMTIME pDateTime );

Chúng ta phải đưa vào ngày cần đặt qua tham số của hàm. - Xác định ngày hiện tại
BOOL BOOL const; CMonthCalCtrl::GetToday( CTime& refDateTime ) const; CMonthCalCtrl::GetToday( LPSYSTEMTIME pDateTime )

Hàm trả về ngày hiện tại qua tham số kiểu CTime hoặc SYSTEMTIME.
225

- Đặt ngày hiện tại
void void SetToday( const CTime* pDateTime ); SetToday( const LPSYSTEMTIME pDateTime );

Chúng ta phải đưa vào tham số giá trị ngày cần đặt hiện tại. - Thay đổi kích thước điều khiển trên cửa sổ Trước hết bạn phải xác định kích thước nhỏ nhất chứa một tháng trên điều khiển, sử dụng hàm GetMinReqRect() theo mẫu sau:
BOOL CMonthCalCtrl::GetMinReqRect( RECT* pRect ) const;

Hàm trả về kích thước qua tham số pRect. Khi cần thay đổi kích thước chúng ta sử dụng hàm của lớp CWnd theo mẫu như sau:
void CWnd :: MoveWindow( int x, int y, int nWidth, int nHeight, BOOL bRepaint = TRUE ); void CWnd :: MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );

Tham số x,y quy định vị trí cửa sổ cần chuyển tới, nWidth và nHeight quy định kích thước chiều ngang và dọc của điều khiển. Giá trị bRepaint sẽ tự động vẽ lại cửa sổ hay không sau khi thay đổi và dịch chuyển. Chú ý: Nếu bạn tăng kích thước chiều ngang lên gấp đôi so với ban đầu thì sẽ hiện hai tháng liên tiếp cùng một lúc trên điều khiển này.

Ví dụ 6.10
Lập trình tạo một điều khiển month calendar và hiện thông báo ngày được chọn nếu cần qua thực đơn. Nội dung chương trình như sau:
#include<afxwin.h> #include<afxcmn.h> #include<afxdtctl.h> #define IDM_SHOW 174 #define IDM_EXIT 142 #define IDD_MONTH 1976

226

class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMonthCalCtrl mc; CMenu mn; public: CMyWin(); void InitControl(); void OnShowDate(); void OnExit(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP( CMyWin , CFrameWnd ) ON_COMMAND( IDM_SHOW, OnShowDate) ON_COMMAND( IDM_EXIT, OnExit) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 10 - Chuong 06",WS_OVERLAPPEDWINDOW, CRect(100,100,700,500)); mn.CreateMenu(); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,IDM_SHOW,"Show date"); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,IDM_EXIT,"Exit"); SetMenu(&mn); InitControl(); } void CMyWin::InitControl()

227

{ INITCOMMONCONTROLSEX ic; ic.dwSize = sizeof(ic); ic.dwICC = ICC_DATE_CLASSES; InitCommonControlsEx(&ic); RECT rt; rt.left=rt.top=0; rt.right=rt.bottom=0; mc.Create(WS_VISIBLE|WS_CHILD|WS_BORDER,rt,this,IDD_MONTH); mc.GetMinReqRect(&rt); mc.MoveWindow(0,0,rt.right*2+10,rt.bottom); } void CMyWin::OnShowDate() { SYSTEMTIME st; mc.GetCurSel(&st); char s[50]; sprintf(s,"%d/%d/%d",st.wDay,st.wMonth,st.wYear); MessageBox(s,"Date is selected"); } void CMyWin::OnExit() { DestroyWindow(); }

Kết quả chương trình là:

Khi chọn một ngày và chọn chức năng “Show date” sẽ có kết quả như sau:

228

6.9. Bài tập
Bài tập 6.1
Lập trình tạo một thanh công cụ trên cửa sổ với các nút lệnh: Hiện bài thơ, Xóa, Thoát có ảnh tự tạo bằng công cụ của Visual C. Thực hiện ánh xạ thông điệp chọn các nút lệnh để hiện một bài thơ bất kỳ lên cửa sổ, xóa nội dung cửa sổ hoặc thoát khỏi chương trình.

Bài tập 6.2
Lập trình tạo ba điều khiển up-down trên cửa sổ để nhập ngày, tháng và năm sinh của bạn, ánh xạ thông điệp để hiện giá trị của các up-down tương ứng là ngày sinh của bạn lên cửa sổ mỗi lần thay đổi giá trị.

Bài tập 6.3
Lập trình tạo một điều khiển slider nằm dọc trên cửa sổ cho phép nhập giá trị từ 0 đến 10 và tạo một editbox. Ánh xạ thông điệp để hiện giá trị vào ô editbox mỗi khi thay đổi giá trị slider.

Bài tập 6.4
Lập trình tạo một điều khiển progress bar nằm dọc trên cửa sổ với miền giới hạn là [0,100] và một ô editbox. Ánh xạ thông điệp để hiện vào ô editbox giá trị của progress bar mỗi lần thay đổi trên điều khiển đó.

Bài tập 6.5
229

Lập trình tạo ba điều khiển progress bar nằm dọc trên cửa sổ với miền giới hạn là [0,1000], tương ứng là p1, p2, p3. Ánh xạ thông điệp để mỗi lần dịch chuyển chuột trên cửa sổ thì điều khiển p1 sẽ tăng, khi p1 tăng hết thì p2 sẽ tăng và p1 về 0, khi p2 tăng hết thì p3 sẽ tăng và p2 về 0, khi p3 tăng hết thì thoát khỏi chương trình (giống như một màn hình cài đặt thông thường).

Bài tập 6.6
Lập trình tạo một điều khiển thanh trạng thái trên cửa sổ, tạo các ô để hiện trạng thái các phím CAPS LOCK, SCROLL LOCK, NUM LOCK và hiện tọa độ của chuột.

Bài tập 6.7
Lập trình tạo một điều khiển tab có 7 thẻ tương ứng với tên 7 ngày trong tuần (Thu 2, Thu 3,..., Chu nhat). Ánh xạ thông điệp để hiện tên thẻ tab lên cửa sổ mỗi lần lựa chọn.

Bài tập 6.8
Lập trình tạo một điều khiển tree view để hiện thông tin về một cây phả hệ của gia đình bạn, gồm ông bà, bố mẹ, anh em, con cái, cháu chắt,... Ánh xạ thông điệp để hiện tên mục được chọn trên cây lên cửa sổ.

Bài tập 6.9
Lập trình tạo một điều khiển month calendar cho phép chọn một ngày, và một điều khiển editbox. Ánh xạ thông điệp để hiện giá trị ngày được chọn vào ô điều khiển editbox mỗi lần bấm chuột trái lên cửa sổ.

Bài tập 6.10
Lập trình sử dụng các điều khiển đã học thực hiện một trong các chương trình sau: - Mô phỏng các lệnh của MS-DOS và hiện cây thư mục lên cửa sổ - Mô phỏng chương trình vẽ Paint của Windows - Mô phỏng chương trình Notepad của Windows - Trò chơi carô hai người trên máy tính

230

Chương 7 KIẾN TRÚC TÀI LIỆU/QUAN SÁT
7.1. Các khái niệm cơ bản
7.1.1. Kiến trúc tài liệu/quan sát
Trong các chương trước mỗi chương trình lập ra đều có hai đối tượng, một là đối tượng ứng dụng từ lớp CMyApp, hai là đối tượng cửa sổ từ lớp CMyWin. Kiểu ứng dụng này gọi là chương trình ứng dụng dạng cửa sổ (application/window), tiếp cận theo cách này chương trình khá đơn giản, có một số phần sẽ do MFC tự động cung cấp. Tuy nhiên sử dụng kiểu ứng dụng cửa sổ không tận dụng được các khả năng cung cấp từ các lớp cơ sở của thư viện MFC, chúng ta phải thực hiện mọi công việc để hiển thị dữ liệu lên cửa sổ. Hầu hết các dữ liệu đều phải tồn tại bên trong cửa sổ của chương trình và quy định cách hiển thị dữ liệu bởi cửa sổ đó. Tuy nhiên trong trường hợp cần hiển thị dữ liệu lên một khung nhìn (quan sát) khác của cùng dữ liệu sẽ khá phức tạp. Một chương trình Windows, dữ liệu là một khái niệm luôn tách rời với việc hiển thị. Nhưng trong kiểu ứng dụng cửa sổ thì hai thuộc tính này gắn kết với nhau trong cùng một lớp cửa sổ. Trong một chương trình kiểu tài liệu/quan sát, dữ liệu (tài liệu văn bản) sẽ tách rời so với cửa sổ hiển thị (khung nhìn). Chúng ta bao gói dữ liệu bên trong lớp CDocument và bao gói kỹ thuật hiển thị dữ liệu bên trong lớp CView. Lớp CView còn quản lý các tương tác giữa người dùng với dữ liệu. Có thể thấy kiến trúc tài liệu/quan sát cũng như hai phần dữ liệu và mã lệnh trong một chương trình.

231

Khi tạo một chương trình kiểu tài liệu/quan sát, lớp tài liệu phải được dẫn xuất từ lớp CDocument và lớp quan sát được dẫn xuất từ lớp CView. Khi tạo một chương trình kiểu tài liệu/quan sát, bạn vẫn phải tạo một lớp khung cửa sổ ứng dụng (frame window) và một lớp ứng dụng. Tuy nhiên lớp khung ứng dụng sẽ đóng vai trò hoàn toàn khác với những ví dụ chương trước, nó chỉ giữ vai trò là một khung của chương trình ứng dụng, còn mọi hiển thị trên cửa sổ ứng dụng sẽ do lớp CView thực hiện. Lớp CView sẽ đè hoàn toàn lên lớp CFrameWnd.

7.1.2. Khái niệm về tài liệu
Tài liệu (document) là dữ liệu (data) được xử lý bởi chương trình của bạn. Khái niệm tài liệu ở đây không chỉ là văn bản, nó bao gồm các định dạng dữ liệu mà chương trình xử lý. Ví dụ như một tệp văn bản, một tệp ảnh, một tệp âm thanh,... Trong kiến trúc tài liệu/quan sát có hai dạng chương trình đó là dạng SDI (Single Document Interface) hoặc dạng MDI (Multi Document Interface). Dạng MDI là một cửa sổ tại một thời điểm có thể chứa nhiều khung nhìn hiển thị các tài liệu, còn dạng SDI một cửa sổ chỉ chứa một khung nhìn tại một thời điểm.

7.1.3. Khái niệm về quan sát
Một quan sát (hay còn gọi là khung nhìn) sẽ hiển thị một tài liệu. Khung nhìn là một biểu diễn dạng vật lý của dữ liệu trong chương trình. Việc ánh xạ tài liệu lên khung nhìn là một tới nhiều, có nghĩa một tài liệu có thể được hiển thị trên nhiều khung nhìn, nhưng một khung nhìn chỉ hiển thị được một tài liệu gắn với nó. Khung nhìn không chỉ giới hạn là hiển thị trên màn hình, nó bao gồm cả những biểu diễn dạng vật lý của dữ liệu trên các thiết bị xuất thông tin như máy in. Vậy tài liệu là dữ liệu nguyên thủy và khung nhìn là một biểu diễn cụ thể của dữ liệu. Minh họa về kiến trúc tài liệu/quan sát:

232

7.1.4. Quản lý lưu trữ tài liệu
Các lớp tài liệu/quan sát cung cấp bởi thư viện MFC sẽ tự động lưu trữ tài liệu vào ổ đĩa, xử lý lưu trữ này gọi là serialization. Khái niệm serialization này thể hiện sự khăng khít giữa việc lưu trữ tài liệu vào ổ đĩa và lấy ra khi cần. Kỹ thuật cho khái niệm quản lý lưu trữ này được xây dựng bên trong lớp CDocument. Serialization là khái niệm quan trọng bởi vì nó định nghĩa những đặc tính của kiến trúc tài liệu/quan sát. Nếu bạn lập chương trình kiểu tài liệu/quan sát nhưng không cung cấp quản lý lưu trữ theo khái niệm này thì chưa thật sự là một ứng dụng tài liệu/quan sát.

7.1.5. Khái niệm tạo lập đối tượng động
Trong chương trình kiểu tài liệu/quan sát, các đối tượng ở dạng động (dynamic). Đó là khi chạy chương trình các đối tượng cửa sổ khung ứng dụng, tài liệu, khung nhìn được tạo lập tại thời điểm thi hành. Khác với các ứng dụng của các chương trước, các đối tượng được tạo ra ngay tại thời điểm lập trình. Việc tạo lập đối tượng động cần thiết ở đây bởi vì khi một tài liệu được nạp vào từ đĩa hoặc được tạo mới thì một khung nhìn phải được tạo ra tương ứng. Để làm việc này chúng ta sử dụng hai macro như sau:
DECLARE_DYNCREATE( class_name )

233

Macro này xác lập việc khai báo một lớp sẽ được tạo lập động tại thời điểm thi hành, tên lớp cần đặt là class_name và viết macro này vào bên trong (ở phần đầu tiên) khai báo của lớp đó.
IMPLEMENT_DYNCREATE( class_name, base_class_name )

Macro này xác lập việc cài đặt cụ thể một lớp động, class_name là tên lớp cần xác lập và base_class_name là tên lớp cơ sở của nó. Viết macro này vào đầu của phần viết mã lệnh các thành viên hàm của lớp động tương ứng. Kết hợp hai macro này với nhau cho phép một lớp động có thể sử dụng như một tham số của macro RUNTIME_CLASS sau:
RUNTIME_CLASS( class_name )

Trong đó class_name là tên lớp động tại thời điểm thi hành đã được xác lập bởi hai macro ở trên. Macro RUNTIME_CLASS trả về con trỏ tới cấu trúc CRuntimeClass, cấu trúc này lưu thông tin về lớp của đối tượng đang thi hành trong chương trình.

7.2. Cách tạo ứng dụng với kiến trúc tài liệu/quan sát
Để tạo một ứng dụng kiểu tài liệu/quan sát chúng ta thực hiện theo các bước sau: Bước 1: Dẫn xuất để xây dựng lớp cung cấp cho chương trình: Lớp ứng dụng CMyApp từ lớp CWinApp. Lớp khung cửa sổ CMyWin từ lớp CFrameWnd. Lớp tài liệu CMyDoc từ lớp CDocument. Lớp CMyView từ lớp CView. Bước 2: Cho phép tạo lập động các lớp CMyWin, CMyDoc và CMyView. Bước 3: Tạo một mẫu (template) để liên kết các lớp CMyWin, CMyDoc và CMyView với nhau. Bước 4: Phân tích cú pháp và xử lý dòng lệnh. Bước 5: Nạp chồng một số hàm thành viên để thực hiện như CObject :: Serialize() và CView :: OnDraw().

234

7.2.1. Dẫn xuất lớp CWinApp và CFrameWnd
Xây dựng hai lớp CMyApp và CMyWin dẫn xuất từ hai lớp CWinApp và CFrameWnd tương tự như chúng ta đã làm ở các chương trước. Tuy nhiên lớp CMyWin sẽ có ít thành viên hàm hơn và lớp CMyApp có thêm một số hàm. Lớp CMyWin phải là lớp động nên ta sử DECLARE_DYNCREATE trong khai báo lớp như sau:
class { DECLARE_DYNCREATE( CMyWin ) ... public: CMyWin(); ... DECLARE_MESSAGE_MAP() }; CMyWin : public CFrameWnd

dụng

macro

DECLARE_DYNCREATE ở đây là phạm vi private, tuy nhiên bạn có thể đặt nó ở phạm vi protected hoặc pubic nếu cần.

7.2.2. Dẫn xuất lớp CDocument
Lớp tài liệu của chương trình phải được dẫn xuất từ lớp CDocument, lớp này cũng phải đặt lớp động bằng macro DECLARE_DYNCREATE. Lớp này sẽ nhận và xử lý các lệnh liên quan đến tài liệu và cung cấp ánh xạ thông điệp để phản ánh kết quả xử lý đó. Lớp tài liệu chứa nhiều hàm thành viên xử lý dữ liệu, phải viết đè một số hàm trong lớp này để thực hiện theo ý đồ của chúng ta. Một hàm luôn được viết đè trong lớp này đó là OnNewDocument(), mẫu khai báo hàm như sau:
virtual BOOL CDocument :: OnNewDocument( );

Trong hàm này bạn phải gọi đến hàm của lớp cơ sở CDocument và trả về giá trị TRUE nếu thành công, ngược lại trả về FALSE.
235

Một hàm khác cũng được viết đè đó là Serialize(), nó là thành viên của lớp CObject. Mẫu khai báo lớp CMyDoc như sau:
class { DECLARE_DYNCREATE( CMyDoc ) ... public: CMyDoc(); BOOL void ... DECLARE_MESSAGE_MAP() }; OnNewDocument(); Serialize( CArchive &arch); CMyDoc : public CDocument

7.2.3. Dẫn xuất lớp CView
Lớp CView có chức năng hiển thị tài liệu trong lớp CDocument, khung nhìn này sẽ nằm đè lên cửa sổ khung ứng dụng và dựa vào các chức năng của nó như thay đổi kích thước. Lớp dẫn xuất CMyView của chương trình có thể kế thừa trực tiếp từ lớp CView hoặc một trong các lớp dẫn xuất từ nó như CScrollView, CCtrlView,... Lớp CMyView cũng phải được xác lập là lớp động bằng macro DECLARE_DYNCREATE và thu thập các tương tác của người dùng để xử lý tài liệu. Lớp CView có rất nhiều thành viên hàm, trong đó chúng ta phải viết đè hàm OnDraw(). Hàm này sẽ được gọi mỗi lần cần cập nhật hiển thị dữ liệu. Về chức năng hàm OnDraw() cũng tương tự hàm OnPaint() trong lớp ứng dụng dạng cửa sổ trước đây. Mẫu khai báo hàm này như sau:
virtual void CView :: OnDraw( CDC* pDC ) = 0;

236

Như vậy hàm OnDraw() là một hàm thuần ảo, có nghĩa là khi bạn xây dựng lớp CMyView dẫn xuất từ lớp CView sẽ phải viết lại hàm này mới trở thành lớp dùng được (lớp CView là lớp trừu tượng, không tạo đối tượng từ lớp này được). Tham số DC trong hàm sẽ trỏ đến ngữ cảnh thiết bị hiện tại của khung nhìn. Chúng ta sử dụng tham số này để thực hiện biểu diễn tài liệu lên cửa sổ khung nhìn tương ứng. Mẫu khai báo lớp CMyView như sau:
class { DECLARE_DYNCREATE( CMyView ) ... public: void ... DECLARE_MESSAGE_MAP() }; OnDraw(CDC *DC); CMyView : public CView

Trước khi cài đặt cụ thể các hàm cho các lớp trên trong tệp mã lệnh (CPP), chúng ta phải xác lập các lớp có thể tạo lập động khi thi hành bằng các macro sau:
IMPLEMENT_DYNCREATE( CMyWin , CFrameWnd ) IMPLEMENT_DYNCREATE( CMyDoc , CDocument ) IMPLEMENT_DYNCREATE( CMyView , CView )

7.2.4. Tạo mẫu tài liệu (document template)
Sau khi đã tạo xong các lớp theo yêu cầu như trên, bạn sử dụng các lớp đó để tạo một mẫu tài liệu. Mẫu tài liệu này thực hiện liên kết các lớp khung cửa sổ, tài liệu, khung nhìn với nhau và cho phép chúng làm việc như một đơn vị thống nhất. Chúng ta có thể tạo mẫu đa tài liệu hoặc mẫu đơn tài liệu

237

tương ứng với kiểu ứng dụng MDI hoặc SDI. Trong ví dụ của chương này chúng ta sẽ làm việc với mẫu đơn tài liệu. Mẫu đơn tài liệu là một đối tượng tạo ra từ lớp CSingleDocumentTemplate, lớp này chỉ có duy nhất một phương thức tạo khai báo theo mẫu sau:
CSingleDcumentTemplate :: CSingleDocTemplate( UINT nID, CRuntimeClass* CRuntimeClass* CRuntimeClass* pDocClass, pFrameClass, pViewClass );

Trong đó nID là số hiệu của tài nguyên sử dụng trong mẫu này, các tài nguyên bao gồm thực đơn và bảng phím tắt cung cấp cho cửa sổ khung ứng dụng, biểu tượng và bảng ký tự chứa tên của nhiều mục dữ liệu liên quan tới chương trình. pDocClass là một con trỏ tới lớp tài liệu, pFrameClass là con trỏ tới lớp khung ứng dụng, pViewClass là con trỏ tới lớp khung nhìn. Các con trỏ này nhận được bằng cách sử dụng macro RUNTIME_CLASS như sau:
RUNTIME_CLASS( class_name )

Ngoài ra, số hiệu nID chứa thêm các tài nguyên xâu ký tự theo thứ tự sau: ♦ Tiêu đề cửa sổ khung ứng dụng. ♦ Tên mặc định của tài liệu, nếu không thì máy sẽ lấy là “Untitled”. ♦ Tên kiểu tài liệu, cần khi chương trình xử lý nhiều kiểu tài liệu khác nhau. ♦ Tên của các kiểu tệp tin được sử dụng trong chương trình. ♦ Lọc tên tệp theo phần mở rộng, ví dụ như .TXT. ♦ Kiểu tệp sử dụng trong đăng ký hệ thống. ♦ Tên của kiểu tệp tài liệu sử dụng trong đăng ký hệ thống. Mỗi xâu ký tự cho một thành phần mô tả ở trên phải cách nhau dấu xuống dòng (\n). Nếu là xâu rỗng thì bạn cũng phải viết dấu xuống dòng. Tài nguyên xâu ký tự được khai báo theo từ khóa STRINGTABLE trong tệp RC như sau:

238

STRINGTABLE { stringID1 , “ xâu ký tự “ stringID2 , “ xâu ký tự “ ... stringIDn , “ xâu ký tự “ }

Ví dụ cho một khai báo tài nguyên xâu ký tự như sau:
STRINGTABLE { IDR_MYWIN “Doc/view program \n DocName \n DocType \n DocFileType (*.doc) \n .doc \n FileType \n NameType” }

Trong đó IDR_MYWIN là số hiệu tài nguyên đã định nghĩa trong tệp Resource.h bởi chỉ dẫn #define, “Doc/View program” là tiêu đề của ứng dụng, “DocName” là tên tài liệu mặc định, DocType là tên kiểu tài liệu, tên kiểu tệp là “DocFileType (*.doc)”, phần mở rộng của tệp là “.doc”, tên kiểu tài liệu và tên kiểu tệp đăng ký trong hệ thống sẽ là “FileType” và “NameType”.

7.2.5. Khởi tạo ứng dụng
Trong lớp ứng dụng CMyApp dẫn xuất từ lớp CWinApp chúng ta phải viết đè hàm InitInstance() để khởi tạo một ứng dụng, như đã thực hiện ở các ví dụ trước. Tuy nhiên trong kiểu ứng dụng tài liệu/quan sát này việc khởi tạo sẽ thực hiện: ♦ Tạo một đối tượng mẫu tài liệu. ♦ Gắn đối tượng đó vào ứng dụng. ♦ Phân tích cú pháp và xử lý các dòng lệnh. Mẫu hàm khởi tạo ứng dụng được viết như sau:

239

BOOL {

CMyApp :: InitInstance()

CSingleDocTemplate *DocPtr = new CSingleDocTemplate( IDR_MYWIN, RUNTIME_CLASS(CMyDoc), RUNTIME_CLASS(CMyWin), RUNTIME_CLASS(CMyView) ); AddDocTemplate(DocPtr); EnableShellOpen(); RegisterShellFileTypes(); CCommandLineInfo CLInfo; ParseCommandLine(CLInfo); if (!ProcessShellCommand(CLInfo)) return FALSE; return TRUE; }

Biến trỏ DocPtr kiểu CSingleDocumentTemplate để trỏ đến đối tượng được tạo ra bởi lệnh new tương ứng. Sử dụng hàm tạo với các tham số là các biến trỏ lớp, nhận được qua macro RUNTIME_CLASS. Sau đó gọi hàm AddDocTemplate() để thêm mẫu tài liệu này vào ứng dụng, hàm có mẫu khai báo như sau:
void CWinApp :: AddDocTemplate( CDocTemplate* pTemplate );

Trong đó pTemplate là con trỏ đến đối tượng lớp CDocTemplate, lớp này là lớp cơ sở của lớp CSingleDocumentTemplate. Sau khi thêm mẫu tài liệu, bạn phải gọi hàm EnableShellOpen() và RegisterShellFileTypes(), hai hàm này có mẫu sau:
void CWinApp :: EnableShellOpen( ); void CWinApp :: RegisterShellFileTypes( BOOL bCompat = FALSE );

240

Hàm EnableShellOpen() cho phép người dùng khởi động ứng dụng bằng cách bấm đúp chuột vào biểu tượng của kiểu tệp tin tương ứng. Hàm RegisterShellFileTypes() đăng ký kiểu tài liệu của ứng dụng vào hệ thống Windows. Nếu giá trị tham số bCompat là TRUE thì các tệp tin tạo ra bởi ứng dụng có thể in ra bằng cách kéo thả vào đối tượng máy in. Chú ý: Thông tin đăng ký được cung cấp bởi xâu ký tự trong tài nguyên (STRINGTABLE) và được sử dụng khi tạo đối tượng mẫu tài liệu. Tiếp theo tạo một đối tượng kiểu CCommandLineInfo để lưu thông tin về tham số dòng lệnh khi thực hiện chương trình. Tham số dòng lệnh do người dùng khởi động ứng dụng đưa vào, có thể dùng để mở tệp tài liệu ngay khi khởi động. Hàm ParseCommandLine() phân tích cú pháp của dòng lệnh, hàm ProcessShellCommand() xử lý dòng lệnh và xác định có hợp lệ hay không. Nếu hàm này trả về giá trị FALSE thì hàm khởi tạo InitInstance() ứng dụng phải trả về giá trị FALSE, ngược lại trả về giá trị TRUE.

Ví dụ 7.1
Lập chương trình dạng tài liệu/quan sát thực hiện vẽ lên cửa sổ hình chữ nhật. Chương trình được tổ chức thành 4 tệp: tệp chứa định nghĩa hằng tài nguyên (resource.h), tệp chứa khai báo tài nguyên (*.rc), tệp khai báo các lớp của chương trình (*.h) và tệp nội dung chương trình (*.cpp). Nội dung tệp định nghĩa hằng tài nguyên resource.h như sau:
#define IDR_MYWIN #define ID_FILE_EXIT 1 40003

Nội dung tệp khai báo tài nguyên thực đơn và xâu ký tự main.rc là:
#include "afxres.h" #include "afxres.rc" #include "resource.h" IDR_MYWIN MENU BEGIN POPUP "File" BEGIN DISCARDABLE

241

MENUITEM "Exit", ID_FILE_EXIT END END // String Table STRINGTABLE DISCARDABLE BEGIN IDR_MYWIN "Chuong trinh DocView - Vidu01/Chuong7\nLHLK\n\nLHK Type (*.lhk)\n.lhk\nFileType\nNameType" END

Nội dung tệp khai báo lớp của chương trình main.h là:
class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { DECLARE_DYNCREATE( CMyWin ) }; class CMyDoc : public CDocument { DECLARE_DYNCREATE( CMyDoc ) }; class CMyView : public CView { DECLARE_DYNCREATE( CMyView ) public: void OnDraw(CDC *DC); };

Nội dung tệp chương trình chính main.cpp là:
#include<afxwin.h> #include"main.h" #include"resource.h" CMyApp theApp; IMPLEMENT_DYNCREATE( CMyWin , CFrameWnd ) IMPLEMENT_DYNCREATE( CMyDoc , CDocument ) IMPLEMENT_DYNCREATE( CMyView , CView ) BOOL CMyApp::InitInstance() {

242

CSingleDocTemplate *DocPtr = new CSingleDocTemplate( IDR_MYWIN, RUNTIME_CLASS(CMyDoc), RUNTIME_CLASS(CMyWin), RUNTIME_CLASS(CMyView)); AddDocTemplate(DocPtr); EnableShellOpen(); RegisterShellFileTypes(); CCommandLineInfo CLInfo; ParseCommandLine(CLInfo); if (!ProcessShellCommand(CLInfo)) return FALSE; return TRUE; } void CMyView::OnDraw(CDC *DC) { CBrush b; b.CreateSolidBrush(RGB(255,0,0)); DC->FillRect(CRect(50,50,200,150),&b); }

Trong ví dụ này chúng ta chỉ thực hiện cài đặt hai hàm thành viên đó là hàm InitInstance() của lớp CMyApp để khởi tạo ứng dụng và hàm OnDraw() của lớp CMyView để vẽ hình chữ nhật theo yêu cầu. Chú ý: Trong tệp khai báo tài nguyên main.rc bạn phải nạp hai tệp tài nguyên chuẩn của MFC như sau: <afxres.h> và <afxres.rc>. Kết quả chương trình sẽ như sau:

243

7.3. Lưu trữ và đọc tài liệu trên ổ đĩa
Một công việc quan trọng trong lập trình kiến trúc tài liệu/quan sát đó là lưu trữ tài liệu và đọc tài liệu trên ổ đĩa. Lớp đối tượng CObject chứa hàm thành viên Serialize() sẽ được tự động gọi thực hiện khi một tài liệu được đọc hoặc lưu trữ (ghi). Lớp đối tượng CArchive sẽ nắm giữ và quản lý số hiệu tệp tin trên đĩa để thực hiện đọc và ghi. Lớp này nạp chồng hai toán tử nhập và xuất (<< và >>) cho việc thực hiện đọc/ghi tệp theo kiểu dữ liệu của ứng dụng. Trong chương trình kiểu tài liệu/quan sát, hàm CObject::Serialize() được gọi mỗi khi thực hiện đọc/ghi tài liệu. Hàm được khai báo như sau:
virtual void CObject :: Serialize( CArchive& ar );

Trong đó tham số ar là đối tượng lớp CArchive định nghĩa dòng vào/ra cho dữ liệu của tài liệu. Trong lớp tài liệu CMyDoc phải viết đè hàm này để thực hiện việc đọc/ghi dữ liệu trên tệp. Để biết tại thời điểm xử lý dòng vào/ra cho tài liệu trên đối tượng ar đang được đọc hoặc ghi chúng ta sử dụng hai hàm sau:
BOOL BOOL CArchive :: IsLoading( ) const; CArchive :: IsStoring( ) const;

Hàm IsLoading() trả về giá trị TRUE nếu tài liệu đang được đọc, tương tự hàm IsStoring() trả về giá trị TRUE nếu tài liệu đang được ghi. Trong phiên bản viết đè hàm Serialize() của lớp CMyDoc chúng ta thực hiện toán tử << để ghi dữ liệu và >> để đọc dữ liệu.

7.4. Một số hàm liên quan
7.4.1. Hàm cập nhật nội dung tài liệu lên khung nhìn
Lớp tài liệu CDocument có chứa hàm để cập nhật tài liệu lên khung nhìn đó là UpdateAllViews(), chúng ta gọi hàm này mỗi khi tài liệu có thay đổi. Hàm được khai báo như sau:
void UpdateAllViews( CView* pV,LPARAM lP=0L,CObject* pO=NULL );

244

Trong đó tham số pV là con trỏ đến đối tượng lớp CView, là đối tượng khung nhìn sẽ được cập nhật. Nếu tham số này là NULL thì tất cả các khung nhìn của tài liệu sẽ được cập nhật. Tham số lP kiểu LPARAM và pO là con trỏ kiểu CObject để tối ưu việc cập nhật nội dung. Gọi hàm UpdateAllViews() sẽ thực hiện hàm CView::OnUpdate(), nếu cần bạn có thể viết đè hàm này thực hiện tối ưu cập nhật.

7.4.2. Đặt trạng thái dữ liệu
Khi tài liệu có thay đổi bạn phải gọi hàm SetModifiedFlag() để thay đổi trạng thái cho tài liệu đó. Sau khi hàm này được gọi, người dùng sẽ được hỏi có thể lưu giữ tài liệu trước khi hủy bỏ nó. Hàm này có mẫu khai báo như sau:
void CDocument :: SetModifiedFlag( BOOL bModified = TRUE );

Tham số bModified bằng TRUE nếu muốn đặt có thay đổi tài liệu, ngược lại đặt bằng FALSE.

7.4.3. Xác định tài liệu trên khung nhìn
Để xác định đối tượng tài liệu đang gắn trên khung nhìn chúng ta sử dụng hàm GetDocument() của lớp CView. Hàm có khai báo như sau:
CDocument* CView :: GetDocument( ) const;

Hàm trả về con trỏ tới đối tượng tài liệu gắn trên khung nhìn được gọi. Chúng ta sử dụng hàm này để xác định tài liệu trong xử lý, ví dụ trong hàm OnDraw() để thực hiện biểu diễn tài liệu lên khung nhìn.

Ví dụ 7.2
Lập chương trình kiểu tài liệu/quan sát cho phép vẽ các đường gấp khúc theo các điểm nhấn chuột trái trên khung nhìn. Thực hiện hủy bỏ các nét vẽ, tạo bản vẽ mới, lưu bản vẽ, mở bản vẽ đã có, chọn màu trên hộp thoại màu. Các chức năng này chọn qua thực đơn chương trình. Dữ liệu về đường gấp khúc chỉ cần các tọa độ điểm, số lượng điểm do đó trong lớp tài liệu CMyDoc chúng ta khai báo hai thành viên lưu hai dữ liệu này: số điểm và mảng lưu các điểm vẽ.

245

Để chọn màu trên hộp thoại chúng ta sử dụng đối tượng lớp CColorDialog như đã đề cập trong chương trước. Các chức năng tạo mới, lưu giữ, đóng, mở tài liệu sẽ được ánh xạ thông điệp trong lớp ứng dụng CMyApp. Còn lại sẽ ánh xạ trong lớp CMyView. Nội dung tệp định nghĩa hằng tài nguyên resource.h là:
#define IDR_MYWIN #define ID_FILE_SAVEAS #define ID_FILE_EXIT #define ID_EDIT_COLOR 102 40006 40007 40008

Nội dung tệp khai báo tài nguyên main.rc là:
#include "afxres.h" #include "afxres.rc" #include "resource.h" IDR_MYWIN MENU DISCARDABLE BEGIN POPUP "File" BEGIN MENUITEM "New\tCtrl+N", ID_FILE_NEW MENUITEM "Open\tCtrl+O", ID_FILE_OPEN MENUITEM SEPARATOR MENUITEM "Save\tCtrl+S", ID_FILE_SAVE MENUITEM "Save as", ID_FILE_SAVEAS MENUITEM SEPARATOR MENUITEM "Exit\tCtrl+E", ID_FILE_EXIT END POPUP "Edit" BEGIN MENUITEM "Undo\tCtrl+U", ID_EDIT_UNDO MENUITEM "Color\tCtrl+C", ID_EDIT_COLOR END END // Accelerator IDR_MYWIN ACCELERATORS DISCARDABLE BEGIN "C", ID_FILE_CLOSE, VIRTKEY, CONTROL, NOINVERT "E", ID_FILE_EXIT, VIRTKEY, CONTROL, NOINVERT "N", ID_FILE_NEW, VIRTKEY, CONTROL, NOINVERT "O", ID_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT "S", ID_FILE_SAVE, VIRTKEY, CONTROL, NOINVERT "U", ID_EDIT_UNDO, VIRTKEY, CONTROL, NOINVERT

246

END // Icon IDR_MYWIN ICON DISCARDABLE "icon1.ico" // String Table STRINGTABLE DISCARDABLE BEGIN IDR_MYWIN "Chuong trinh Vidu02/Chuong7\nKhanh\n\nLHK Type (*.lhk)\n.lhk\nFileType\nNameType" END

Nội dung tệp biểu tượng chương trình icon1.ico là:

Nội dung tệp khai báo các lớp của chương trình main.h là:
#define MAXPOINT 150 class CMyApp : public CWinApp { public: BOOL InitInstance(); afx_msg void OnFileSaveAs(); afx_msg void OnFileExit(); DECLARE_MESSAGE_MAP() }; class CMyWin : public CFrameWnd { DECLARE_DYNCREATE(CMyWin) }; class CMyDoc : public CDocument { DECLARE_DYNCREATE(CMyDoc)

247

public: int mCount; COLORREF mColor; CPoint mPoint[MAXPOINT]; CMyDoc(); BOOL OnNewDocument(); void OnEditUndo(); void OnEditColor(); void Serialize(CArchive &ar); DECLARE_MESSAGE_MAP() }; class CMyView : public CView { DECLARE_DYNCREATE(CMyView) public: void OnDraw(CDC *DC); afx_msg void OnLButtonDown(UINT nFlags,CPoint point); DECLARE_MESSAGE_MAP() };

Nội dung tệp mã lệnh chương trình main.cpp là:
#include<afxwin.h> #include<afxdlgs.h> #include"resource.h" #include"main.h" CMyApp theApp; IMPLEMENT_DYNCREATE(CMyWin,CFrameWnd) IMPLEMENT_DYNCREATE(CMyDoc,CDocument) IMPLEMENT_DYNCREATE(CMyView,CView) BEGIN_MESSAGE_MAP(CMyApp,CWinApp) ON_COMMAND(ID_FILE_NEW,CWinApp::OnFileNew) ON_COMMAND(ID_FILE_OPEN,CWinApp::OnFileOpen) ON_COMMAND(ID_FILE_SAVEAS,OnFileSaveAs) ON_COMMAND(ID_FILE_EXIT,OnFileExit) END_MESSAGE_MAP() BEGIN_MESSAGE_MAP(CMyDoc,CDocument) ON_COMMAND(ID_EDIT_UNDO,OnEditUndo) ON_COMMAND(ID_EDIT_COLOR,OnEditColor) END_MESSAGE_MAP()

248

BEGIN_MESSAGE_MAP(CMyView,CView) ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { CSingleDocTemplate *p=new CSingleDocTemplate( IDR_MYWIN, RUNTIME_CLASS(CMyDoc), RUNTIME_CLASS(CMyWin), RUNTIME_CLASS(CMyView)); AddDocTemplate(p); EnableShellOpen(); RegisterShellFileTypes(); CCommandLineInfo cmd; ParseCommandLine(cmd); if (!ProcessShellCommand(cmd)) return FALSE; return TRUE; } void CMyApp::OnFileSaveAs() { } void CMyApp::OnFileExit() { CWnd *mw = GetMainWnd(); mw->DestroyWindow(); } CMyDoc::CMyDoc() { mCount=0; mColor=RGB(255,0,0); } void CMyDoc::Serialize(CArchive &ar) { int i; if (ar.IsLoading()) { ar>>mCount; ar>>mColor;

249

for (i=0;i<mCount;i++) ar>>mPoint[i]; } else { ar<<mCount; ar<<mColor; for (i=0;i<mCount;i++) ar<<mPoint[i]; } } BOOL CMyDoc::OnNewDocument() { return CDocument::OnNewDocument(); } void CMyDoc::OnEditUndo() { if (mCount>0) mCount-=1; UpdateAllViews(NULL); } void CMyDoc::OnEditColor() { CColorDialog cd; if (cd.DoModal()==IDOK) { mColor = cd.GetColor(); UpdateAllViews(NULL); } } void CMyView::OnDraw(CDC *DC) { CMyDoc *doc = (CMyDoc*)GetDocument(); CPen p; p.CreatePen(PS_SOLID,1,doc->mColor); DC->SelectObject(&p); DC->MoveTo(doc->mPoint[0]); for (int i=1;i<doc->mCount;i++) DC->LineTo(doc->mPoint[i]); } void CMyView::OnLButtonDown( UINT nFlags, CPoint point ) { CMyDoc *doc = (CMyDoc*)GetDocument(); if (doc->mCount==MAXPOINT) { MessageBox("Nhieu diem qua...!"); return;

250

} doc->mPoint[doc->mCount]=point; doc->mCount++; doc->SetModifiedFlag(); doc->UpdateAllViews(NULL); }

Kết quả chương trình sẽ như sau:

7.5. Bài tập
Bài tập 7.1
Lập chương trình kiểu tài liệu/quan sát để nhập văn bản từ bàn phím. Mỗi văn bản có thể chọn màu chữ, phông chữ để hiển thị trên cửa sổ cũng như lưu vào tệp. Các chức năng thực hiện qua lựa chọn thực đơn.

Bài tập 7.2
Lập trình kiểu tài liệu/quan sát cho phép vẽ các hình cơ bản lên cửa sổ như: hình chữ nhật, hình tròn/elíp, hình quạt, đường thẳng. Ngoài ra cho phép chọn màu vẽ, màu tô, kiểu tô để thực hiện. Dữ liệu mỗi hình vẽ bạn lưu gồm các phần sau: ký hiệu hình vẽ (C, T, Q, D,...); màu vẽ, màu tô, kiểu tô, dữ liệu về tọa độ điểm của hình đó.

Bài tập 7.3
251

Lập trình kiểu tài liệu/quan sát thực hiện giải phương trình bậc hai, dữ liệu gồm các hệ số a,b,c và các chức năng nhập dữ liệu, giải phương trình và thông báo kết quả.

Bài tập 7.4
Lập trình kiểu tài liệu/quan sát thực hiện giải bài toán ma trận gồm các chức năng sau: nhập ma trận mới, chuyển vị ma trận, tính lũy thừa ma trận vuông, chéo hóa ma trận,... Các chức năng thực hiện qua thực đơn. Bạn phải xây dựng lớp CMyView để hiển thị ma trận lên khung nhìn theo hàng, cột. Dữ liệu lưu trữ sẽ là ma trận nhập vào.

Bài tập 7.5
Lập trình kiểu tài liệu/quan sát thực hiện mô phỏng các lệnh của MS-DOS và hiển thị cây thư mục lên màn hình. Sử dụng lớp CTreeView để hiển thị cây thư mục. Dữ liệu cây thư mục bạn tự khai báo dưới dạng cây như sau: - Mỗi nút tương ứng với một thư mục. - Trong một nút có các phần sau: con trỏ đến nút mẹ (thư mục mẹ), con trỏ đến con đầu (thư mục con đầu tiên), con trỏ đến em kề (thư mục kề tiếp với nó cùng cấp) và phần nội dung tên của nút đó. Các lệnh thực hiện nhập vào qua một ô editbox ở phía dưới cửa sổ.

Bài tập 7.6
Lập trình kiểu tài liệu/quan sát mô phỏng trò chơi life. Life do tạp chí Scientific American đưa ra từ 10/1970. Trò chơi mô phỏng việc sinh ra, cuộc sống, chết và phát sinh lại các tế bào trong vũ trụ bao gồm mảng hai chiều các hình vuông. Mỗi hình vuông đại diện cho một tế bào, màu xanh tương ứng với tế bào sống, màu trắng là tế bào rỗng. Tại mỗi tế bào sau khi chết coi như tế bào đó là rỗng. Một ô tế bào sẽ có các hàng xóm bên cạnh được xác lập như sau: gồm có 8 tế bào trên, dưới, trái, phải và 4 góc đường chéo liền kề. Một bước phát triển sang thế hệ mới theo quy luật sau: ♦ Một tế bào sống (màu xanh) có chính xác hai hay ba hàng xóm sẽ sống qua thế hệ tiếp theo.
252

♦ Một tế bào rỗng có chính xác hai hàng xóm sẽ được sinh ra trong thế hệ tiếp theo. ♦ Một tế bào sống có ít hơn hai hay nhiều hơn ba hàng xóm sẽ chết trong thế hệ tiếp theo. Các chức năng khởi tạo thế hệ ban đầu, phát triển thế hệ tiếp theo,... đều thực hiện qua thực đơn hoặc phím tắt.

253

Chương 8 VÀO RA DỮ LIỆU VỚI Ổ ĐĨA VÀ MÁY IN
8.1. Vào ra dữ liệu trên ổ đĩa và lớp CStdioFile
8.1.1. Giới thiệu vào ra ổ đĩa
Như chúng ta đã biết (trình bày trong chương 7) công việc lưu trữ và đọc dữ liệu trên ổ đĩa được thực hiện một cách tự động thông qua hàm Serialize() của lớp CDocument trong thư viện MFC. Trong hàm này có một tham số ar kiểu lớp CArchive nắm giữ số hiệu tệp tin cần đọc và ghi. Mọi thao tác đọc ghi dữ liệu đều thực hiện trên đối tượng ar này. Đối với trường hợp chương trình có kiến trúc cửa sổ (application/window) thì việc đọc ghi tệp có thể thực hiện qua đối tượng lớp CStdioFile. Để thực hiện đọc ghi một tệp trên đĩa chúng ta phải làm những thao tác sau: - Xác định tên tệp cần thực hiện. - Mở tệp để đọc hoặc ghi. - Thực hiện đọc hoặc ghi theo chế độ đã mở. - Đóng tệp. Tuy nhiên công việc này sẽ được tự động thực hiện tương ứng với các số hiệu chức năng ID_FILE_SAVE hoặc ID_FILE_OPEN của hệ thống, bắt buộc trong chương trình kiểu tài liệu/quan sát phải có định nghĩa các chức năng đó trong thực đơn. Trong chương 8 này chúng tôi muốn trình bày một cách khác để thực hiện đọc ghi dữ liệu trên ổ đĩa một cách trực tiếp, theo nghĩa chúng ta phải thực hiện hết các thao tác trình bày ở trên cho một quá trình đọc ghi tệp.

254

8.1.2. Lớp CStdioFile
Thư viện MFC có cung cấp một lớp để làm việc này trực tiếp đó là lớp CStdioFile, lớp này dẫn xuất từ lớp CFile và có các thành viên như sau: - Thành viên dữ liệu
m_hFile

chứa số hiệu thẻ tệp khi làm việc trên hệ điều hành. - Mở tệp
virtual BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL );

Trong đó tham số lpszFileName là tên tệp (có thể có cả đường dẫn), còn tham số nOpenFlags quy định chế độ mở tệp bao gồm:
Giá tr quy đ nh CFile::modeCreate CFile::modeNoTruncate CFile::modeRead CFile::modeReadWrite CFile::modeWrite CFile::typeText CFile::typeBinary Ý nghĩa T o t p m i, n u t p đã có thì s b xóa K t h p v i modeCreate thì s không xóa t p đã có M t p đ ch đ c M t p đ đ c và ghi M t p đ ch ghi Ki u đ c ghi t p s là văn b n – text Ki u đ c ghi là nh phân

Tham số pError sẽ chứa kết quả lỗi nếu có, nếu không cần bạn có thể bỏ qua tham số này và mặc định là NULL. - Đóng tệp
virtual void Close( );

- Đọc tệp
virtual UINT Read( void* Buf, UINT Size ); DWORD ReadHuge( void* Buf, DWORD Size ); virtual LPTSTR BOOL ReadString( LPTSTR lpsz, UINT nMax ); ReadString(CString& rString);

255

Tham số Buf trỏ đến vùng nhớ chứa dữ liệu đọc ra, Size là số byte cần đọc. Hàm Read() sẽ đọc một lần nhỏ hơn 64Kb, hàm ReadHuge() có thể đọc với số lượng byte nhiều hơn. Hai hàm đều trả về số byte thực sự đã đọc thành công. Kết quả trả về có thể ít hơn số byte yêu cầu nếu tệp đã hết dữ liệu. Hàm ReadString() để đọc dữ liệu dạng văn bản với tham số lpsz là địa chỉ xâu và nMax là độ dài tối đa để đọc. - Ghi tệp
virtual void void virtual void Write( const void* Buf, UINT Size ); WriteHuge( const void* Buf, DWORD Size ); WriteString( LPCTSTR lpsz );

Tham số Buf trỏ đến vùng nhớ chứa dữ liệu cần ghi, Size là số byte cần ghi từ vùng nhớ vào tệp. Tương tự như đọc tệp, hàm Write() chỉ ghi một lần nhỏ hơn 64Kb và hàm WriteHuge() có thể ghi hơn. Hai hàm đều trả về số byte đã thực sự ghi thành công. Hàm WriteString() để ghi dữ liệu xâu nằm trong tham số lpsz vào tệp. - Đẩy vùng đệm lên tệp
virtual void Flush( );

Sau mỗi lần ghi tệp các bạn nên nhớ gọi hàm này để thực hiện chuyển dữ liệu ghi từ vùng đệm vào tệp mới được hoàn thành. Nếu gọi hàm đóng tệp Close() thì không cần gọi hàm này. - Chuyển đầu đọc/ghi trên tệp
virtual LONG Seek( LONG lOff, UINT nFrom );

Tham số lOff quy định khoảng cần dịch chuyển, nFrom quy định vị trí bắt đầu dịch chuyển bao gồm:
Giá tr CFile::begin CFile::current CFile::end Ý nghĩa Đ ut p Vv trí hi n th i Cu i t p

256

- Xác định độ dài tệp (tính bằng byte)
virtual DWORD GetLength( ) const;

- Xác định vị trí hiện thời của đầu đọc/ghi tệp
virtual DWORD GetPosition( ) const;

- Đổi tên tệp
static void Rename( LPCTSTR OldName, LPCTSTR NewName );

Tham số OldName là tên cũ của tệp cần đổi (bao gồm cả đường dẫn nếu cần), NewName là tên mới (không có đường dẫn). - Xóa tệp
static void Remove( LPCTSTR lpszFileName );

Ví dụ 8.1
Lập trình vẽ một hệ trục tọa độ lên cửa sổ, cho phép nhấn chuột trái để nhập các điểm và ghi vào tệp dưới hai dạng văn bản và nhị phân. Nội dung chương trình như sau:
#include<afxwin.h> #include<afxdlgs.h> #define MAXPOINT 500 #define ID_SAVE 174 #define ID_OPEN 142 #define ID_EXIT 149 class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { private: int nSize; CPoint pt[MAXPOINT]; public: CMyWin(); void OnSave();

257

void OnOpen(); void OnExit(); afx_msg void OnPaint(); afx_msg void OnLButtonDown(UINT nFlags,CPoint pt); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP(CMyWin,CFrameWnd) ON_COMMAND(ID_SAVE,OnSave) ON_COMMAND(ID_OPEN,OnOpen) ON_COMMAND(ID_EXIT,OnExit) ON_WM_LBUTTONDOWN() ON_WM_PAINT() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { nSize=0; Create(NULL,"Vi du 01 - Chuong 08"); CMenu mn; mn.CreateMenu(); mn.AppendMenu(MF_BYCOMMAND|MF_STRING,ID_OPEN,"Open"); mn.AppendMenu(MF_BYCOMMAND|MF_STRING,ID_SAVE,"Save"); mn.AppendMenu(MF_BYCOMMAND|MF_STRING,ID_EXIT,"Exit"); SetMenu(&mn); } void CMyWin::OnExit() { DestroyWindow(); } void CMyWin::OnOpen() {

258

CStdioFile f; CFileDialog fd(TRUE); if (fd.DoModal()==IDOK) { if (f.Open(fd.GetPathName(),CFile::modeRead)) { f.Read(&nSize,sizeof(nSize)); if (nSize>=MAXPOINT) nSize=MAXPOINT; for (int i=0;i<nSize;i++) f.Read(&pt[i],sizeof(pt[i])); f.Close(); InvalidateRect(NULL); } } } void CMyWin::OnSave() { CStdioFile f; CFileDialog fd(FALSE); if (fd.DoModal()==IDOK) { if (f.Open(fd.GetPathName(),CFile::modeCreate|CFile::modeWrite)) { f.Write(&nSize,sizeof(nSize)); for (int i=0;i<nSize;i++) f.Write(&pt[i],sizeof(pt[i])); f.Close(); } } } void CMyWin::OnPaint() { CDC *dc=GetDC(); RECT rt; GetClientRect(&rt); int gx = (rt.right-rt.left)/2; int gy = (rt.bottom-rt.top)/2; dc->MoveTo(gx,0); dc->LineTo(gx,2*gy); dc->MoveTo(0,gy); dc->LineTo(2*gx,gy); dc->MoveTo(gx-5,7); dc->LineTo(gx,0); dc->LineTo(gx+5,7); dc->MoveTo(2*gx-7,gy+5); dc->LineTo(2*gx,gy); dc->LineTo(2*gx-7,gy-5); for (int i=0;i<nSize;i++) dc->Ellipse(gx+pt[i].x-2,gy-pt[i].y-2,gx+pt[i].x+2,gy-pt[i].y+2); } void CMyWin::OnLButtonDown(UINT nFlags,CPoint pt) { RECT rt; GetClientRect(&rt);

259

int gx = (rt.right-rt.left)/2; int gy = (rt.bottom-rt.top)/2; if (nSize >= MAXPOINT) return; this->pt[nSize].x=pt.x-gx; this->pt[nSize].y=gy-pt.y; nSize++; CDC *dc=GetDC(); dc->Ellipse(pt.x-2,pt.y-2,pt.x+2,pt.y+2); }

Chương trình viết dưới dạng cửa sổ bình thường (không dùng kiến trúc tài liệu/quan sát). Thực hiện ghi tệp và đọc tệp trong hai chức năng ứng với hai hàm OnOpen() và OnSave() của lớp cửa sổ CMyWin. Trong hai hàm này có sử dụng đối tượng lớp CStdioFile để thực hiện đọc ghi tệp, trước đó dùng đối tượng lớp CFileDialog để mở hộp thoại chọn tên tệp cần lưu giữ hoặc cần mở. Để xác định hộp thoại là mở hay ghi ta đưa vào tham số của hàm tạo lớp CFileDialog giá trị tương ứng là TRUE hay FALSE, như ví dụ trên. Kết quả chương trình là:

260

8.2. Cơ chế xuất dữ liệu ra máy in trên Windows
Để xuất dữ liệu ra các thiết bị như màn hình, máy in chúng ta phải sử dụng ngữ cảnh thiết bị của màn hình hay máy in tương ứng. Cũng giống như màn hình đã được trình bày ở chương trước, xuất dữ liệu ra máy in thông qua ngữ cảnh máy in. Trước khi muốn in chúng ta phải xác định ngữ cảnh máy in, có thể dùng hàm CreateDC() để tạo ngữ cảnh thiết bị máy in thực hiện theo mẫu sau:
BOOL CDC :: CreateDC( LPCTSTR lpszDriverName, LPCTSTR lpszDeviceName, LPCTSTR lpszOutput, const void* lpInitData );

Trong đó lpszDriverName là tên trình điều khiển thiết bị máy in, lpszDeviceName là tên thiết bị máy in, lpszOutput là tên tệp nếu cần và lpInitData là dữ liệu khởi tạo. Tuy nhiên người lập trình sẽ không muốn phải xác định các tham số ở trên, phụ thuộc vào sự cài đặt trong hệ điều hành để chọn ra các máy tùy ý theo người dùng. Để làm điều này chúng ta sử dụng đối tượng lớp hộp thoại máy in CPrintDialog. Lớp này có các hàm thành viên như sau: - Hàm tạo đối tượng
CPrintDialog :: CPrintDialog( BOOL bSO, ... );

Hàm có khá nhiều tham số và hầu hết được đặt mặc định, tham số đầu có giá trị TRUE nếu chỉ thiết lập máy in, và FALSE có thể chọn máy in để in dữ liệu. Sau khi tạo xong đối tượng máy in bạn có thể gọi hàm DoModal() để hiện hộp thoại máy in cho người dùng lựa chọn, hoặc chọn mặc định như hàm sau. - Xác định máy in mặc định đang chọn trong hệ thống
BOOL GetDefaults( );

Hàm trả về giá trị TRUE nếu có máy in đang chọn trong hệ thống và ngược lại FALSE nếu không có máy in.
261

- Xác định ngữ cảnh máy in đã lựa chọn
HDC GetPrinterDC( ) const;

Hàm trả về giá trị kiểu HDC là ngữ cảnh máy in đã được chọn bởi người dùng qua hộp thoại hoặc lấy mặc định. Sau khi xác định được ngữ cảnh máy in qua đối tượng lớp CPrintDialog chúng ta gắn nó vào một đối tượng lớp CDC để thực hiện xuất dữ liệu, lệnh gắn như sau:
BOOL CDC :: Attach( HDC hDC );

Ví dụ để xác định ngữ cảnh máy in như sau:
CDC dc; CPrintDialog pd(FALSE); if (pd.DoModal() == IDOK) { dc.Attach( pd.GetPrinterDC() ); ... th c hi n xu t d li u ra máy in t i đây ... }

Các thao tác xuất dữ liệu ra máy in đều thực hiện qua đối tượng lớp CDC đã được gắn với ngữ cảnh máy in như trên. Cũng giống như xuất dữ liệu ra cửa sổ qua DC, chúng ta có thể sử dụng các hàm thành viên để xuất dữ liệu như văn bản, các lệnh vẽ,... Chúng ta thấy một quá trình in có thể rất nhiều trang, do đó trong quá trình xuất dữ liệu ra máy in chúng ta thực hiện các bước như sau: Bước 1: Bắt đầu in bằng cách gọi hàm StartDoc() theo mẫu sau:
int CDC :: StartDoc( LPDOCINFO lpDocInfo );

Trong đó tham số lpDocInfo là cấu trúc DOCINFO lưu các thông tin liên quan đến máy in đang chọn tương ứng, cấu trúc DOCINFO gồm các thành phần được khai báo như sau:
typedef struct { int cbSize; LPCTSTR lpszDocName;

262

}

LPCTSTR lpszOutput; LPCTSTR lpszDatatype; DWORD fwType; DOCINFO, *LPDOCINFO;

Thành phần cbSize lưu độ lớn của cấu trúc, lpszDocName là tên tài liệu cho quá trình in đó, lpszOutput là tên tệp nếu xuất ra tệp thường bằng NULL, hai thành phần sau không sử dụng là lpszDatattype đặt bằng NULL và fwType đặt bằng 0. Hàm StartDoc() trả về kết quả thành công ứng với một số dương đó là số hiệu công việc đang thực hiện trong hệ thống, ngược lại trả về giá trị 0 hoặc âm. Bước 2: Bắt đầu in một trang bằng cách gọi hàm StartPage() theo mẫu sau:
int StartPage( );

Bước 3: Thực hiện các lệnh xuất dữ liệu như văn bản, các lệnh vẽ,... Bắt đầu mỗi trang in chúng ta cần phải thiết lập các chế độ xuất dữ liệu nếu cần như màu sắc, tô nền như trên cửa sổ, sử dụng các đối tượng lớp CPen, CBrush,... và thiết lập chế độ ánh xạ đơn vị vẽ SetMapMode(),... Bước 4: Kết thúc trang in bằng lệnh sau:
int EndPage( );

Bước 5: Nếu còn dữ liệu thì lặp lại bước 2 để in một trang mới, nếu không sang bước 6. Bước 6: Kết thúc một quá trình in bằng lệnh sau:
int EndDoc( );

Hai hàm EndPage() và EndDoc() sẽ trả về kết quả in, bao gồm các giá trị với ý nghĩa như sau:
Giá tr SP_ERROR SP_OUTOFDISK SP_OUTOFMEMORY SP_USERABORT Ý nghĩa L i in Tràn đĩa

Trang b nh RAM máy tính K t thúc b i l a ch n ngư i dùng

263

Ví dụ 8.2
Lập trình vẽ một elíp lên cửa sổ và cho phép in ra khi chọn chức năng trên thực đơn. Để in ra trên giấy có kích thước theo quy định ta sử dụng chế độ ánh xạ là MM_LOMETRIC theo tỷ lệ một đơn vị vẽ tương ứng với 0,1 mm. Vậy 100 đơn vị lôgíc ứng với 1 cm. Nội dung chương trình là:
#include<afxwin.h> #include<afxdlgs.h> #define ID_PRINT 174 #define ID_DRAW 142 #define ID_EXIT 149 /* Khai báo l p ng d ng */ class CMyApp : public CWinApp { public: BOOL InitInstance(); }; /* Khai báo l p khung c a s ng d ng */ class CMyWin : public CFrameWnd { public: CMyWin(); void OnPrint(); void OnDraw(); void OnExit(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP(CMyWin,CFrameWnd) ON_COMMAND(ID_PRINT,OnPrint) ON_COMMAND(ID_DRAW,OnDraw) ON_COMMAND(ID_EXIT,OnExit) END_MESSAGE_MAP() BOOL CMyApp::InitInstance()

264

{ m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 02 - Chuong 08"); CMenu mn; mn.CreateMenu(); mn.AppendMenu(MF_BYCOMMAND|MF_STRING,ID_DRAW,"Draw"); mn.AppendMenu(MF_BYCOMMAND|MF_STRING,ID_PRINT,"Print"); mn.AppendMenu(MF_BYCOMMAND|MF_STRING,ID_EXIT,"Exit"); SetMenu(&mn); } void CMyWin::OnPrint() { CDC dc; CPen p; p.CreatePen(PS_SOLID,3,RGB(255,0,0)); CPrintDialog pd(FALSE); DOCINFO di; pd.DoModal(); dc.Attach(pd.GetPrinterDC()); di.cbSize=sizeof(di); di.fwType=0; di.lpszDatatype=NULL; di.lpszOutput=NULL; di.lpszDocName="Tai lieu in thu"; dc.StartDoc(&di); dc.StartPage(); dc.SelectObject(&p); dc.SetMapMode(MM_LOMETRIC); dc.Ellipse(0,0,1000,-1000); dc.EndPage(); dc.EndDoc(); MessageBox("Da in xong !"); dc.DeleteDC(); } void CMyWin::OnDraw()

265

{ CPen p; p.CreatePen(PS_SOLID,3,RGB(255,0,0)); CDC *dc=GetDC(); dc->SelectObject(&p); dc->SetMapMode(MM_LOMETRIC); dc->Ellipse(0,0,1000,-1000); } void CMyWin::OnExit() { DestroyWindow(); }

Trong hàm OnPrint() ở trên chúng ta sử dụng một đối tượng hộp thoại máy in (CPrintDialog), sau đó sử dụng hàm DoModal() để hiện lên màn hình một hộp thoại máy in cho phép người dùng chọn một máy in đã cài đặt trong hệ thống. Hàm GetPrinterDC() sẽ trả về ngữ cảnh thiết bị máy in đã được chọn. Kết quả chương trình khi chọn chức năng “Draw” là:

266

Khi chọn chức năng “Print” sẽ xuất hiện hộp thoại máy in:

Chọn máy in xong và bấm chức năng Print trên hộp thoại thì lệnh DoModal() sẽ trả về giá trị IDOK và thực hiện in. Quá trình in thực hiện bên trong cặp lệnh StartDoc() và EndDoc(), mỗi trang in thực hiện bên trong cặp lệnh StartPage() và EndPage(). Chúng ta sử dụng các hàm trong lớp CDC để in dữ liệu ra máy in như việc hiện lên cửa sổ. Trong ví dụ trên chỉ dùng lệnh Ellipse() để vẽ một hình tròn có màu đỏ. Trong cửa sổ máy in chúng ta có một tài liệu đang in:

267

8.3. Cách in tài liệu trong MFC
Thư viện MFC cung cấp các tiến trình in đơn giản hơn, các thao tác ở phần trên được gói trong các lớp của thư viện. Đặc biệt trong ứng dụng kiểu tài liệu/quan sát thì mọi thao tác này đều tự động làm và người lập trình chỉ thực hiện giống như với màn hình. Trong đó khung ứng dụng sẽ tự động cấp phát DC máy in cho người lập trình, sau đó tự giải phóng khi đã in xong. Các thao tác bắt đầu - StartDoc() và kết thúc - EndDoc() tiến trình in, bắt đầu – StartPage() và kết thúc EndPage() một trang in cũng được tự động gọi thực hiện. Ngoài ra cung cấp một hàm để thực hiện hủy bỏ tiến trình do người dùng yêu cầu. Người lập trình sử dụng tiến trình in qua cơ chế làm việc của lớp CView, một số hàm cần phải viết đè ở lớp dẫn xuất của CView với ý nghĩa như sau:

8.3.1. Hàm chuẩn bị công tác in
BOOL CView :: OnPreparePrinting( CPrintInfo* pInfo );

Hàm được gọi ngay khi bắt đầu in, viết đè hàm này để chọn máy in, gọi hàm tạo ngữ cảnh máy in, cung cấp các thông tin về in như số trang,... Tham số pInfo là con trỏ tới đối tượng lớp CPrintInfo lưu giữ thông tin về máy in và thông tin về tiến trình in. Trong phiên bản viết đè hàm OnPreparePrinting() bạn phải gọi hàm DoPreparePrinting() để hiện hộp thoại máy in cho người dùng lựa chọn và thực hiện tạo ngữ cảnh máy in nếu chọn xong. Hàm có khai báo như sau:
BOOL CView :: DoPreparePrinting( CPrintInfo* pInfo );

Hàm trả về giá trị TRUE nếu thực hiện thành công và ngược lại là giá trị FALSE. Kết quả hàm này cũng là kết quả hàm OnPreparePrinting() ở trên. Ngoài ra chúng ta có thể thiết lập các thông tin về tiến trình qua tham số pInfo của lớp CPrintInfo, bao gồm:
void void CView :: SetMinPage( UINT nMinPage ); CView :: SetMaxPage( UINT nMaxPage );

268

Hàm quy định số của trang in đầu tiên và số của trang in cuối cùng với tham số tương ứng là nMinPage hay nMaxPage. Hoặc ngược lại chúng ta có hàm GetMinPage() và GetMaxPage() để trả về hai giá trị tương ứng. Như vậy số trang in ra sẽ là nMaxPage - nMinPage + 1. Lớp CPrintInfo chứa thành phần m_strPageDesc mô tả định dạng số trang được in ra gồm một xâu ký tự chứa hai xâu con, xâu con thứ nhất mô tả cho hiển thị trang đơn và xâu con thứ hai mô tả hiển thị trang đôi, hai xâu cách nhau dấu '\n'. Mặc định là “Page %u\nPages %u-%u\n”, tuy nhiên bạn có thể quy định lại định dạng khác và thực hiện điều này trong hàm viết đè OnPreparePrinting(). Ví dụ về phiên bản viết đè như sau:
BOOL CMyView :: OnPreparePrinting(CPrintInfo* pInfo) { pInfo->SetMinPage(1); pInfo->SetMaxPage(5); return DoPreparePrinting(pInfo); }

8.3.2. Hàm bắt đầu in và kết thúc in
Như chúng ta biết trước một tiến trình in chúng ta phải thiết lập các chế độ cho ngữ cảnh máy in như xác định tổng số trang in ra dựa vào độ lớn của tài liệu in, tạo co chữ nếu cần thiết,... Trong phiên bản của lớp dẫn xuất từ CView chúng ta viết đè hàm OnBeginPrinting() để thực hiện các công việc ở trên và ngược lại hàm OnEndPrinting() để giải phóng các tài nguyên đã tạo ra. Hàm bắt đầu và kết thúc in được khai báo theo mẫu sau:
void void CView :: OnBeginPrinting( CDC* pDC, CPrintInfo* pInfo ); CView :: OnEndPrinting( CDC* pDC, CPrintInfo* pInfo );

Tham số pDC trỏ đến đối tượng lớp CDC biểu diễn ngữ cảnh của máy in, pInfo trỏ đến đối tượng lớp CPrintInfo được sử dụng ở hàm chuẩn bị in ở trên. Trong hàm chuẩn bị in chúng ta có thiết lập số trang in nhưng khi đó chưa xác định được tổng số trang in ra, tham số này phụ thuộc vào độ dài của tài
269

liệu và kích thước vùng in ra. Do đó có thể thực hiện trong hàm bắt đầu in OnBeginPrinting() như sau: Bước 1: Xác định độ cao của vùng in được qua lớp CDC
int CDC :: GetDeviceCaps( int nIndex ) const;

Tham số nIndex quy định chiều và đơn vị xác định độ lớn, thông thường chúng ta đặt là VERTSIZE hoặc HORZSIZE để lấy chiều cao hoặc chiều rộng theo đơn vị milimet. Bước 2: Xác định độ dài của tài liệu, dựa vào hàm GetDocument() trả về tài liệu để xác định độ lớn của tài liệu do chương trình chúng ta tạo và xử lý. Bạn nên tạo một hàm thành viên trong lớp tài liệu để xác định độ dài của tài liệu theo một đơn vị nào đó. Bước 3: Tính tỷ số độ dài tài liệu/độ cao của vùng in ta được số trang, tuy nhiên hai giá trị ở tỷ số trên phải cùng đơn vị mới chính xác, có thể là milimet. Bước 4: Dùng hai hàm SetMinPage() và SetMaxPage() để thiết lập số trang in ra cho đối tượng lớp CPrintInfo qua tham số pInfo. Ngoài ra chúng ta có thể tính toán và tạo độ co của chữ in ra trong trường hợp in văn bản cũng được thực hiện trong hàm này. Nói chung lớp CView cung cấp hai hàm này OnBeginPrinting() và OnEndPrinting() cho phép người lập trình thực hiện các công việc bắt đầu và kết thúc in cho một quá trình xuất dữ liệu ra máy in. Nếu không bạn không cần phải viết đè hai hàm này trong phiên bản lớp dẫn xuất của CView.

8.3.3. Hàm chuẩn bị ngữ cảnh cho một trang in
Như đã trình bày trước mỗi một trang in ra chúng ta phải thực hiện thiết lập các chế độ cho ngữ cảnh thiết bị máy in, MFC cung cấp hàm OnPrepareDC() trong lớp CView để thực hiện điều này. Hàm này sẽ được gọi mỗi lần in một trang tài liệu. Hàm được khai báo như sau:
void CView :: OnPrepareDC( CDC* pDC, CPrintInfo* pInfo = NULL );

270

Hàm này còn được gọi trong trường hợp xuất dữ liệu ra màn hình, khi đó pDC trỏ đến đối tượng lớp CDC biểu diễn ngữ cảnh màn hình và giá trị pInfo bằng NULL. Để biết tham số pDC của hàm này đang biểu diễn cho ngữ cảnh máy in hay không ta sử dụng hàm CDC :: IsPringting() trả về giá trị khác 0 là máy in, nếu bằng 0 thì ngữ cảnh tương ứng là của màn hình. Các thao tác thường thực hiện trong hàm này là: - Quy định gốc của khung nhìn trong vùng in ra sử dụng hàm SetViewportOrg(). - Tạo vùng cắt để giới hạn vùng in ra so với ban đầu của máy in dùng hàm CDC::SelectClipRgn() với tham số là đối tượng lớp CRgn xác định vùng giới hạn cần đặt. - Đặt chế độ ánh xạ đơn vị lôgíc trên ngữ cảnh thiết bị máy in bằng hàm SetMapMode(). Chú ý: Trong phiên bản viết đè hàm này chúng ta luôn phải gọi hàm cơ sở tương ứng CView::OnPrepareDC() ở lệnh đầu tiên.

8.3.4. Hàm thực hiện in
Sau khi hàm OnPrepareDC() được thực hiện xong, khung ứng dụng sẽ gọi hàm OnPrint() để kiểm tra tính hợp lý của ngữ cảnh máy in và chuyển qua lời gọi hàm OnDraw(). Hàm OnPrint() được khai báo như sau:
void CView :: OnPrint( CDC* pDC, CPrintInfo* pInfo );

Thực chất quá trình xuất dữ liệu ra máy in được thực hiện chính thức trong hàm OnDraw() theo từng trang một, còn hàm OnPrint() nếu cần viết đè trong phiên bản lớp dẫn xuất thực hiện in ra các tiêu đề trang, in ra số trang,... là những phần mà trong hàm OnDraw() không được thực hiện. Nếu mã lệnh cho việc xuất dữ liệu ra máy in và màn hình khác nhau thì trong phiên bản nạp chồng hàm OnPrint() chúng ta thực hiện luôn các thao tác đó mà không gọi đến hàm OnDraw().

271

Trong tham số pInfo trỏ tới đối tượng lớp CPrintInfo có thành viên dữ liệu m_nCurPage xác định số trang đang in hiện thời, do đó trong hàm OnPrint() chúng ta sử dụng giá trị này để biết nội dung cần in ra trên trang tương ứng. Chú ý: Nếu có thay đổi của ngữ cảnh máy in bởi hàm SetViewportOrg() hoặc SelectClipRgn() thì nên thực hiện trong hàm OnPrint() để các tiêu đề và số trang in ra không bị tác động.

8.3.5. Hàm ánh xạ thông điệp in chuẩn ID_FILE_PRINT
MFC cung cấp một hàm ánh xạ cho thông điệp in chuẩn đó là hàm CView::OnFilePrint(). Trong chương trình chúng ta có thể sử dụng số hiệu ID_FILE_PRINT này để tạo một chức năng in và ánh xạ tới hàm tương ứng hoặc tới hàm được viết đè. Sơ đồ quá trình in trong MFC có thể minh họa như sau:

272

8.3.6. Quan sát trước khi in
Hầu hết các ứng dụng trên Windows cho phép người sử dụng xem xét bố cục và nội dung trang trước khi xuất ra máy in. Công việc này được thực hiện thông qua hàm thành viên OnFilePrintPreview của lớp CView. Trong hàm này sẽ gọi đến hàm DoPrintPreview để hiển thị cửa sổ xem trang in. Phiên bản lớp dẫn xuất từ lớp CView bạn phải ánh xạ xử lý thông điệp ID_FILE_PRINT_PREVIEW và gọi hàm OnFilePrintPreview của lớp CView để thực hiện xem trang in. Tham số đối tượng lớp CPrintInfo có thành viên m_bPreview có giá trị TRUE tiến trình in đã được xem trước (thực hiện hàm DoPrintPreview) và ngược lại FALSE. Thành viên m_nNumPreviewPages lưu số trang cùng xem một lúc trên cửa sổ này. Chú ý: Trong chương trình kiểu tài liệu/quan sát có sử dụng cơ chế in của MFC chúng ta phải thiết lập chế độ sử dụng các lớp thư viện MFC dạng chia sẻ. Các bước chọn là Project Setting (Alt+F7) trong thẻ General chọn giá trị "Use MFC in shared DLL" trong mục "Microsoft Foundation Class".

Ví dụ 8.3
Lập trình thực hiện vẽ lên màn hình ô bàn cờ Carô, cho phép bấm chuột để điền các dấu X hoặc O luân phiên nhau, có thể xem và in trang bàn cờ ra giấy bằng chức năng trên thực đơn. Lớp CMyDoc dẫn xuất từ lớp CDocument sẽ lưu các thông tin về trạng thái bàn cờ. Lớp CMyView làm các nhiệm vụ chính như ánh xạ xử lý thông điệp chức năng, bấm chuột và đánh dấu vào ô cờ,... Tệp định nghĩa hằng tài nguyên resource.h
#define IDR_MYWIN #define ID_FILE_RESET #define ID_FILE_EXIT #define MAXSIZE 101 40001 40004 30

Tệp khai báo tài nguyên main.rc
#include "resource.h" #include "afxres.h"

273

// Menu IDR_MYWIN MENU DISCARDABLE BEGIN POPUP "File" BEGIN MENUITEM "Reset", ID_FILE_RESET MENUITEM "Print preview", ID_FILE_PRINT_PREVIEW MENUITEM "Print", ID_FILE_PRINT MENUITEM SEPARATOR MENUITEM "Exit", ID_FILE_EXIT END END // String Table STRINGTABLE DISCARDABLE BEGIN IDR_MYWIN "Chuong trinh Vidu03/Chuong8\nLHLK\nLHK Type (*.lhk)\n.lhk\n\nV3C8FT\nV3C8FN" END

Tệp khai báo các lớp trong chương trình main.h
class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { DECLARE_DYNCREATE(CMyWin); }; class CMyDoc : public CDocument { DECLARE_DYNCREATE(CMyDoc) public: char ttBanco[MAXSIZE][MAXSIZE]; char ttDau; void OnReset(); char OnDanhDau(int i,int j); BOOL OnNewDocument(); }; class CMyView : public CView

274

{ DECLARE_DYNCREATE(CMyView) public: CBitmap mBMP; CDC mDC; CBrush mBR; CPen mP; int XMAX,YMAX; int DauSize; BOOL IsPrint; afx_msg int OnCreate( LPCREATESTRUCT lpCreateStruct ); afx_msg void OnLButtonDown(UINT nFlags,CPoint pt); void OnExit(); void OnReset(); void OnDraw(CDC *dc); void OnDrawBanco(); BOOL OnPreparePrinting(CPrintInfo *pInfo); void OnPrint( CDC* pDC, CPrintInfo* pInfo ); DECLARE_MESSAGE_MAP() };

Tệp nội dung mã lệnh chương trình main.cpp
#include<afxwin.h> #include<afxext.h> #include"resource.h" #include"main.h" CMyApp theApp; IMPLEMENT_DYNCREATE(CMyDoc,CDocument) IMPLEMENT_DYNCREATE(CMyWin,CFrameWnd) IMPLEMENT_DYNCREATE(CMyView,CView) BEGIN_MESSAGE_MAP(CMyView,CView) ON_WM_CREATE() ON_WM_LBUTTONDOWN() ON_COMMAND(ID_FILE_EXIT,OnExit) ON_COMMAND(ID_FILE_RESET,OnReset) ON_COMMAND(ID_FILE_PRINT,CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW,CView::OnFilePrintPreview) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() {

275

CSingleDocTemplate *pd=new CSingleDocTemplate( IDR_MYWIN, RUNTIME_CLASS(CMyDoc), RUNTIME_CLASS(CMyWin), RUNTIME_CLASS(CMyView)); AddDocTemplate(pd); EnableShellOpen(); RegisterShellFileTypes(); CCommandLineInfo cif; ParseCommandLine(cif); if (!ProcessShellCommand(cif)) return FALSE; return TRUE; } void CMyView::OnExit() { CWnd *w=GetParent(); w->DestroyWindow(); } void CMyView::OnDrawBanco() { int x,y,mi,mj,i,j,mx,my; mi=YMAX/DauSize; mj=XMAX/DauSize; mx=(mj-1)*DauSize; my=(mi-1)*DauSize; for (x=0,j=0;j<=mj;x+=DauSize,j++) { mDC.MoveTo(x,0); mDC.LineTo(x,my); } for (y=0,i=0;i<=mi;y+=DauSize,i++) { mDC.MoveTo(0,y); mDC.LineTo(mx,y); } } void CMyView::OnDraw(CDC *dc) { RECT rt; dc->GetClipBox(&rt); int cx=rt.right-rt.left, cy=rt.bottom-rt.top;

276

if (IsPrint) dc->StretchBlt(0,0,cx,cy,&mDC,0,0,XMAX,YMAX,SRCCOPY); else dc->BitBlt(0,0,cx,cy,&mDC,0,0,SRCCOPY); IsPrint=FALSE; } int CMyView::OnCreate( LPCREATESTRUCT lpCreateStruct ) { int kq=CView::OnCreate(lpCreateStruct); CClientDC dc(this); XMAX= GetSystemMetrics(SM_CXSCREEN); YMAX= GetSystemMetrics(SM_CYSCREEN); mBMP.CreateCompatibleBitmap(&dc,XMAX,YMAX); mDC.CreateCompatibleDC(&dc); mP.CreatePen(PS_SOLID,1,RGB(0,0,255)); mBR.CreateStockObject(WHITE_BRUSH); mDC.SelectObject(&mBMP); mDC.SelectObject(&mBR); mDC.SelectObject(&mP); mDC.PatBlt(0,0,XMAX,YMAX,PATCOPY); DauSize=20; IsPrint=FALSE; OnDrawBanco(); return kq; } void CMyView::OnLButtonDown(UINT nFlags,CPoint pt) { int i,j,x,y,x1,y1; char kq; CMyDoc *d = (CMyDoc*)GetDocument(); i=pt.y/DauSize; j=pt.x/DauSize; x=j*DauSize; y=i*DauSize; x1=x+DauSize; y1=y+DauSize; kq=d->OnDanhDau(i,j); if (kq==1) { mDC.MoveTo(x+3,y+3); mDC.LineTo(x1-3,y1-3); mDC.MoveTo(x1-3,y+3); mDC.LineTo(x+3,y1-3); } else if (kq==2)

277

{ mDC.Ellipse(x+3,y+3,x1-3,y1-3); } InvalidateRect(NULL); } void CMyView::OnReset() { mDC.PatBlt(0,0,XMAX,YMAX,PATCOPY); CMyDoc *d=(CMyDoc*)GetDocument(); d->OnReset(); OnDrawBanco(); InvalidateRect(NULL); } BOOL CMyView::OnPreparePrinting(CPrintInfo *pInfo) { pInfo->SetMaxPage(1); return DoPreparePrinting(pInfo); } void CMyView::OnPrint( CDC* pDC, CPrintInfo* pInfo ) { IsPrint=TRUE; CView::OnPrint(pDC,pInfo); } char CMyDoc::OnDanhDau(int i,int j) { if ((i<MAXSIZE)&&(j<MAXSIZE)&&(ttBanco[i][j]==0)) { ttBanco[i][j]=ttDau; if (ttDau==1) { ttDau=2; return 1;} else { ttDau=1; return 2; } } return 0; } BOOL CMyDoc::OnNewDocument() {

278

OnReset(); return CDocument::OnNewDocument(); } void CMyDoc::OnReset() { ::ZeroMemory(ttBanco,sizeof(char)*MAXSIZE*MAXSIZE); ttDau=1; }

Hàm CMyView::OnExit() lấy ra cửa sổ chứa khung nhìn bằng lệnh GetParent() và gọi hàm DestroyWindow() để thoát khỏi chương trình. Trong hàm này chúng ta có thể sử dụng đối tượng theApp xác định cửa sổ chính bằng lệnh GetMainWnd() để kết thúc thay cho lệnh GetParent(). Bàn cờ được vẽ trên một cửa sổ ảo có DC chứa đối tượng mDC, là thành viên lớp CMyView. Các thao tác vẽ bàn cờ và đánh dấu mỗi khi người dùng bấm chuột lên ô bàn cờ đều thực hiện trên cửa sổ ảo, sau đó trong hàm OnDraw() sẽ chép vào cửa sổ bằng lệnh BitBlt(). Ngoài ra trong hàm OnDraw() thực hiện chép cửa sổ ảo sang máy in bằng lệnh StretchBlt() nếu chúng ta đang in dựa vào biến IsPrint được thiết lập trong hàm OnPrint(). Hàm StretchBlt() có tác dụng như BitBlt(), chép hình ảnh từ một DC vào một DC khác (từ cửa sổ ảo vào DC máy in) và thực hiện co giãn để ảnh nguồn được chồng khít lên vùng in đựơc. Hàm vẽ bàn cờ CMyView::OnDrawBanco() thực hiện vẽ các đường thẳng dọc và ngang với khoảng cách như nhau bằng DauSize đơn vị vẽ lôgíc. Chúng ta viết đè hàm OnCreate() trong lớp CMyView để thực hiện tạo một cửa sổ ảo cho lớp khung nhìn tương ứng, đặt nền cửa sổ ảo là nền trắng quy định bằng mBR, màu vẽ là màu xanh quy định bằng mP. mBR và mP là các thành viên của lớp CMyView. Ánh xạ xử lý thông điệp nhấn chuột trái bởi hàm OnLButtonDown() trong lớp CMyView để xác định tọa độ chuột khi nhấn tính tọa độ ô cờ thực hiện đánh dấu nếu đang còn trống. Sau đó thay đổi trạng thái đánh dấu dựa vào biến ttDau bằng 1 là X và bằng 2 là O. Kết quả chương trình sẽ là:

279

Nếu chọn chức năng "Print preview" sẽ có màn hình như sau:

280

8.4. Bài tập
Bài tập 8.1
Lập trình dạng cửa sổ đơn giản cho phép in dòng chữ thông tin của bạn ra máy in, gồm họ tên, ngày sinh, quê quán.

Bài tập 8.2
Lập trình dạng cửa sổ đơn giản thực hiện vẽ lên màn hình lá cờ tổ quốc, cho phép in ra máy in khi chọn chức năng trên thực đơn.

Bài tập 8.3
Lập trình dạng cửa sổ đơn giản kiểm tra tốc độ gõ phím bằng cách hiện ra một dòng chữ trên cửa sổ. Chờ gõ từng chữ trong xâu ký tự đó và kiểm tra chính xác, sau đó tính phần trăm số chữ gõ đúng. Sau khi gõ xong hỏi người chơi có in kết quả không? nếu có thực hiện in ra máy in thông tin về bạn gồm họ tên, ngày sinh, xâu ký tự đã kiểm tra, xâu ký tự đã gõ vào, phần trăm gõ đúng.

Bài tập 8.4
Lập trình kiểu tài liệu/quan sát cho phép nhập các chữ cái và chữ số từ bàn phím để hiện lên cửa sổ. Có thể chọn phông chữ và màu chữ hiển thị (phải tính độ co giãn chữ để không vượt ra ngoài cửa sổ). Có thể xem và in ra máy in qua thực đơn.

Bài tập 8.5
Lập trình kiểu tài liệu quan sát vẽ bàn cờ vua lên cửa sổ, tạo các quân cờ vua trong tài nguyên bitmap và nạp vào chương trình, cho phép chọn một quân cờ trên thanh công cụ và bấm chuột vào một ô để vẽ quân cờ vào ô đó (chép ảnh bitmap vào). Có thể xem và in bàn cờ sau khi vẽ ra máy in.

281

Chương 9 LẬP TRÌNH ĐA TUYẾN
9.1. Các khái niệm cơ bản
Windows là một hệ điều hành đa nhiệm nếu nhìn dưới góc độ người sử dụng. Tuy nhiên dưới góc độ lập trình thì Windows cung cấp hai cơ chế đa nhiệm, thứ nhất đa nhiệm theo chế độ tiến trình (process) tức là tại một thời điểm có thể thực hiện nhiều tiến trình (nhiều chương trình cùng thực hiện), chế độ này tồn tại ở Window 3.11. Thứ hai đa nhiệm theo chế độ luồng (thread) được cung cấp bởi Win32, trong một tiến trình có thể chứa nhiều luồng và tất nhiên sẽ có tối thiểu một luồng trong mỗi tiến trình gọi là luồng chính (main thread). Với cách thứ hai này người lập trình có thể phân chia công việc thành nhiều luồng xử lý khác nhau độc lập để tận dụng tối đa thời gian của CPU và không phải chờ nhau khi thực hiện. Lập trình có phân chia và xử lý đến các luồng ta gọi là lập trình đa tuyến. Thư viện MFC cung cấp cơ chế lập trình đa tuyến khá hoàn thiện và dễ dàng đối với lập trình viên. Ví dụ chúng ta có thể tổ chức chương trình thành 3 luồng, luồng thứ nhất xử lý công việc lưu trữ dữ liệu lên ổ đĩa, luồng thứ 2 lấy thông tin từ một thiết bị từ xa nào đó, luồng thứ 3 để nhận và xử lý các yêu cầu từ người dùng. Tiến trình là quá trình thực hiện một chương trình nào đó, mỗi khi bắt đầu một tiến trình thì một luồng chính ứng với tiến trình đó cũng được bắt đầu thực hiện và chúng ta có thể tạo thêm nhiều luồng mới từ đây. MFC định nghĩa hai kiểu luồng: luồng giao diện (interface) và luồng làm việc (worker). Luồng giao diện có thể nhận và xử lý các thông điệp, nó chứa một vòng lặp thu nhận và chuyển phát thông điệp (message pump). Như

282

chúng ta thấy trong các ví dụ trước mỗi chương trình có luồng chính được bắt đầu tại thời điểm tạo đối tượng lớp CMyApp và nó là một luồng giao diện. Luồng làm việc không nhận và xử lý thông điệp, thay vì nó cung cấp thêm một luồng xử lý công việc nào đó từ luồng giao diện. Có rất ít chương trình cần nhiều vòng lặp nhận và xử lý thông điệp nên hầu hết các chương trình chỉ có một luồng giao diện tương ứng là luồng chính. Thông thường chúng ta tạo thêm các luồng làm việc khác để xử lý các công việc trong chương trình và trong chương này sẽ ví dụ về kiểu luồng này. Chú ý: Trong lập trình API hai kiểu luồng này chỉ được xem như một, sự phân chia chỉ có tác dụng trong lập trình MFC.

9.2. Tạo và sử dụng luồng
Thư viện MFC cung cấp cơ chế lập trình đa tuyến bởi lớp CWinThread và nó là lớp cơ sở của lớp CWinApp, lớp ứng dụng này sẽ hình thành luồng giao diện và là luồng chính trong chương trình.

9.2.1. Tạo luồng làm việc
Để tạo một luồng làm việc sử dụng hàm AfxBeginThread() có hai mẫu khai báo như sau:
CWinThread* AfxBeginThread( AFX_THREADPROC LPVOID pParam, int Priority = THREAD_PRIORITY_NORMAL, UINT StackSize = 0, DWORD CreateFlag = 0, LPSECURITY_ATTRIBUTES SecAttr = NULL ); ThreadProc,

Mỗi luồng được bắt đầu bằng cách gọi một hàm, hàm này gọi là hàm luồng. Quá trình thực hiện luồng sẽ tiếp tục cho đến khi hàm của luồng kết thúc. Tham số ThreadProc là địa chỉ của hàm này và hàm phải được khai báo theo mẫu sau:
UINT ThreadProc( LPVOID pParam ); 283

Giá trị pParam của hàm AfxBeginThread() sẽ được chuyển vào tham số pParam của hàm luồng ở trên. Chúng ta sử dụng tham số này cho mọi mục đích. Tham số Priority quy định mức độ ưu tiên của luồng đối với tài nguyên thời gian CPU, mặc định là chế độ ưu tiên bình thường. Tuy nhiên có thể sử dụng một trong các giá trị sau:
Giá tr THREAD_PRIORITY_TIME_CRITICAL THREAD_PRIORITY_HIGHEST THREAD_PRIORITY_ABOVE_NORMAL THREAD_PRIORITY_NORMAL THREAD_PRIORITY_BELOW_NORMAL THREAD_PRIORITY_LOWEST THREAD_PRIORITY_IDLE Ý nghĩa Đòi h i th i gian tuy t đ i Ưu tiên cao nh t Trên m c bình thư ng Bình thư ng Dư i m c bình thư ng Th p nh t Nhàn r i

Mỗi luồng có một stack của bản thân nó để chứa các thông tin về quá trình thực hiện. Chúng ta có thể đặt kích thước cho stack của luồng qua tham số StackSize, nếu không máy sẽ lấy mặc định là bằng với stack của luồng đã tạo ra nó. Tham số CreateFlag quy định trạng thái thực hiện của luồng. Mặc định bằng 0 thì luồng sẽ được thực hiện ngay sau khi tạo, nếu đặt bằng CREATE_SUSPENDED thì luồng tạo ra sẽ chưa thực hiện ngay, sẽ chờ cho đến khi gọi hàm CWinThread::ResumeThread(). Con trỏ SecAttr sẽ trỏ đến cấu trúc các thuộc tính quy định độ an toàn đối với luồng được tạo ra, thường lấy mặc định bằng NULL sẽ tự động lấy các thuộc tính từ luồng hiện tại đặt cho luồng mới. Cấu trúc này có khai báo là:
typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; *PSECURITY_ATTRIBUTES

} SECURITY_ATTRIBUTES,

284

Hàm AfxBeginThread() trả về một con trỏ tới đối tượng ứng với luồng được tạo ra hoặc trả về 0 nếu bị lỗi. Nếu bạn cần truy xuất để thay đổi thuộc tính an toàn hoặc trạng thái làm việc thì cần phải lưu giữ con trỏ này, thông thường bỏ qua kết quả này. Ngoài ra hàm tạo luồng có dạng thứ hai như sau:
CWinThread* AfxBeginThread( CRuntimeClass* int ThreadClass,

Priority = THREAD_PRIORITY_NORMAL, StackSize = 0, CreateFlag = 0,

UINT

DWORD

LPSECURITY_ATTRIBUTES SecAttr = NULL );

Thay tham số tên hàm của luồng bằng con trỏ lớp dẫn xuất từ CWinThread, sử dụng cơ chế lớp động thông qua macro RUNTIME_CLASS() để truyền cho tham số này. Hàm của luồng hoặc lớp này sẽ thực hiện các công việc ứng với luồng được tạo ra.

9.2.2. Kết thúc một luồng
Như chúng ta thấy ở trên, thời điểm kết thúc một luồng là lúc kết thúc hàm xử lý của luồng đó. Tuy nhiên có thể kết thúc tại thời điểm tùy ý trong quá trình thực hiện bằng cách gọi hàm sau:
void AfxEndThread( UINT nExitCode );

Trong đó nExitCode là trạng thái kết thúc. Thông thường chúng ta để luồng kết thúc khi hàm tương ứng của nó kết thúc.

Ví dụ 9.1
Lập trình tạo một luồng làm việc khi chọn chức năng trên thực đơn. Hàm của luồng thực hiện in lên màn hình một thông báo và tạo tiếng bíp. Hàm xử lý luồng sẽ nhận dữ liệu qua tham số kiểu LPVOID là một con trỏ tới đối tượng lớp của sổ, từ đó xác định ngữ cảnh để hiển thị dữ liệu. Nội dung tệp chương trình là:
285

#include<afxwin.h> #define IDM_THREAD 174 #define IDM_EXIT 142 UINT MyThread(LPVOID pMain); int MyID=0; class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: int mY; CMyWin(); void OnThread(); void OnExit(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP(CMyWin,CFrameWnd) ON_COMMAND(IDM_THREAD,OnThread) ON_COMMAND(IDM_EXIT,OnExit) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 01 - Chuong 9"); CMenu mn; mn.CreateMenu(); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,IDM_THREAD,"Thread"); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,IDM_EXIT,"Exit"); SetMenu(&mn); mY=0; } void CMyWin::OnThread()

286

{ AfxBeginThread(MyThread,this); } void CMyWin::OnExit() { DestroyWindow(); } UINT MyThread(LPVOID pMain) { CMyWin *p = (CMyWin*)pMain; CDC *d = p->GetDC(); char s[255]; int id = MyID++; TEXTMETRIC tm; d->GetTextMetrics(&tm); for (int i=0;i<60;i++) { Sleep(1000); sprintf(s,"(Thread=%d,Count=%d)",id,i); d->TextOut(id*150,p->mY,s,strlen(s)); p->mY=p->mY+tm.tmHeight+tm.tmExternalLeading; MessageBeep(MB_OK); } MyID--; return 0; }

Chúng ta sử dụng một biến toàn cục MyID để xác định số hiệu của một luồng được tạo mới, bắt đầu hàm của luồng sẽ tăng MyID và thực hiện các việc, trước khi kết thúc hàm giảm MyID để kết thúc luồng đó. Hàm của luồng có tham số truyền vào là con trỏ this (trỏ đến đối tượng cửa sổ chính của lớp CMyWin) qua lệnh AfxBeginMessage(), vì thế trong hàm này ta sử dụng tham số đó để xác định ngữ cảnh cửa sổ hiện các thông báo của luồng. Công việc thực hiện trong hàm của luồng là một lệnh lặp for gồm 60 lần lặp, mỗi lần sẽ chờ 1 giây (1000 mili giây) bằng lệnh Sleep(1000), hiện thông báo số hiệu của luồng và số lần đang lặp, sau đó phát tiếng kêu ra loa bằng lệnh MessageBeep(). Nội dung chương trình sau khi chọn để tạo các luồng là:

287

Ví dụ 9.2
Lập trình tạo hai luồng làm việc, một luồng vẽ quả bóng màu đỏ chuyển động trên cửa sổ, một luồng vẽ hình vuông màu xanh cho xuất hiện tại những vị trí bất kỳ. Nếu quả bóng gặp cạnh cửa sổ sẽ phản xạ trở lại theo góc đối xứng. Trên mỗi quả bóng vẽ ra ở luồng thứ nhất có hiển thị số hiệu quả bóng tương ứng với lần tạo thứ mấy và tương tự ở luồng tạo hình vuông. Hai luồng này không kết thúc cho đến khi kết thúc chương trình. Để xử lý tình huống phản xạ của quả bóng chúng ta xác định tọa độ của nó, nếu gặp một cạnh cửa sổ sẽ thay đổi bước tăng dx theo x hoặc dy theo y bằng cách đổi dấu.
288

Nội dung chương trình là:
#include<afxwin.h> #define IDM_THREAD1 174 #define IDM_THREAD2 147 #define IDM_EXIT 142 UINT MyThread1(LPVOID pMain); int MyID1 = 0; UINT MyThread2(LPVOID pMain); int MyID2 = 0; class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); void OnThread1(); void OnThread2(); void OnExit(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP(CMyWin,CFrameWnd) ON_COMMAND(IDM_THREAD1,OnThread1) ON_COMMAND(IDM_THREAD2,OnThread2) ON_COMMAND(IDM_EXIT,OnExit) END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 02 - Chuong 9"); CMenu mn;

289

mn.CreateMenu(); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,IDM_THREAD1,"Bong do"); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,IDM_THREAD2,"Hinh vuong xanh"); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,IDM_EXIT,"Exit"); SetMenu(&mn); } void CMyWin::OnThread1() { AfxBeginThread(MyThread1,this); } void CMyWin::OnThread2() { AfxBeginThread(MyThread2,this); } void CMyWin::OnExit() { DestroyWindow(); } UINT MyThread1(LPVOID pMain) { CMyWin *p = (CMyWin*)pMain; CDC *dc = p->GetDC(); int r=15, id = MyID1++; //Tao mot ngu canh ao chua bong CDC mDC; CBitmap mBMP; CBrush mBR; char s[50]; mDC.CreateCompatibleDC(dc); mBMP.CreateCompatibleBitmap(dc,2*r,2*r); mBR.CreateStockObject(WHITE_BRUSH); mDC.SelectObject(&mBMP); mDC.SelectObject(&mBR); mDC.PatBlt(0,0,2*r,2*r,PATCOPY); mBR.DeleteObject(); mBR.CreateSolidBrush(RGB(255,0,0)); mDC.SelectObject(&mBR); CPen pen; pen.CreatePen(PS_SOLID,1,RGB(255,0,0)); mDC.SelectObject(&pen); mDC.Ellipse(1,1,2*r-1,2*r-1); sprintf(s,"%d",id); mDC.SetBkMode(TRANSPARENT); mDC.SetTextColor(RGB(0,255,0));

290

mDC.SetTextAlign(TA_CENTER); mDC.TextOut(r,r-8,s,strlen(s)); RECT rt; p->GetClientRect(&rt); int mx=rt.right-rt.left, my=rt.bottom-rt.top; int x,y,dx=1,dy=1; x=rand()*(id+7)%mx; y=rand()*(id+15)%my; while (1) { dc->BitBlt(x-r,y-r,2*r,2*r,&mDC,0,0,SRCCOPY); Sleep(10); dc->BitBlt(x-r,y-r,2*r,2*r,NULL,0,0,WHITENESS); x+=dx; if ((x<0)||(x>mx)) { dx=-dx; x+=dx; } y+=dy; if ((y<0)||(y>my)) { dy=-dy; y+=dy; } } pen.DeleteObject(); mDC.DeleteDC(); mBMP.DeleteObject(); mBR.DeleteObject(); MyID1--; return 0; } UINT MyThread2(LPVOID pMain) { CMyWin *p = (CMyWin*)pMain; CDC *dc = p->GetDC(); int r=10, id = MyID2++; //Tao mot ngu canh ao chua hinh vuong CDC mDC; CBitmap mBMP; CBrush mBR; char s[50]; mDC.CreateCompatibleDC(dc); mBMP.CreateCompatibleBitmap(dc,2*r,2*r); mBR.CreateStockObject(WHITE_BRUSH); mDC.SelectObject(&mBMP); mDC.SelectObject(&mBR); mDC.PatBlt(0,0,2*r,2*r,PATCOPY);

291

mBR.DeleteObject(); mBR.CreateSolidBrush(RGB(0,0,255)); mDC.SelectObject(&mBR); CPen pen; pen.CreatePen(PS_SOLID,1,RGB(0,0,255)); mDC.SelectObject(&pen); mDC.Rectangle(1,1,2*r-1,2*r-1); sprintf(s,"%d",id); mDC.SetBkMode(TRANSPARENT); mDC.SetTextColor(RGB(255,255,0)); mDC.SetTextAlign(TA_CENTER); mDC.TextOut(r,r-7,s,strlen(s)); RECT rt; p->GetClientRect(&rt); int mx=rt.right-rt.left, my=rt.bottom-rt.top; int x,y; x=(rand()*(id+5))%mx; y=(rand()*(id+5))%my; while (1) { dc->BitBlt(x-r,y-r,2*r,2*r,&mDC,0,0,SRCCOPY); Sleep(1000); dc->BitBlt(x-r,y-r,2*r,2*r,NULL,0,0,WHITENESS); x=(rand()*(id+5))%mx; y=(rand()*(id+5))%my; } pen.DeleteObject(); mDC.DeleteDC(); mBMP.DeleteObject(); mBR.DeleteObject(); MyID2--; return 0; }

Trong hai hàm của luồng MyThread1() và MyThread2() đều vẽ hình lên một DC ảo, sau đó thực hiện chép DC vào cửa sổ từng vị trí chuyển động hoặc ngẫu nhiên. Kết quả chương trình sau khi chọn để tạo một số hình tròn và hình vuông như sau:

292

9.3. Quản lý luồng
9.3.1. Dừng và kích hoạt luồng
Một luồng có thể bị tạm dừng thực hiện bằng việc gọi hàm SuspendThread(), khi đó luồng sẽ bị ngưng trệ không thực hiện và nó được kích hoạt lại bằng hàm ResumeThread(). Hai hàm có khai báo như sau:
DWORD DWORD CWinThread :: SuspendThread( ); CWinThread :: ResumeThread( );

Mỗi luồng thực hiện sẽ có một giá trị đếm số lần tạm dừng. Nếu giá trị này bằng 0 thì luồng tương ứng đang hoạt động, ngược lại giá trị đếm khác 0 thì luồng đang ở trạng thái dừng. Mỗi lần gọi hàm SuspendThread() thì giá trị này sẽ tăng lên một, ngược lại mỗi lần gọi hàm ResumeThread() sẽ giảm đi một. Như vậy số lần gọi hai hàm phải bằng nhau để luồng quay trở lại trạng thái họat động. Cả hai hàm trên đều trả về giá trị đếm trước đó hoặc bằng -1 nếu bị lỗi.
293

9.3.2. Quản lý độ ưu tiên luồng
Mỗi luồng tạo ra được gắn với nó một độ ưu tiên (priority), độ ưu tiên này là sự kết hợp giữa hai giá trị: giá trị ưu tiên của lớp ứng với tiến trình process (lớp ứng dụng - CMyApp) và giá trị ưu tiên của riêng bản thân luồng đó. Độ ưu tiên của một luồng quy định tài nguyên thời gian CPU dành cho việc thực hiện luồng đó. Luồng ưu tiên thấp sẽ có ít thời gian thực hiện, luồng ưu tiên cao sẽ có nhiều thời gian hơn. Tất nhiên thời gian CPU dành cho một luồng sẽ gặp phải sự va chạm trên đặc tính thực hiện và sự tương tác giữa luồng đó với các luồng khác đang cùng thực hiện trong hệ thống. Chúng ta có thể xác định độ ưu tiên hiện tại của lớp ứng dụng bằng hàm sau:
DWORD GetPriorityClass( HANDLE hProcess );

Trong đó tham số hProcess là số hiệu của tiến trình đang thực hiện, tương ứng với lớp ứng dụng CMyApp. Tham số này chính là giá trị HINSTANCE của tiến trình, được lấy từ thành viên của đối tượng ứng dụng CWinApp::m_hInstance hoặc sử dụng hàm AfxGetInstanceHandle( ). Để đặt độ ưu tiên cho tiến trình hiện tại ta sử dụng hàm sau:
BOOL SetPriorityClass( HANDLE DWORD hProcess, dwPriorityClass );

Trong đó hProcess là số hiệu của tiến trình hiện tại cần đặt, dwPriorityClass là độ ưu tiên cần đặt, bao gồm các giá trị sau:
Giá tr ABOVE_NORMAL_PRIORITY_CLASS BELOW_NORMAL_PRIORITY_CLASS HIGH_ PRIORITY_CLASS IDLE_ PRIORITY_CLASS NORMAL_ PRIORITY_CLASS REALTIME_ PRIORITY_CLASS Ý nghĩa Trên m c bình thư ng Dư i m c bình thư ng Ưu tiên cao Ưu tiên th p Ưu tiên bình thư ng M c ưu tiên cao nh t

Mặc định mọi chương trình đều có mức ưu tiên NORMAL_PRIORITY_CLASS. Thông thường bạn không cần phải đặt lại mức ưu tiên cho lớp ứng dụng của chương trình, bởi vì sẽ làm thay đổi quá trình đang thực hiện trên toàn bộ hệ thống. Nếu bạn đặt độ ưu tiên là cao
294

nhất (REALTIME_PRIORITY_CLASS) thì nó sẽ chi phối tài nguyên CPU. Chỉ có một số ít các chương trình đặc biệt mới thực hiện điều này, do vậy ở đây chúng ta nên sử dụng mức ưu tiên mặc định. Với mọi mức ưu tiên của lớp ứng dụng, mức ưu tiên của luồng quy định thời gian của CPU dành riêng cho từng luồng ở bên trong tiến trình tương ứng. Khi tạo một luồng thì sẽ là ưu tiên mặc định nhưng chúng ta có thể thay đổi chúng, thậm chí ngay cả khi nó đang thực hiện. Mức ưu tiên luồng được điều khiển bởi các hàm thành viên lớp CWinThread. Chúng ta sử dụng hàm GetThreadPriority() để xác định và SetThreadPriority() để đặt mức ưu tiên cho luồng, hai hàm có khai báo như sau:
int BOOL CWinThread :: GetThreadPriority( ); CWinThread :: SetThreadPriority( int nPriority );

Hàm GetThreadPriority() trả về giá trị ưu tiên hiện thời của luồng, tham số nPriority trong hàm SetThreadPriority() quy định mức ưu tiên cần đặt, nó bao gồm các giá trị sau sắp xếp từ cao xuống thấp:
THREAD_PRIORITY_TIME_CRITICAL THREAD_PRIORITY_HIGHEST THREAD_PRIORITY_ABOVE_NORMAL THREAD_PRIORITY_NORMAL THREAD_PRIORITY_BELOW_NORMAL THREAD_PRIORITY_LOWEST THREAD_PRIORITY_IDLE

Ví dụ 9.3
Lập trình tạo hai luồng và đặt ưu tiên khác nhau, xem xét sự họat động thực hiện của hai luồng đó. Mỗi luồng sẽ thực hiện đếm và hiện ra giá trị đếm từ 0 đến 5000. Nội dung chương trình là:

295

#include<afxwin.h> #define IDM_THREAD 174 #define IDM_EXIT 142 UINT MyThread1(LPVOID pMain); UINT MyThread2(LPVOID pMain); CWinThread *pThread1,*pThread2; int ttOnThread=0; class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); afx_msg void OnTimer(UINT nIDEnvent); void OnThread(); void OnExit(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP(CMyWin,CFrameWnd) ON_COMMAND(IDM_THREAD,OnThread) ON_COMMAND(IDM_EXIT,OnExit) ON_WM_TIMER() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Vi du 03 - Chuong 9"); CMenu mn; mn.CreateMenu(); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,IDM_THREAD,"Bat dau");

296

mn.AppendMenu(MF_STRING|MF_BYCOMMAND,IDM_EXIT,"Exit"); SetMenu(&mn); } void CMyWin::OnTimer(UINT nIDEnvent) { CDC *dc=GetDC(); char s[100]; sprintf(s,"%2d",ttOnThread++); dc->TextOut(50,5,s,strlen(s)); } void CMyWin::OnThread() { if (ttOnThread>0) return; SetTimer(1,1000,NULL); ttOnThread=1; pThread1=AfxBeginThread(MyThread1,this); pThread2=AfxBeginThread(MyThread2,this); pThread1->SetThreadPriority(THREAD_PRIORITY_TIME_CRITICAL); pThread2->SetThreadPriority(THREAD_PRIORITY_IDLE); } void CMyWin::OnExit() { KillTimer(1); DestroyWindow(); } UINT MyThread1(LPVOID pMain) { CMyWin *p = (CMyWin*)pMain; CDC *dc = p->GetDC(); long Count1=0; char s[100]; while (Count1<5000) { for (long j=1,i=0;i<1000000;i++) j=i-j+2*i-3*j; sprintf(s,"{Count of Thread1 =%ld}",Count1++); dc->TextOut(0,50,s,strlen(s)); } return 0; } UINT MyThread2(LPVOID pMain) {

297

CMyWin *p = (CMyWin*)pMain; CDC *dc = p->GetDC(); long Count2=0; char s[100]; while (Count2<5000) { for (long j=1,i=0;i<1000000;i++) j=i-j+2*i-3*j; sprintf(s,"{Count of Thread2 =%ld}",Count2++); dc->TextOut(200,50,s,strlen(s)); } return 0; }

Trong chương trình sử dụng hai biến trỏ lớp CWinThread (pThread1, pThread2) để trỏ đến hai đối tượng tương ứng là hai luồng được tạo ra. Biến ttOnThread để đếm thời gian theo từng giây hiện lên cửa sổ qua hàm thông điệp thời gian OnTimer(), đồng thời quy định chức năng ứng với hàm OnThread() chỉ thực hiện một lần với giá trị ttOnThread xuất phát bằng 0. Trong hàm OnThread() sau khi khởi tạo hai luồng chúng ta đặt mức độ ưu tiên cho hai luồng khác nhau, luồng thứ nhất có mức ưu tiên cao nhất (THREAD_PRIORITY_TIME_CRITICAL) và luồng thứ hai có mức ưu tiên thấp nhất (THREAD_PRIORITY_IDLE). Trong hàm của luồng sử dụng lệnh for sau để tạo độ trễ:
for (long j=1,i=0;i<1000000;i++) j=i-j+2*i-3*j;

Kết quả chương trình sau 20 giây sẽ là:

Ta thấy hai giá trị đếm trên hai luồng khác nhau, luồng 1 có giá trị đếm cao hơn nhiều so với luồng 2 chứng tỏ luồng 1 được thực hiện ưu tiên hơn.
298

Nếu đặt mức độ ưu tiên hai luồng như sau và bằng mức bình thường thì kết quả sau 20 giây sẽ là:

Hai giá trị đếm của hai luồng là như nhau và ngược lại nếu đặt giá trị ưu tiên cho luồng 2 cao hơn luồng 1 thì kết quả sẽ là:

Cơ chế xử lý hai hàm ứng với hai luồng trên là như nhau, trong đó vòng lặp while với điều kiện là 5000 lần lặp để đếm và hiện kết quả lên cửa sổ. Giá trị 20 hiện trên cửa sổ là số giây đã thực hiện luồng, giá trị này được hiện bởi hàm xử lý thông điệp thời gian OnTimer().

299

9.4. Đồng bộ hóa trong lập trình đa tuyến
Khái niệm đồng bộ hóa trong lập trình đa tuyến là đồng bộ việc truy nhập tài nguyên trên máy giữa các luồng, ngoài ra có đồng bộ các sự kiện giữa các luồng. Khi sử dụng nhiều luồng hoặc nhiều tiến trình và các tài nguyên dùng chung chỉ được phép truy xuất bởi một luồng tại một thời điểm. Ví dụ như chúng ta có một luồng đang ghi tệp, một luồng khác cũng ghi tệp đó vào cùng thời điểm thì phải được ngăn chặn cho đến khi luồng trước ghi xong. Một lý do khác đối với sự đồng bộ này khi một luồng đang chờ một sự kiện được sinh ra bởi một luồng khác, trong trường hợp này thì luồng thứ nhất phải được giữ ở trạng thái chờ Suspend cho đến khi sự kiện được xảy ra. Có hai trang thái của một công việc có thể là: thứ nhất nó đang hoạt động, thứ hai nó bị khóa (blocked).

9.4.1. Cơ chế đồng bộ hóa
Windows cung cấp các dịch vụ cho phép truy xuất các tài nguyên dùng chung một cách hiệu quả, nhưng không cung cấp hướng dẫn về các dịch vụ này. Một tiến trình hay luồng không thể biết chi tiết cách truy xuất tài nguyên của hệ thống. Có thể tưởng tượng rằng chúng ta đang viết chương trình trên hệ điều hành đa nhiệm mà không cung cấp bất kỳ một cơ chế đồng bộ nào. Hơn nữa khi hai tiến trình A và B đang cùng truy xuất một tài nguyên R nào đó trên hệ thống nhưng tài nguyên chỉ được phép một tiến trình truy xuất tại một thời điểm, phải làm gì để ngăn chặn sự xung đột như trên. Một cách làm khá đơn giản là sử dụng một giá trị cờ flag được khởi tạo là 0, đối với mỗi tiến trình truy xuất tài nguyên R, trước hết phải chờ trạng thái đặt cờ cho đến khi bằng 0 (tài nguyên R rỗi), sau khi có trạng thái cờ bằng 0 ta đặt lại bằng 1 và thực hiện truy xuất, xong ta hủy cờ bằng 0. Mẫu chương trình như sau:
300

while (flag); //chờ giá trị cờ cho ñến khi bằng 0 flag = 1; // ... … truy xuất tài nguyên dùng chung ở ñây // flag = 0;

Giải pháp cho vấn đề đồng bộ hóa càng đơn giản càng tốt. Hệ điều hành cung cấp một hành động không phải ngắt đó là kiểm tra và đặt cờ nếu có thể. Trước đây giá trị cờ được sử dụng để điều khiển đồng bộ hóa giữa các tiến trình được gọi là semaphore.

9.4.2. Đối tượng đồng bộ hóa
MFC cung cấp 4 kiểu đối tượng đồng bộ hóa, tất cả đều dựa trên khái niệm semaphore. Kiểu thứ nhất là lớp semaphore, một semaphore có thể cho phép giới hạn số tiến trình hoặc luồng truy xuất tới một tài nguyên, hoặc cho phép chỉ một tiến trình hoặc luồng truy xuất tài nguyên tại mọi thời điểm. Semaphore là thể hiện cho việc sử dụng một biến đếm sẽ được tăng khi công việc gắn tới semaphore và ngược lại giảm biến đếm khi công việc được giải phóng. Đối tượng đồng bộ thứ hai là mutex semaphore, sử dụng đối tượng này để đồng bộ tài nguyên với mỗi thời điểm chỉ có một và chỉ một tiến trình hoặc luồng được truy xuất. Thứ ba là đối tượng event object, được sử dụng để khóa truy xuất tài nguyên cho đến khi một luồng hoặc tiến trình bật tín hiệu có thể dùng được. Cuối cùng bạn có thể ngăn chặn một đoạn mã được sử dụng bởi nhiều luồng tại cùng thời điểm bằng cách đặt nó vào trong critical section và sử dụng đối tượng critical section này. Trong chương này chúng tôi chỉ trình bày cách tạo và sử dụng hai đối tượng là semaphore và event.
301

9.4.3. Các lớp đồng bộ của MFC
MFC bao gói kỹ thuật đồng bộ hóa bởi các lớp sau:
Tên l p CCriticalSection CEvent CMutex CSemaphore Ý nghĩa Đ i tư ng đ ng b critical section Đ i tư ng event Đ i tư ng mutex semaphore Đ i tư ng semaphore

Các lớp này đều dẫn xuất từ lớp CSyncObject, nó cung cấp kỹ thuật cơ sở cho việc đồng bộ hóa. MFC còn định nghĩa thêm hai lớp là CSingleLock và CMultiLock để điều khiển truy xuất các đối tượng đồng bộ ở trên. Chúng có các thành viên hàm để đặt và hủy bỏ một đối tượng đồng bộ. Lớp CSingleLock điều khiển truy xuất tới một đối tượng đồng bộ, ngược lại CMultiLock cho phép điều khiển truy xuất nhiều đối tượng đồng bộ. Chúng ta sẽ sử dụng lớp CSingleLock để minh họa, còn bạn có thể tự minh họa thêm về lớp CMultiLock. Sau khi tạo một đối tượng đồng bộ, bạn có thể điều khiển truy xuất nó bằng cách sử dụng một đối tượng CSingleLock. Trước hết bạn sử dụng hàm tạo của lớp để tạo đối tượng CSingleLock theo mẫu sau:
CSingleLock( CSyncObject FALSE); *SyncOb, BOOL InitialState =

Trong đó SyncOb là con trỏ tới đối tượng đồng bộ, như là semaphore. Giá trị InitialState quy định hàm này có đòi hỏi đối tượng đồng bộ hay không ứng với TRUE hoặc FALSE. Mặc định là không cần đối tượng đồng bộ được trỏ bởi SyncOb. Sau khi tạo đối tượng CSingleLock bạn sử dụng nó để điều khiển truy xuất đối tượng đồng bộ tương ứng với các hàm Lock() và UnLock() có khai báo như sau:
BOOL BOOL CSingleLock :: Lock(DWORD dwDelay=INFINITE); CSingleLock :: UnLock();

302

BOOL CSingleLock :: UnLock(LONG Count, LONG *Previous=NULL);

Hàm Lock() đòi hỏi phải có đối tượng đồng bộ, luồng đang thực hiện sẽ bị ngưng cho đến khi hàm này trả về. Giá trị dwDelay là số mili giây để công việc đang thực hiện chờ đối tượng đồng bộ. Hàm trả về TRUE nếu thành công và ngược lại. Hàm UnLock() hủy bỏ đối tượng đồng bộ và cho phép luồng khác sử dụng nó. Giá trị Count ở mẫu thứ hai quy định số đếm đối tượng truy xuất, thường sử dụng cho semaphore với đa truy xuất tài nguyên, Previous sẽ lưu giá trị số đếm trước đó. Hàm trả về TRUE nếu thành công và ngược lại. Các bước thực hiện đồng bộ sử dụng đối tượng CSingleLock như sau: Bước 1: Tạo một đối tượng đồng bộ từ một trong 4 lớp ở trên (CCriticalSection, CEvent, CMutex, CSemaphore) dùng để điều khiển truy xuất tài nguyên. Bước 2: Tạo một đối tượng lớp CSingleLock sử dụng đối tượng đồng bộ đã tạo ở trên. Bước 3: Để chặn truy xuất tới tài nguyên gọi hàm Lock(). Bước 4: Thực hiện truy xuất tài nguyên. Bước 5: Gọi UnLock() để hủy bỏ chặn bởi hàm Lock().

Ví dụ 9.4
Lập trình sử dụng đối tượng lớp CSemaphore để đồng bộ hóa hai luồng MyThread1 và MyThread2 như ở ví dụ trước. Lớp CSemaphore có một hàm tạo như sau:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );

Nhưng chúng ta sẽ dùng các giá trị mặc định khi tạo đối tượng.

303

Nội dung chương trình là:
#include<afxwin.h> #include<afxmt.h> #define IDM_THREAD 174 #define IDM_EXIT 142 UINT MyThread1(LPVOID pMain); UINT MyThread2(LPVOID pMain); int ttOnThread=0; class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CSemaphore sema; int mY; CMyWin(); void OnThread(); void OnExit(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP(CMyWin,CFrameWnd) ON_COMMAND(IDM_THREAD,OnThread) ON_COMMAND(IDM_EXIT,OnExit) ON_WM_TIMER() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin()

304

{ Create(NULL,"Vi du 03 - Chuong 9"); CMenu mn; mn.CreateMenu(); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,IDM_THREAD,"Bat dau"); mn.AppendMenu(MF_STRING|MF_BYCOMMAND,IDM_EXIT,"Exit"); SetMenu(&mn); mY=0; } void CMyWin::OnThread() { if (ttOnThread) return; ttOnThread=1; AfxBeginThread(MyThread1,this); AfxBeginThread(MyThread2,this); } void CMyWin::OnExit() { DestroyWindow(); } UINT MyThread1(LPVOID pMain) { CMyWin *p = (CMyWin*)pMain; CDC *dc = p->GetDC(); long Count1=0; char s[100]; /* Khóa tài nguyên s d ng đ i tư ng l p CSingleLock */ CSingleLock syncOb(&(p->sema)); syncOb.Lock(); while (Count1<10) { sprintf(s,"{Count of Thread1 =%ld}",Count1++); dc->TextOut(0,p->mY,s,strlen(s)); p->mY +=15; Sleep(500); } /* H y khóa tài nguyên */ syncOb.Unlock();

305

return 0; } UINT MyThread2(LPVOID pMain) { CMyWin *p = (CMyWin*)pMain; CDC *dc = p->GetDC(); long Count2=0; char s[100]; /* Khóa tài nguyên s d ng đ i tư ng l p CSingleLock */ CSingleLock syncOb(&(p->sema)); syncOb.Lock(); while (Count2<10) { sprintf(s,"{Count of Thread2 =%ld}",Count2++); dc->TextOut(200,p->mY,s,strlen(s)); p->mY +=15; Sleep(500); } /* H y khóa tài nguyên */ syncOb.Unlock(); return 0; }

Trong lớp cửa sổ CMyWin khai báo thêm hai thành viên là đối tượng đồng bộ sema kiểu lớp CSemaphore, thành viên mY quy định tọa độ dòng hiện chữ lên cửa sổ. Luồng ứng với hàm MyThread1() sẽ hiện các dòng chữ tại cột 0 còn luồng MyThread2() sẽ hiện tại cột 200. Trong mỗi hàm này chúng ta tạo đối tượng lớp CSingleLock và gắn nó với đối tượng đồng bộ, trước khi thực hiện công việc phải gọi hàm Lock() để khóa không cho luồng khác được thực hiện, sau khi làm xong ta gọi lại hàm Unlock() để hủy bỏ khóa ở trên. Kết quả chương trình sẽ là:

306

Nếu chúng ta bỏ cơ chế đồng bộ, cụ thể ở đây chúng ta bỏ 2 lệnh:
CSingleLock syncOb(&(p->sema)); syncOb.Lock();

trước khi thực hiện trong mỗi luồng và bỏ lệnh:
syncOb.Unlock();

sau khi thực hiện xong của luồng đó. Ta được kết quả như sau:

307

Tức là việc thực hiện hai luồng ở đây sẽ đan xen nhau, khác với đồng bộ ở trên là thực hiện xong luồng 1 mới thực hiện luồng 2.

9.5. Bài tập
Bài tập 9.1
Lập trình tạo hai luồng thực hiện hai công việc sau: - Hiện một chữ bất kỳ ra tại vị trí bất kỳ trên cửa sổ. - Hiện dòng chữ họ và tên bạn chạy ngang qua cửa sổ từ trái phải và ngược lại.

Bài tập 9.2
Lập trình tạo một luồng thực hiện công việc sau: - Vẽ một quả bóng bay trên cửa sổ và phản xạ ở cạnh theo góc đối xứng.

Bài tập 9.3
Lập trình vẽ một mô hình ôtô trên ảnh bitmap và nạp vào cửa sổ ảo, tạo một luồng để cho ôtô đó chạy trên cửa sổ từ trái qua phải bằng cách chép cửa sổ ảo vào các vị trí sau đó xóa đi và chép lại...

Bài tập 9.4
Lập trình vẽ 5 ảnh bitmap tương ứng là 5 động tác đi của con người và nạp vào 5 cửa sổ ảo, tạo một luồng để cho 5 ảnh đó hiện lên màn hình theo thứ tự để minh họa động tác đi của con người (giống hoạt hình).

Bài tập 9.5
Lập trình tạo hai luồng thực hiện hai việc sau: - Vẽ mô hình máy bay bay ngang qua cửa sổ - Thực hiện bắn đạn từ vị trí súng ở dưới cửa sổ (được xác định trước) khi nhấn phím Enter và kiểm tra nếu trúng máy bay thì tăng điểm lên 1, ngược lại bay quá cửa sổ thì dừng đạn. Luồng tạo máy bay được gọi bắt đầu theo thời gian cứ sau một đại lượng ngẫu nhiên thời gian nào đó.

308

Chương 10 THƯ VIỆN LIÊN KẾT ĐỘNG DLL
10.1. Các khái niệm cơ bản
10.1.1. Thư viện DLL là gì?
Thư viện liên kết động là một tập các hằng, biến nhớ, hàm, lớp và các đối tượng được định nghĩa trong một tệp chương trình không thi hành, tệp này sẽ cung cấp cho các chương trình thi hành những thành phần như trên. Thư viện liên kết động sẽ không được dịch và liên kết với chương trình dùng nó. Quá trình liên kết sẽ được thực hiện khi chương trình chạy và máy sẽ nạp thư viện đó vào bộ nhớ khi cần sử dụng đến. Một thư viện liên kết động có thể được sử dụng để cung cấp cho nhiều chương trình cùng thực hiện một lúc, khi đó ta gọi là thư viện liên kết động có chia sẻ. Ngược lại là thư viện liên kết động không chia sẻ. Thư viện liên kết động là một đặc điểm mạnh của lập trình trên hệ điều hành Windows. Một lập trình viên luôn cần đến sự liên kết giữa chương trình của mình với các thư viện động khác.

10.1.2. Các đặc trưng
Khi nhiều chương trình có dùng chung một đoạn mã lệnh thì bạn đưa nó vào trong một thư viện động để cung cấp. Hơn nữa trong chương trình bạn có các đoạn mã lệnh thực hiện nhiều công việc ở các thời điểm khác nhau thì bạn đưa mỗi đoạn vào một thư viện động để khi cần thì nạp vào máy thi hành, làm giảm kích thước chương trình khi chạy.

309

Một điều chắc chắn nữa là khi bạn thực hiện bất cứ điều gì với thư viện liên động thì bạn có thể làm tương tự với lập trình đối tượng thành phần (COM Component Object Model). So với các công cụ COM và ATL (Active Template Library) thì lập trình thư viện động đơn giản hơn nhiều, ngược lại lập trình COM và ATL cần đầu tư nhiều thời gian và công sức mới thực hiện được.

10.1.3. Các kiểu DLL
Có hai kiểu thư viện động có thể tạo trong lập trình MFC đó là thư viện DLL mở rộng MFC và thư viện DLL thông thường. Đối với thư viện DLL thông thường có hai kiểu liên kết là liên kết tĩnh hoặc liên kết động. Chúng ta có thể tạo thư viện DLL theo lập trình Win32 (lập trình C) nhưng ở đây chúng ta sẽ sử dụng lập trình dựa trên MFC. Trong mọi thư viện động sẽ có các giao diện với chương trình sử dụng nó. Giao diện này là tập các biến nhớ, con trỏ, hàm và các lớp, chúng có thể được sử dụng bởi chương trình liên kết tương ứng. Đối với thư viện DLL mở rộng MFC thì các giao diện có thể chứa các tham số, biến, giá trị kiểu của C++ và MFC và tất nhiên có thể sử dụng C++ và MFC để lập trình tạo ra DLL. Tuy nhiên thư viện DLL mở rộng MFC sẽ có liên kết với thư viện của bản thân MFC, do đó chương trình liên kết tương ứng phải có cùng phiên bản với thư viện MFC của DLL. Điều này đôi khi gây khá nhiều phiền toái cho người lập trình cũng như người sử dụng. Thư viện DLL mở rộng MFC thường có kích thước nhỏ và liên kết nhanh với chương trình, tất nhiên nó còn phụ thuộc vào phần mã lệnh trong thư viện đó. Đối với thư viện DLL thông thường thì có thể sử dụng trong hầu hết các chương trình trên Windows, trong khi thư viện DLL mở rộng MFC lại chỉ được sử dụng bởi chương trình kiểu MFC. Tuy vậy trong thư viện thông thường chúng ta không thể chứa trong giao diện là các kiểu của MFC hoặc kế thừa từ MFC. Có thể sử dụng MFC trong lập trình tạo ra thư viện DLL thông thường, không đưa vào giao diện.

310

Trong thư viện DLL thông thường nếu cần sử dụng đến thư viện MFC thì chúng ta có thể chọn giải pháp liên kết tĩnh (statically link) hoặc liên kết động (dynamically link). Trong trường hợp liên kết động thì thư viện DLL bạn tạo ra sẽ dùng thư viện MFC trên máy thi hành, do đó đòi hỏi phiên bản thư viện MFC phải thích hợp với yêu cầu trong DLL của bạn. Trường hợp liên kết tĩnh thì thư viện DLL của bạn sẽ rất lớn nhưng thực hiện độc lập trên máy khi liên kết.

10.2. Cách xây dựng thư viện DLL
10.2.1. Các bước tạo thư viện DLL
Chúng ta có thể tạo một thư viện liên kết động DLL theo cách tự làm từ đầu, tuy nhiên trên môi trường VisualC++ cho phép chúng ta tạo ra thư viện một cách dễ dàng hơn, sử dụng công cụ Wizard. Trong hộp thoại của chức năng "New" từ thực đơn "File" chúng ta chọn thẻ "Projects" và có một trong 3 trường hợp sau:

311

MFC AppWizard (dll) Thư viện liên kết động theo kiểu lập trình MFC (C++). Win32 Dynamic-Link Library Thư viện liên kết động kiểu lập trình C. Win32 Static Library Thư viện liên kết tĩnh kiểu lập trình C.

(i)

(ii)

(iii)

Chúng ta sẽ chọn trường hợp (i) theo kiểu lập trình MFC. Nhập tên của project tương ứng là tên thư viện và chọn "OK" sẽ xuất hiện bước tiếp theo.

Trong bước này bạn chọn một trong 3 kiểu thư viện liên kết động như đã trình bày ở trên: + Regular DLL with MFC statically linked Tạo thư viện liên kết động thông thường và thực hiện liên kết tĩnh với thư viện MFC, khi thi hành không phụ thuộc vào phiên bản MFC trên máy tính. + Regular DLL using shared MFC DLL

312

Tạo thư viện liên kết động thông thường sử dụng thư viện MFC chia sẽ, liên kết động và sẽ phụ thuộc vào phiên bản MFC trên máy tính khi thi hành thư viện này. + MFC Extension DLL ( using shared MFC DLL) Tạo thư viện liên kết động kiểu MFC và tất nhiên sử dụng thư viện MFC liên kết động, với thư viện này bạn có thể xuất ra giao diện các kiểu của MFC và phụ thuộc vào phiên bản MFC trên máy tính thi hành thư viện này. Trong trường hợp này bạn chọn (i) ở dạng đơn giản nhất, tuy vậy bạn có thể chọn bất kỳ trong 3 trường hợp trên nếu cần.

10.2.2. Định nghĩa giao diện của thư viện DLL
Khác với chương trình thông thường, ngoài các tệp tài nguyên .RC, tệp khai báo .H và tệp nội dung .CPP thư viện liên kết động có thêm một tệp chứa định nghĩa giao diện các thành phần xuất ra từ thư viện để các chương trình liên kết có thể dùng. Tệp này có phần mở rộng là .DEF với nội dung cơ bản như sau:
; Tệp ñịnh nghĩa giao diện thư viện .DEF LIBRARY " tên của thư viện "

DESCRIPTION ' mô tả thư viện ' EXPORTS ; Các ñịnh nghĩa thành phần giao diện ở ñây

Trong đó một định nghĩa thành phần giao diện có cú pháp sau:
entryname[=internalname] [@ordinal[NONAME]]

Trong đó entryname là tên chính thức của thành phần (biến nhớ, kiểu dữ liệu, lớp, hàm), thành phần này sẽ được sử dụng bởi các chương trình có liên kết với thư viện, internalname là tên của thành phần được khai báo và sử dụng trong thư viện, ordinal là số thứ tự thành phần. Với một định nghĩa thành phần giao diện chỉ cần tên chính thức, lúc này tên đó cũng là tên đã được khai báo và sử dụng bên trong thư viện. Ví dụ định nghĩa một giao diện:
313

; vidu_dll3.def : Declares the module parameters for the DLL. LIBRARY "vidu_dll3" DESCRIPTION 'vidu_dll3 Windows Dynamic Link Library' EXPORTS MAX @1 MIN @2 SUM @3 CMyClass @4

Tuy nhiên bạn có thể định nghĩa các thành phần giao diện cho thư viện không cần tệp .DEF này và bạn phải định nghĩa trong chương trình thư viện, tệp .H hoặc tệp .CPP. Khi đó cú pháp để khai báo sẽ là:
__declspec(dllexport) khai-báo-thành-phần;

Trong đó khai báo thành phần có thể là khai báo biến, khai báo hàm. Đối với khai báo lớp chúng ta làm theo cách sau:
class __declspec(dllexport) khai-báo-lớp;

Trong trường hợp này bạn phải dùng từ khóa __declspec(dllexport) để thực hiện, có thể định nghĩa từ này thành một hằng thay thế đơn giản và ngắn hơn. Ví dụ:
#define MYDEC __declspec(dllexport) class MYDEC CVidu_dll2 { public: CVidu_dll2(void); }; MYDEC int nVidu_dll2; MYDEC int fnVidu_dll2(void);

Với cách định nghĩa giao diện qua tệp .DEF sẽ đem lại nhiều lợi điểm như việc thêm các thành phần mới vào giao diện theo số thứ tự (@ordinal) tăng dần sẽ không ảnh hưởng đến chương trình sử dụng nó. Sau khi bạn nâng cấp DLL thành một phiên bản mới thì chương trình ứng dụng sử dụng phiên bản DLL cũ sẽ làm việc tương thích với phiên bản DLL mới này, không cần phải dịch lại chương trình ứng dụng. Ngoài ra nếu bạn sử dụng NONAME trong khai báo sẽ không sử dụng tên trong thư viện và làm giảm kích thước của thư viện.

314

Hình minh họa sự liên kết giữa thư viện liên kết động với chương trình ứng dụng như sau:

Ví dụ 10.1
Lập một thư viện DLL xuất ra giao diện lớp CMyClass các đối tượng phân số để xử lý cộng phân số và rút gọn phân số, xuất thêm một biến số nguyên m và một hàm sum để tính tổng hai số nguyên.
/* T p khai báo header vidu01.h như sau */ class CVidu01App : public CWinApp { public: CVidu01App(); }; __declspec(dllexport) int m=7; __declspec(dllexport) int sum(int x,int y); class __declspec(dllexport) CMyClass { int ts,ms; public: CMyClass(int x=1,int y=1);

315

CMyClass operator + (CMyClass vp); void rutgon(); }; /* T p chương trình vidu01.cpp như sau */ #include<afxwin.h> #include<math.h> #include "Vidu01.h" CVidu01App::CVidu01App(){} CVidu01App theApp; CMyClass::CMyClass(int x,int y) { ts=x; ms=y; } CMyClass CMyClass::operator + (CMyClass vp) { CMyClass kq; kq.ts = ts*vp.ms + vp.ts*ms; kq.ms = ms*vp.ms; return kq; } void CMyClass::rutgon() { if (ts*ms==0) return; int a=abs(ts), b=abs(ms); while (a!=b) { if (a>b) a=a-b; else b=b-a; } ts=ts/a; ms=ms/a; } int sum(int x,int y) { return x+y; }

316

10.3. Sử dụng DLL trong chương trình
Một thư viện liên kết động không tự nó thi hành trên máy, mà do một chương trình ứng dụng nạp và sử dụng nó qua giao diện tương ứng. Khi bạn dịch thư viện DLL thì máy sẽ tạo ra hai tệp .DLL và .LIB, cả hai tệp này quan trọng khi liên kết với chương trình ứng dụng. Một chương trình sử dụng thư viện liên kết động có hai phương pháp là phương pháp không tường minh (implicite) hoặc phương pháp tường minh (explicite).

10.3.1. Liên kết không tường minh
Liên kết này sẽ thực hiện nạp thư viện động vào máy mỗi lần chạy chương trình ứng dụng (load-time dynamic linking). Các bước thực hiện như sau: Bước 1: Bạn phải chép cả hai tệp của thư viện động là .DLL và .LIB vào thư mục của chương trình để thực hiện liên kết. Bước 2: Bạn thiết lập cho chương trình của bạn có thêm liên kết với thư viện DLL bằng cách chọn chức năng "Project" "Setting" Chọn thẻ "Link" trên hộp thoại này và gõ vào ô "Object/Library modules" tên tệp .LIB của thư viện động tương ứng. Bước 3: Trong chương trình bạn phải khai báo nhận các thành phần có sử dụng từ giao diện của thư viện động bằng cú pháp sau:
__declspec(dllimport) khai-báo-thành-phần;

Chúng ta thấy khai báo này tương tự như khai báo giao diện cho thư viện động nhưng ở đây sử dụng từ khóa dllimport thay cho dllexport. Ví dụ:
__declspec(dllimport) int m=7; __declspec(dllimport) int sum(int x,int y); class __declspec(dllimport) CMyClass;

Bước 4: Bạn có thể dùng các thành phần đã khai báo như bình thường trong chương trình.

317

Ví dụ 10.2
Lập chương trình sử dụng thư viện ở ví dụ 10.1 để thực hiện cộng hai số nguyên, cộng hai phân số và hiện kết quả lên cửa sổ. Nội dung tệp chương trình sẽ là:
#include<afxwin.h> __declspec(dllimport) int m; __declspec(dllimport) int sum(int x,int y); class __declspec(dllimport) CMyClass { int ts,ms; public: CMyClass(int x=1,int y=1); CMyClass operator + (CMyClass vp); void rutgon(); int getTS(); int getMS(); }; class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); void OnPaint(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP(CMyWin,CFrameWnd) ON_WM_PAINT() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow();

318

return TRUE; } CMyWin::CMyWin() { Create(NULL,"Chuong trinh su dung DLL - vidu10.2"); } void CMyWin::OnPaint() { CPaintDC dc(this); char s[200]; sprintf(s,"sum(5,19) is %d",sum(5,19)); dc.TextOut(10,10,s,strlen(s)); sprintf(s,"varible m is %d",m); dc.TextOut(10,40,s,strlen(s)); CMyClass a(5,7),b(3,5),c; c = a+b; sprintf(s,"(a=%d/%d) + (b=%d/%d) is (c=%d/%d)", a.getTS(),a.getMS(),b.getTS(),b.getMS(), c.getTS(),c.getMS()); dc.TextOut(10,70,s,strlen(s)); }

Kết quả chạy thử chương trình sẽ là:

Chúng ta thấy việc sử dụng thư viện động theo kiểu liên kết không tường mình (implicite) sẽ khá phức tạp, thực hiện khá nhiều bước trong chương trình.

319

10.3.2. Liên kết tường minh
Liên kết tường minh sẽ thực hiện liên kết mỗi khi chương trình cần đến thư viện, do đó thư viện sẽ không được nạp vào máy khi chương trình chạy mà chúng ta phải thực hiện nạp thư viện bằng các câu lệnh trong chương trình. Để thực hiện được liên kết tường minh chúng ta phải khai báo các thành phần giao diện trong thư viện DLL qua tệp .DEF với từ khóa EXPORTS. Các bước thực hiện liên kết tường minh: Bước 1: Gọi hàm LoadLibrary() để nạp thư viện DLL vào máy và lưu giữ số hiệu của module tương ứng với thư viện được nạp. Mẫu hàm khai báo như sau:
HMODULE LoadLibrary( LPCTSTR filename );

Trong đó filename là tên tệp của thư viện cần nạp vào máy, có thể bao gồm cả đường dẫn. Nếu thành công hàm trả về số hiệu định danh (HMODULE hoặc HINSTANCE) thư viện đã được nạp, ngược lại trả về NULL. Trong trường hợp không có đường dẫn thì máy sẽ tự động tìm trên các thư mục của hệ thống như: thư mục hiện tại, thư mục hệ thống, thư mục windows,... Bước 2: Gọi hàm GetProcAddress() để xác định địa chỉ của một hàm trong thư viện sau khi được nạp, địa chỉ hàm này được lưu bởi con trỏ hàm và dùng để gọi hàm. Mẫu hàm khai báo như sau:
FARPROC GetProcAddress( HMODULE hModule, LPCSTR lpProcName );

Trong đó hModule là số hiệu của thư viện được lấy từ giá trị trả về bởi hàm LoadLibrary(), lpProcName là tên của hàm trong môđun thư viện cần lấy địa chỉ. Hàm trả về kiểu con trỏ hàm tương ứng với tên hàm. Mẫu khai báo kiểu con trỏ hàm lưu địa chỉ của một hàm như sau:
typedef số); kiểu-hàm (CALLBACK *kiểu-con-trỏ)( các-kiểu-tham-

320

Sau đó dùng kiểu-con-trỏ để khai báo các con trỏ lưu địa chỉ hàm cần sử dụng trong chương trình. Ví dụ:
typedef UINT (CALLBACK* FPointer)(DWORD,UINT); .. HINSTANCE hDLL; // Handle to DLL FPointer lpfnDllFunc1; // Function pointer DWORD dwParam1; UINT uParam2, uReturnVal; hDLL = LoadLibrary("MyDLL"); if (hDLL != NULL) { lpfnDllFunc1 = (FPointer)GetProcAddress(hDLL, "DLLFunc1"); if (!lpfnDllFunc1) { // handle the error FreeLibrary(hDLL); return SOME_ERROR_CODE; } else { // call the function uReturnVal = lpfnDllFunc1(dwParam1, uParam2); } }

Bước 3: Gọi hàm FreeLibrary() sau khi đã sử dụng xong các hàm trong thư viện. Mẫu hàm như sau:
BOOL AFXAPI FreeLibrary( HMODULE hInstLib );

Trong đó hInstLib là số hiệu thư viện cần giải phóng.

Ví dụ 10.3
Lập trình tạo một thư viện có khai báo giao diện qua tệp .DEF và lập một chương trình sử dụng thư viện đó để nạp và thực hiện các hàm trong thư viện. Thư viện ở đây chúng ta thiết kế có 2 hàm như sau:

321

int int

max(int *a, int n); sum(int *a, int n);

Bước 1: Xây dựng thư viện, tạo hai tệp vidu03lib.cpp và vidu03lib.def với nội dung như sau:
// T p đ nh nghĩa giao di n cho thư vi n LIBRARY "vidu03lib.dll" DISCRIPTION 'thu vien cho vidu03' EXPORTS myMAX @1 mySUM @2 // T p chương trình thư vi n cho ví d 03 #include<afxwin.h> class CMyLib : public CWinApp { public: CMyLib() { AfxMessageBox("Bat dau cua thu vien DLL"); } }; CMyLib theLib; int myMAX(int *a,int n) { for (int m=0,i=0; i<n;i++) if (a[m]<a[i]) m=i; return a[m]; } int mySUM(int *a,int n) { for (int s=0,i=0; i<n;i++) s=s+a[i]; return s; }

Sau khi dịch thư viện trên ta được tệp vidu03lib.dll và chép vào thư mục hệ thống của Windows để liên kết. Bước 2: Xây dựng chương trình có sử dụng thư viện trên gồm một tệp main.cpp như sau:

322

#include<afxwin.h> class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMyWin : public CFrameWnd { public: CMyWin(); void OnPaint(); DECLARE_MESSAGE_MAP() }; CMyApp theApp; BEGIN_MESSAGE_MAP(CMyWin,CFrameWnd) ON_WM_PAINT() END_MESSAGE_MAP() BOOL CMyApp::InitInstance() { m_pMainWnd = new CMyWin; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } CMyWin::CMyWin() { Create(NULL,"Lien ket DLL tuong minh - vidu10.3"); } void CMyWin::OnPaint() { CPaintDC dc(this); typedef int (CALLBACK *FPointer)(int*,int); FPointer fp; HINSTANCE h1; h1= LoadLibrary("vidu03lib"); if (h1==NULL) { AfxMessageBox("Khong nap duoc thu vien 'vidu03lib.dll'"); return; } int a[5]={1,54,2,-12,5};

323

int n=5; char s[100]; fp=(FPointer)GetProcAddress(h1,"mySUM"); if (fp!=NULL) { sprintf(s,"SUM is %d",fp(a,n)); dc.TextOut(10,10,s,strlen(s)); } fp=(FPointer)GetProcAddress(h1,"myMAX"); if (fp!=NULL) { sprintf(s,"MAX is %d",fp(a,n)); dc.TextOut(10,40,s,strlen(s)); } FreeLibrary(h1); }

Kết quả chạy chương trình trên màn hình sẽ là:

10.4. Bài tập
Bài tập 10.1
Lập trình tạo một thư viện có các hàm để thực hiện các thuật toán trên mảng như: sắp xếp, đảo ngược, xóa bỏ phần tử, chèn thêm phần tử, tìm max, tìm min, tính tổng. Lập chương trình sử dụng thư viện trên để thực hiện các thuật toán đó và hiện kết quả lên cửa sổ.

Bài tập 10.2
Lập trình tạo một thư viện có một lớp hoạt động theo kiểu stack và một lớp hoạt động theo kiểu queu trên dữ liệu số thực.

324

Lập chương trình sử dụng thư viện đó để mô phỏng hai cấu trúc dữ liệu này lên cửa sổ. Cho phép nhập vào một số và đẩy vào stack và queu, cho phép loại bỏ phần tử trong stack và queu.

Bài tập 10.3
Lập một thư viện có một hàm vẽ ngôi sao gồm n cánh, với bán kính ngoài và bán kính trong của ngôi sao là R và r. Vẽ thông qua một DC của cửa sổ nào đó. Lập chương trình sử dụng thư viện trên nhập vào 3 số n, R, r để vẽ lên cửa sổ ngôi sao đó.

Bài tập 10.4
Xây dựng một thư viện chứa các hàm tính toán và xử lý các phép toán ma trận như: nhân, cộng, tìm max theo hàng, theo cột,… Lập chương trình sử dụng thư viện trên để thử nghiệm.

Bài tập 10.5
Xây dựng một thư viện chứa các hàm xử lý phép tính toán số nguyên lớn gồm: phép cộng, trừ, nhân, chia. Lập chương trình sử dụng thư viện trên để thử nghiệm.

325

Chương 11 LẬP TRÌNH ACTIVEX
11.1. Các khái niệm cơ bản
ActiveX là một trong những nguyên lý lập trình trên Windows rất hay, tuy nhiên tương đối khó để nắm vững và triển khai được nguyên lý này. Nó đóng vai trò rất quan trọng trong việc phát triển phần mềm trên Windows. Hệ thống con ActiveX là rất lớn và rất phức tạp, vì vậy trong tài liệu này chỉ dừng lại ở mức trình bày đơn giản và từng bước làm quen với nguyên lý này qua các ví dụ. Điều khiển ActiveX đòi hỏi nhiều kỹ thuật lập trình phức tạp hơn, do đó chúng ta cần nắm khá nhiều vấn đề ở đây. Tuy nhiên một cách đơn giản là chúng ta sử dụng "ActiveX Control Wizard" cung cấp bởi Visual C++ để thực hiện. Chúng tôi sẽ cố gắng trình bày với cách đơn giản nhất để bạn có thể hiểu được các vấn đề trong lập trình ActiveX.

11.1.1. Khái niệm về ActiveX
ActiveX là một tập các phương thức và thủ tục cho phép bạn tạo các bộ phận cấu thành phần mềm (software component). Một software component có thể được sử dụng lại ở nhiều chương trình khác nhau. Software component là một trong các đích đến của công nghệ phần mềm. Đối với một hệ thống đơn việc sử dụng software component không mang ý nghĩa lớn bởi các ứng dụng sẽ cài đặt đầy đủ và người dùng muốn nâng cấp thì phải cài đặt mới một phiên bản phần mềm tốt hơn. Ưu điểm lớn của software component là việc sử dụng trên mạng, khi có yêu cầu sử dụng thành phần ứng dụng trên một máy trạm thì nó sẽ được

326

download tự động từ máy chủ. Từ khi có Internet chúng ta thấy các ứng dụng (đặc biệt như World Wide Web) đều phải sử dụng kỹ thuật này. Các software component được cài đặt trên máy chủ Web và chỉ khi nào Website yêu cầu thực hiện một chức năng thì thành phần tương ứng sẽ được tải xuống máy trạm và thực hiện.

11.1.2. OLE và ActiveX
OLE (Object Link Embed) là một bước tiến bắt đầu cho việc hình thành kỹ thuật lập trình ActiveX. Bước đầu OLE được thiết kế cho phép các ứng dụng liên kết (Link) hoặc nhúng (Embed) thông tin được tạo ra bởi các ứng dụng khác, khi đó một tài liệu kết hợp được tạo ra. OLE đã trải qua một bước phát triển khá dài bắt đầu từ năm 1991, cho đến nay là phiên bản OLE 3.0 và đã được định nghĩa thêm đặc tính Component Object Model (COM). Đặc tính này đã dẫn tới công nghệ ActiveX ngày nay. Để đáp ứng cho các ứng dụng và dịch vụ trên mạng Internet, Microsoft định nghĩa ActiveX vào năm 1996. Dựa trên kỹ thuật OLE, nó cung cấp thêm cho các ứng dụng Web. Tuy nhiên từ khi ActiveX ra đời và cung cấp tất cả những gì mà OLE có thì thuật ngữ OLE gần như bị quên lãng.

11.1.3. Component Object Model (COM)
ActiveX dựa trên mô hình đối tượng thành phần (COM). Mô hình này định nghĩa cách tương tác giữa các ứng dụng có chứa ActiveX với nhau. Đặc biệt nó định nghĩa giao diện chuẩn để một đối tượng có thể sử dụng để đưa ra các chức năng của nó tới các đối tượng khác. Trong mô hình đối tượng thành phần có hai kiểu ứng dụng: kiểu chứa (container) và kiểu chủ (server). Trong kiểu chứa thì ứng dụng sẽ yêu cầu dữ liệu, còn kiểu chủ thì ứng dụng sẽ cung cấp dữ liệu. Một thuật ngữ khác của container là client (khách). Cách để một client (container) và server giao tiếp với nhau thông qua giao diện được định nghĩa bởi COM. ActiveX cũng sử dụng giao diện của mô hình COM và một điều khiển ActiveX sẽ nằm trong xử lý chủ (server), còn ứng dụng sử dụng ActiveX đó có thể coi là khách (client).

327

11.1.4. Giao diện COM
Giao diện COM được thể hiện như là một bảng các con trỏ hàm, những con trỏ này trỏ tới các hàm có trong thư viện (OLE hoặc ActiveX). Để đưa ra một giao diện, server trả về một con trỏ tới bảng của giao diện tương ứng. Khi client tìm liên lạc với server, nó chứa một con trỏ tới bảng giao diện tương ứng trên server. Client sau đó gọi các hàm được cung cấp bởi server qua các con trỏ trong bảng hàm. Client không biết hoặc không truy xuất được thông tin chi tiết các hàm mà nó gọi. Nó chỉ biết là đang truy xuất một hàm chuẩn thông qua giao diện chuẩn. Tất cả các giao diện COM được định nghĩa bởi một bộ định danh toàn cục duy nhất - Globally Unique IDentifier (GUID), nó phân biệt một giao diện COM với những giao diện COM khác. Định danh duy nhất này được tạo nên từ 11 phần bao gồm 1 giá trị long, 2 giá trị WORD và 8 giá trị BYTE. Các giá trị này thường được sinh ra bởi công cụ thứ 3, có thể sử dụng công cụ GUIDGEN để thực hiện. Hầu hết ActiveX đều thể hiện các giao diện định nghĩa bởi COM, tuy nhiên có một giao diện mà ActiveX dùng đó là IUnknown. Sử dụng hàm QueryInterface() được định nghĩa bởi IUnknown để một ứng dụng có thể tìm thấy các giao diện có trong ứng dụng khác. Giá trị cơ bản trong giao diện là một ứng dụng chứa COM có thể tận dụng những ưu điểm của các chức năng cung cấp bởi những chương trình COM.

11.1.5. MFC và ActiveX
MFC cung cấp công nghệ ActiveX được gọi là OLE Controls Developer Kit (CDK) và nó nằm trong một phần của thư viện MFC. Công nghệ ActiveX trong MFC là sự kết hợp giữa các lớp (class) và các macro, gồm 3 lớp chính đó là COleControlModule, COleControl, COlePropertyPage. Ngoài 3 lớp này còn có một tập các macro được định nghĩa để xây dựng và phát triển điều khiển ActiveX. Lớp COlePropertyPage thể hiện giao diện đồ họa của bảng thuộc tính cho điều khiển ActiveX. Lớp này có thể không cần sử dụng trong xây dựng điều khiển ActiveX và chúng ta sẽ không đề cập đến nhiều trong chương này.
328

Ngoài ra MFC còn cung cấp các công cụ để phát triển như công cụ sinh các giá trị định danh GUID, công cụ kiểm thử điều khiển ActiveX, công cụ sinh chương trình khung xây dựng điều khiển ActiveX.

11.2. Xây dựng điều khiển ActiveX
Khác với những chương trình ứng dụng trước đây, chương trình ActiveX khá phức tạp và cách tiếp cận khác với chương trình ứng dụng. Đối với một chương trình ứng dụng bao gồm các thành phần cơ bản sau: - Kiểu ứng dụng là chương trình thi hành trên Windows - Chương trình chứa một lớp dẫn xuất của CWinApp và một lớp dẫn xuất của CWnd - Chương trình chứa một hoặc nhiều kiểu tài nguyên Chương trình ActiveX có chia sẻ một vài đặc tính của chương trình ứng dụng, tuy nhiên có một số điều khác biệt như sau. Chương trình ActiveX được thể hiện như một thư viện liên kết động, khác với một chương trình ứng dụng thi hành như trước đây. Kiểu chương trình DLL sẽ thích hợp hơn cho các đòi hỏi của software component, bởi vì nó thiết kế dưới dạng môđun và được sử dụng bởi các client (ứng dụng) khác. Tệp tin thư viện DLL thể hiện cho điều khiển ActiveX có phần mở rộng là OCX (OLE Control Extension) thay cho DLL. Tương tự một thư viện liên kết động DLL, chương trình dạng ActiveX cần một tệp chứa các định nghĩa. Môđun này thực hiện hai việc đó là: thứ nhất là khai báo tên các hàm được xuất ra bởi DLL, nó chính là các hàm trong điều khiển ActiveX nhưng được gọi từ các chương trình client ngoài; thứ hai là khai báo hai hàm DllRegisterServer() và DllUnregisterServer(), cho phép điều khiển tự đăng ký trong hệ thống, khi đó nó sẽ cập nhật các thông tin cần thiết vào đăng ký hệ thống (registry). Tệp chứa môđun định nghĩa có phần mở rộng là DEF. Một ví dụ khai báo trong tệp DEF là:

329

// T p vidu01.DEF LIBRARY "VIDU01.OCX" EXPORTS DllCanUnloadNow @1 PRIVATE DllGetClassObject @2 PRIVATE DllRegisterServer @3 PRIVATE DllUnregisterServer @4 PRIVATE

Một thành phần khác quan trọng là tệp tin ngôn ngữ mô tả đối tượng (Object Description Language - ODL), có phần mở rộng tương ứng là ODL. Trong tệp này chứa mô tả các giao diện được thể hiện bởi đối tượng điều khiển, nó sẽ được dịch vào trong tệp thư viện của điều khiển tương ứng. Một ví dụ của tệp ODL như sau:
// T p vidu01.ODL #include <olectl.h> #include <idispids.h> [ uuid(12CD1DBB-0FAA-42A1-B5D7-BE2DCF1E48A8), version(1.0), helpfile("Vidu01.hlp"), helpstring("Vidu01 ActiveX Control module"), control ] library VIDU01Lib { importlib(STDOLE_TLB); importlib(STDTYPE_TLB); // Primary dispatch interface for CVidu01Ctrl [ uuid(14253E53-34FE-404B-AEEF-7023A0B32C79), helpstring("Dispatch interface for Vidu01 Control"), hidden ] dispinterface _DVidu01 { properties: methods: }; // Event dispatch interface for CVidu01Ctrl [ uuid(708CED8D-F919-41FE-8DAF-86846D5F4528), helpstring("Event interface for Vidu01 Control") ] dispinterface _DVidu01Events { properties: methods:

330

}; // Class information for CVidu01Ctrl [ uuid(AADFA311-1DB0-4053-8ADF-FCC79ED405B6), helpstring("Vidu01 Control"), control ] coclass Vidu01 { [default] dispinterface _DVidu01; [default, source] dispinterface _DVidu01Events; }; };

Trong tệp ví dụ ODL này có hai giao diện rỗng. Thứ nhất là giao diện chuyển phát chính (primary dispatch interface), có tên _Dmycontrol. Giao diện này sẽ thể hiện cho các thuộc tính và phương thức của điều khiển ActiveX. Giao diện thứ hai có tên là _DmycontrolEvents, chứa các sự kiện của điều khiển khi thi hành. Tuy nhiên chúng ta chưa khai báo các thuộc tính, phương thức và sự kiện trong ví dụ trên. Chúng ta hãy chú ý tới các giá trị phức tạp và có vẻ như vô nghĩa ở ví dụ trên, đó chính là các định danh duy nhất được sinh bởi công cụ GUIDGEN. Giá trị GUID thứ nhất là định danh của thư viện chứa điều khiển, GUID thứ 2 và thứ 3 tương ứng là định danh chính và định danh các sự kiện của giao diện. Cuối cùng là định danh cho coclass của đối tượng mycontrol. Ngoài ra chúng ta có tệp khai báo và tài nguyên chuẩn, ví dụ như sau:
// T p resource.H #define IDS_VIDU01 1 #define IDD_PROPPAGE_VIDU01 200 #define IDB_VIDU01 1 // T p vidu01.RC #include<afxres.h> #include"resource.h" STRINGTABLE DISCARDABLE BEGIN IDS_VIDU01 "Vidu01 Control" END 1 TYPELIB "Vidu01.tlb"

331

Tài nguyên bitmap ở trên có định danh là IDB_MYCONTROL phải là một ảnh bitmap có kích thước 16x15 pixels. Bitmap này sẽ hiển thị trên thanh công cụ cho điều khiển ActiveX khi sử dụng. Lệnh cuối cùng trong tệp tài nguyên là:
1 TYPELIB "Vidu01.tlb"

nó chỉ ra tệp tài nguyên này sẽ được bao gồm vào trong thư viện chương trình. Trong ví dụ trên chúng ta đặt tên thư viện là "mycontrol.lib", với định danh là 1. Trong chương trình điều khiển ActiveX còn có các tệp tin chính chứa các lớp và nội dung chương trình. Các tệp này thể hiện sự cài đặt các lớp gồm lớp môđun điều khiển COleControlModule và lớp điều khiển ActiveX COleControl.

11.2.1. Lớp COleControlModule
Một điều khiển ActiveX đóng vai trò như một in-process server, khác với một ứng dụng độc lập. Điều khiển sẽ chứa một đối tượng môđun ở bên trong đó, tạo ra từ lớp COleControlModule. Khác với ứng dụng trước đây là lớp CWinApp sẽ chứa framework. Mục đích sử dụng hai lớp này là như nhau. Lớp COleControlModule thừa kế hai hàm đó là InitInstance() và ExitInstance(). Hai hàm này có thể được viết đè trong lớp dẫn xuất của COleControlModule tương ứng, chúng thực hiện các công việc khởi tạo và kết thúc môđun của điều khiển ActiveX. Tuy nhiên chúng ta không cần thực hiện gì nhiều đối với lớp COleControlModule, chỉ gần gọi hai hàm ở lớp cơ sở tương ứng. Một ví dụ của hai hàm trong lớp dẫn xuất:
BOOL l p_d n_xu t :: InitInstance() { return COleControlModule::InitInstance(); } BOOL l p_d n_xu t :: ExitInstance() { return COleControlModule:: ExitInstance(); }

332

11.2.2. Lớp COleControl
Trọng tâm của xây dựng điều khiển ActiveX là lớp dẫn xuất của lớp COleControl. Lớp này cung cấp các tính năng như lớp dẫn xuất của CWnd trong chương trình ứng dụng. Nó cài đặt cho tất cả các giao diện và các hàm người dùng của điều khiển ActiveX. Trong lớp dẫn xuất của COleControl, thường bạn khai báo một cấu tử (hàm tạo) để thực hiện các khởi tạo giao diện sự kiện và phân phối chính mà điều khiển cung cấp. Để khởi tạo giao diện chúng ta phải gọi hàm COleControl::InitializeIIDs(), có khai báo như sau:
void COleControl :: InittializeIIDs( const IID *DispatchInterface, const IID *EventInterface);

Trong đó DispatchInterface là một con trỏ tới giao diện phân phối chính của điều khiển, trong ví dụ trên là _Dmycontrol và EventInterface là một con trỏ tới giao diện sự kiện, trong ví dụ trên là _DmycontrolEvents. Hai tham số này do chúng ta truyền vào và các giá trị định danh duy nhất GUID được sinh bởi GUIDGEN nằm trong tệp ODL của chương trình. Thông thường hai biến kiểu IID để truyền vào hàm trên được định nghĩa trong chương trình đặt bằng hai giá trị GUID tương ứng. Ví dụ khai báo hai biến tương ứng truyền vào cho hàm InitializeIIDs() theo ví dụ trên là:
const IID BASED_CODE IID_DVidu01 = { 0x14253e53, 0x34fe, 0x404b, { 0xae, 0xef, 0x70, 0x23, 0xa0, 0xb3, 0x2c, 0x79 } }; const IID BASED_CODE IID_DVidu01Events = { 0x708ced8d, 0xf919, 0x41fe, { 0x8d, 0xaf, 0x86, 0x84, 0x6d, 0x5f, 0x45, 0x28 } };

Nếu điều khiển ActiveX của bạn có hiển thị trong chương trình khi sử dụng thì cần phải viết đè hàm COleControl::OnDraw(). Hàm này tương tự như hàm OnDraw() của lớp CWnd, vẽ lại cửa sổ khi cần thiết. Hàm được khai báo như sau:

333

virtual

void

COleControl::OnDraw( CDC *DC, const CRect const CRect &ControlRect, &InvalidateRegion);

Trong đó tham số DC là con trỏ tới ngữ cảnh thiết bị sử dụng bởi điều khiển. ControlRect là vùng hiện tại được hiển thị bởi điều khiển và InvalidateRegion là vùng có thể vẽ lại. Chúng ta có thể thực hiện vẽ tùy ý lên vùng này. Chúng ta sử dụng các macro để thực hiện các khai báo ở trên được dễ dàng hơn trong việc xây dựng chương trình, bao gồm các macro sau:

11.2.3. Các macro của ActiveX
Trong các chương trước chúng ta có các macro để định nghĩa như: + DECLARE_MESSAGE_MAP(), + DECLARE_DYNCREATE(), + BEGIN/END_MESSAGE_MAP(), + IMPLEMENT_DYNCREATE(). - Macro tạo lớp động Lớp của điều khiển ActiveX cũng là lớp động, được tạo ra bởi yêu cầu của chương trình sử dụng, do đó cần phải sử dụng cặp macro DECLARE_DYNCREATE() và IMPLEMENT_DYNCREATE() để thực hiện trong lớp dẫn xuất của COleControl. - Macro cho lớp factory Để tạo một thi hành của điều khiển ActiveX, chương trình sử dụng điều khiển đó phải gọi một hàm thành viên của lớp "factory" của điều khiển. Lớp factory là một lớp độc lập đáp ứng cho việc tạo một đối tượng của lớp khác, ở đây là lớp dẫn xuất COleControl. Lớp factory này đáp ứng yêu cầu cho mọi đối tượng ở dạng COM. Khai báo lớp factory này phải sử dụng cặp macro với khai báo như sau:
DECLARE_OLECREATE_EX( tên_lớp )

334

IMPLEMENT_OLECREATE_EX( tên_lớp, tên_ngoài, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8 )

Trong đó tên_lớp là tên của lớp điều khiển sẽ được tạo bởi lớp factory này, tên_ngoài là của điều khiển. Tên ngoài này được sử dụng ở bên ngoài (trong chương trình dùng điều khiển đó) và để định danh điều khiển được tạo ra. Các tham số còn lại là các giá trị của GUID cho đối tượng coclass trong tệp ODL, nó bao gồm một giá trị long (l), 2 giá trị word (w1, w2) và 8 giá trị byte (b1,b2,...,b8). - Macro cho kiểu thư viện Mỗi điều khiển ActiveX đều sử dụng một kiểu thư viện (library) để mô tả các giao diện đưa ra cho chương trình dùng nó. Để thể hiện kiểu thư viện cho điều khiển ActiveX, MFC cung cấp hai macro sau:
DECALRE_OLETYPE( class_name ) IMPLEMENT_OLETYPELIB ( class_name, typelib, vermajor, verminor)

Trong đó class_name là tên lớp điều khiển được mô tả bởi thư viện, typelib là giá trị GUID của thư viện được tìm thấy ở khai báo đầu tiên trong tệp ODL, vermajor và verminor là hai giá trị mô tả phiên bản lớn nhỏ của kiểu thư viện. Ví dụ phiên bản 1.0 thì vermajor là 1 và verminor là 0. Thông thường giá trị typelib được định nghĩa dưới dạng một hằng theo giá trị GUID trong tệp ODL, sử dụng kiểu GUID CDECL để khai báo với ví dụ như sau:
const GUID CDECL BASED_CODE _tlid = { 0x12cd1dbb, 0xfaa, 0x42a1, { 0xb5, 0xd7, 0xbe, 0x2d, 0xcf, 0x1e, 0x48, 0xa8 } };

- Macro cho kiểu điều khiển và trạng thái khởi tạo Cặp macro tiếp theo sẽ mô tả tên ngoài của lớp điều khiển và một số kiểu trạng thái khác, cặp này được khai báo trong lớp điều khiển. Tên ngoài là một chuỗi ký tự, sẽ hiển thị trong giao diện các thành phần của chương trình dùng điều khiển ActiveX. Chuỗi này được định nghĩa trong tài nguyên bảng chuỗi STRING và được tham chiếu bởi một giá trị định danh ID. Trong ví dụ trên thì giá trị này là IDS_MYCONTROL.

335

Các kiểu trạng thái khác là những giá trị được hình thành khi điều khiển được tạo và nạp vào ứng dụng. Điều này đảm bảo ứng dụng không bắt buộc phải nạp đầy đủ các tham số trạng thái khi dùng điều khiển ActiveX. Nó bao gồm một hoặc nhiều giá trị sau:
Giá tr OLEMISC_SETCLIENTSITEFIRST OLEMISC_INSIDEOUT OLEMISC_CANTLINKINSIDE OLEMISC_RECOMPOSEONRESIZE Ý nghĩa Đi u khi n s truy xu t các thu c tính khi đư c n p Đi u khi n có th đư c kích ho t không qua giao di n ngư i dùng Đi u khi n không h tr liên k t v i bên ngoài Đi u khi n s t v l i n u có thay đ i

Sử dụng cặp macro sau để khai báo tên ngoài của lớp và trạng thái khác:
DECLARE_OLECTRLTYPE (c_name ) IMPLEMENT_OLECTRLTYPE( c_name , e_name, miscStatus )

Trong đó c_name là tên của lớp điều khiển được mô tả trong thư viện, e_name là ngoài của điều khiển sử dụng bởi ứng dụng chứa nó và miscStatus là các giá trị trạng thái ở bảng trên. - Ánh xạ thông điệp và ánh xạ giao diện Trong lập trình ứng dụng sử dụng cặp macro sau khai báo thực hiện ánh xạ cho các thông điệp tới các hàm thành viên, DECLARE_MESSAGE_MAP() và BEGIN/END_MESSAGE_MAP(). Tuy nhiên trong lớp điều khiển ActiveX chúng ta phải thực hiện thêm chuyển phát và ánh xạ sự kiện. Giao diện chuyển phát sẽ đưa ra các thuộc tính và phương thức cung cấp bởi điều khiển ActiveX tới ứng dụng. Cặp macro thực hiện điều này là:
DECLARE_DISPATCH_MAP()

và:
BEGIN_DISPATCH_MAP( owner , base ) END_ DISPATCH_MAP()

Trong đó owner là tên lớp được ánh xạ giao diện chuyển phát, base là tên lớp cơ sở của lớp owner. Lớp ánh xạ chuyển phát luôn là lớp dẫn xuất từ COleControl.
336

Để ánh xạ các sự kiện cho điều khiển ActiveX chúng ta sử dụng cặp macro sau:
DECLARE_EVENT_MAP()

và:
BEGIN_EVENT_MAP( owner, base ) END_EVENT_MAP()

Trong đó owner là tên lớp thực hiện ánh xạ sự kiện luôn là lớp dẫn xuất từ COleControl và base là lớp COleControl.

11.2.4. Đăng ký điều khiển
Một điều khiển ActiveX trước khi được sử dụng bởi các ứng dụng, lớp điều khiển và server (DLL) của nó phải được đăng ký với hệ thống Windows. Đăng ký này bao gồm một vài khoản mục trong cơ sở dữ liệu đăng ký. Các khoản mục này mô tả nhiều mặt của lớp điều khiển ActiveX: + Kiểu đối tượng điều khiển + Vị trí của server (DLL chứa điều khiển) + Các giá trị khởi tạo + Ảnh bitmap sử dụng hiển thị cho điều khiển trên thanh công cụ + Vị trí và định danh của kiểu thư viện (library) + Phiên bản của điều khiển - Đăng ký lớp điều khiển Để đăng ký lớp điều khiển ActiveX được thực hiện bằng cách viết đè hàm COleObjectFactory::UpdateRegistry(), hàm có khai báo như sau:
virtual void COleObjectFactory::UpdateRegistry(BOOL reg)=0;

Vậy hàm này là một hàm ảo nguyên thủy, do đó trong lớp dẫn xuất ắt phải viết đè nó với tham số regcls quy định cơ sở dữ liệu đăng ký có cập nhật hay không tương ứng với giá trị TRUE hoặc FALSE. Trong hàm viết đè này chúng ta phải gọi đến hàm AfxOleRegisterControlClass() để thực hiện đăng ký, với khai báo hàm như sau:
337

BOOL

AFXAPI

AfxOleRegisterControlClass( HINSTANCE hInstance, REFCLSID clsid, LPCTSTR pszProgID, UINT idTypeName, UINT idBitmap, int nRegFlags, DWORD dwMiscStatus, REFGUID tlid, WORD wVerMajor, WORD wVerMinor );

Tất cả các tham số đều bắt buộc truyền vào cho hàm khi thực hiện, được giải thích bởi bảng sau:
Tên tham s hInstance clsid pszProgID idTypeName idBitmap nRegFlags Ý nghĩa Giá tr đ nh danh c a đ i tư ng đi u khi n, nó chính là giá tr đ nh danh c a môđun ch a đi u khi n Đ nh danh c a l p, là GUID c a l p đ i tư ng đi u khi n n m trong t p ODL Giá tr GUID c a đi u khi n n m trong t p ODL S hi u tài nguyên chu i ng v i tên đi u khi n S hi u tài nguyên bitmap ng v i bi u tư ng Giá tr ch a m t ho c hai giá tr sau: * afxRegApartmentThreading - khóa đăng ký * afxRegInsertable - có xu t hi n trong h p tho i chèn đ i tư ng, thông thư ng ch dùng giá tr này. dwMiscStatus tlib wVerMajor wVerMinor M t trong các giá tr tr ng thái tùy ý Giá tr GUID trong t p ODL S phiên b n chính S phiên b n ph b ng trên

Ngoài ra hàm UpdateRegistry() của lớp COleObjectFactory có thể dùng để hủy bỏ đăng ký một điều khiển.
338

- Đăng ký điều khiển Server Để đăng ký server được thực hiện bằng hai hàm DllRegisterServer() và DllUnregisterServer() đã được đưa ra trong tệp DEF, khai báo hai hàm như sau:
STDAPI STDAPI DllRegisterServer(); DllUnregisterServer();

Trong cài đặt hàm DllRegisterServer() chúng ta phải gọi hàm AfxOleRegisterTypeLib() với khai báo sau:
BOOL AfxOleRegisterTypeLib( HINSTANCE hInstance, REFGUID tlid, LPCTSTR pszFileName = NULL, LPCTSTR pszHelpDir = NULL );

và gọi hàm COleObjectFactoryEx::UpdateRegistryAll() có mẫu khai báo như sau:
static void PASCAL COleObjectFactoryEx :: UpdateRegistryAll( );

Các hàm này sẽ kiểm tra xem kiểu thư viện tương ứng và các thông tin khác đã được đăng ký hay chưa, nếu không có khoản mục nào như vậy thì kiểu thư viện và điều khiển sẽ được đăng ký, ngược lại sẽ bỏ qua. Trong cài đặt hàm DllUnregisterServer() chúng ta phải gọi hai hàm ngược lại là AfxOleUnregisterTypeLib() có khai báo sau:
BOOL AFXAPI AfxOleUnregisterTypeLib( REFGUID tlID );

và hàm COleObjectFactoryEx::UpdateRegistryAll() để xác định nếu có khoản mục đăng ký của điều khiển thì sẽ được loại bỏ.

Ví dụ 11.1
Lập trình tạo một điều khiển ActiveX cho phép hiển thị lên ứng dụng khi sử dụng nó và vẽ lá cờ tổ quốc. Tệp khai báo vidu01.h như sau:

339

class CVidu01App : public COleControlModule { public: BOOL InitInstance(); int ExitInstance(); }; class CVidu01Ctrl : public COleControl { DECLARE_DYNCREATE(CVidu01Ctrl) public: CVidu01Ctrl(); public: virtual void OnDraw(CDC* pdc, const CRect& rB, const CRect& rV); protected: ~CVidu01Ctrl(); DECLARE_OLECREATE_EX(CVidu01Ctrl) DECLARE_OLETYPELIB(CVidu01Ctrl) DECLARE_OLECTLTYPE(CVidu01Ctrl) DECLARE_MESSAGE_MAP() DECLARE_DISPATCH_MAP() DECLARE_EVENT_MAP() }; extern const GUID CDECL _tlid; extern const WORD _wVerMajor; extern const WORD _wVerMinor;

Tệp chương trình vidu01.cpp là:
#include "afxctl.h" #include "Vidu01.h" #include "resource.h" IMPLEMENT_DYNCREATE(CVidu01Ctrl, COleControl) BEGIN_MESSAGE_MAP(CVidu01Ctrl, COleControl) END_MESSAGE_MAP() BEGIN_DISPATCH_MAP(CVidu01Ctrl, COleControl) END_DISPATCH_MAP() BEGIN_EVENT_MAP(CVidu01Ctrl, COleControl) END_EVENT_MAP() BEGIN_PROPPAGEIDS(CVidu01Ctrl, 1) END_PROPPAGEIDS(CVidu01Ctrl) IMPLEMENT_OLECREATE_EX(CVidu01Ctrl, "VIDU01.Vidu01Ctrl.1",

340

0xaadfa311, 0x1db0, 0x4053, 0x8a, 0xdf, 0xfc, 0xc7, 0x9e, 0xd4, 0x5, 0xb6) IMPLEMENT_OLETYPELIB(CVidu01Ctrl, _tlid, _wVerMajor, _wVerMinor) const IID BASED_CODE IID_DVidu01 = { 0x72d88209, 0x5cf7, 0x4c2c, { 0x92, 0x28, 0x87, 0x8b, 0x61, 0x6c, 0x8a, 0x71 } }; const IID BASED_CODE IID_DVidu01Events = { 0xb9e44700, 0x2b6d, 0x412e, { 0x8e, 0xc9, 0x5, 0xcc, 0x27, 0x71, 0x15, 0x81 } }; static const DWORD BASED_CODE _dwVidu01OleMisc = OLEMISC_SETCLIENTSITEFIRST | OLEMISC_INSIDEOUT | OLEMISC_CANTLINKINSIDE | OLEMISC_RECOMPOSEONRESIZE; IMPLEMENT_OLECTLTYPE(CVidu01Ctrl, IDS_VIDU01, _dwVidu01OleMisc) BOOL CVidu01Ctrl::CVidu01CtrlFactory::UpdateRegistry(BOOL bRegister) { if (bRegister) return AfxOleRegisterControlClass( AfxGetInstanceHandle(), m_clsid, m_lpszProgID, IDS_VIDU01, IDB_VIDU01, afxRegApartmentThreading, _dwVidu01OleMisc, _tlid, _wVerMajor, _wVerMinor); else return AfxOleUnregisterClass(m_clsid, m_lpszProgID); } CVidu01Ctrl::CVidu01Ctrl() { InitializeIIDs(&IID_DVidu01, &IID_DVidu01Events); } CVidu01Ctrl::~CVidu01Ctrl() { } void CVidu01Ctrl::OnDraw( CDC* pdc, const CRect& rB, const CRect& rV ) { double PI = 3.141592; pdc->FillRect(rB,new CBrush(RGB(255,0,0)));

341

CPen p; p.CreatePen(PS_SOLID,0,RGB(255,255,0)); pdc->SelectObject(&p); int cx,cy,x,y; double g=PI/2.0,r; cx=(rB.right-rB.left)/2; cy=(rB.bottom-rB.top)/2; r=((cx>cy)?cy:cx)*0.7; x=int(cx+r*cos(g)); y=int(cy-r*sin(g)); pdc->MoveTo(x,y); for (int i=0;i<10;i++) { g=g+2*PI/10; if (i%2==0) { x=int(cx+0.4*r*cos(g)); y=int(cy-0.4*r*sin(g)); } else { x=int(cx+r*cos(g)); y=int(cy-r*sin(g)); } pdc->LineTo(x,y); } CBrush b; b.CreateSolidBrush(RGB(255,255,0)); pdc->SelectObject(&b); pdc->FloodFill(cx,cy,RGB(255,255,0)); }

Kết quả khi kiểm thử điều khiển ActiveX như sau:

342

11.2.5. Công cụ Visual C++ tạo khung ActiveX
Chúng ta thấy việc thiết lập một bộ khung cho chương trình dạng ActiveX rất phức tạp và mất nhiều thời gian, trong môi trường Visual C++ cho phép tạo một khung chương trình kiểu ActiveX rất thuận tiện và đơn giản. Tiếp theo chúng ta có thể điều chỉnh, thêm bớt các thành phần mong muốn trong chương trình để có được một ActiveX hoàn thiện. Các bước thực hiện như sau: Bước 1: Tạo một project ứng với chương trình ActiveX sẽ phát triển: Chọn thứ tự các chức năng sau: [File] [New] chọn thẻ [Projects] chọn mục [MFC ActiveX ControlWizard] gõ tên chương trình vào ô [Project name] và chọn [Ok]

Sau khi chọn Ok sẽ xuất hiện hộp thoại để xác lập các yếu tố cho việc tạo một ActiveX như sau:

343

Trong đó [1] là số điều khiển cần tạo trong ActiveX, [2] quy định bản quyền (license), [3] quy định có hướng dẫn khi sinh mã, [4] quy định tạo tệp trợ giúp. Chọn [Next] sẽ xuất hiện hộp thoại:

344

Trong đó: + [Activates when visible]: điều khiển sẽ tự kích hoạt khi hiện nó được hiển thị. + [Invisible at runtime]: đối tượng dạng ẩn khi thực hiện. + [Avaible in "Insert Object" dialog]: điều khiển sẽ được hiển thị trong hộp thoại chèn đối tượng của các ứng dụng. + [Has an "About" box]: điều khiển sẽ có một hộp thoại giới thiệu. + [Acts as a simple frame control]: điều khiển tạo ra sẽ có một số điều khiển khác và hoạt động dưới dạng một frame. Sau đó chọn [Finish] để kết thúc quá trình tạo. Bước 2: Điều chỉnh các thành phần trong ActiveX vừa tạo. Sau khi tạo xong ActiveX sẽ có một số thành phần chính được tổ chức dưới dạng cây sau:

345

Trong đó: 1: là giao diện chính của điều khiển ActiveX sẽ được tạo ra, giao diện này dùng để các ứng dụng client tương tác với ActiveX. 2: là giao diện các sự kiện có thể thực hiện trên ActiveX, cho phép các ứng dụng client sử dụng các sự kiện đó yêu cầu ActiveX thực hiện. 3: lớp ứng dụng để tạo ra ActiveX, có hai hàm InitInstance() để khởi tạo và hàm ExitInstance() để giải phóng tài nguyên mỗi lần ActiveX được sử dụng bởi ứng dụng client. Lớp ứng dụng này kế thừa từ lớp COleControlModule. 4: lớp chứa điều khiển ActiveX, là lớp chính tạo ra ActiveX. Chúng ta phải xây dựng các hàm thành viên cần thiết để cung cấp xử lý cho ứng dụng client. Lớp này kế thừa từ lớp COleControl. 5: lớp chứa giao diện các thuộc tính thiết kế đối với ActiveX, trong lớp có hộp thoại chứa các thành phần để nhập sửa thuộc tính. Lớp này kế thừa từ lớp COlePropertyPage và chúng ta có thể bỏ qua sử dụng lớp này. 6: hàm DllRegisterServer() sẽ thực hiện đăng ký ActiveX vào hệ thống một cách tự động. 7: hàm DllUnregisterServer() sẽ tự động hủy bỏ đăng ký ActiveX khởi hệ thống khi không cần thiết sử dụng nữa. 8: chứa định danh toàn cục duy nhất (GUID) cho hai giao diện của ActiveX, gồm định danh giao diện chính cho ActiveX và định danh giao diện sự kiện cho ActiveX. 9: đối tượng ứng dụng của ActiveX được tạo ra để khởi tạo mỗi lần ActiveX được sử dụng bởi ứng dụng client. Bây giờ chúng ta có thể dịch và kiểm thử ActiveX vừa tạo ra, để điều chỉnh ActiveX ta thêm bớt các thành viên của lớp chứa điều khiển ActiveX ở mục 4 ở trên.

11.2.6. Công cụ kiểm thử ActiveX của VisualC++
Sau khi xây dựng thành công ActiveX chúng ta có thể kiểm thử nó bằng công cụ "ActiveX Control Test Container", sau đây là hướng dẫn sử dụng công cụ để kiểm thử ActiveX.

346

Bước 1: Chọn chức năng [Tools] xuất hiện hộp thoại sau:

[ActiveX Control Test Container]

sẽ

Bước 2: Chọn chức năng có biểu tượng như trên để xác định ActiveX cần kiểm thử, sẽ xuất hiện hộp thoại chọn sau:

Khi chọn ActiveX cần kiểm thử và Ok trên cửa sổ sẽ có điều khiển tương ứng với ActiveX mà chúng ta đã chọn, xem xét sự hoạt động của nó và có thể thực hiện các phương thức, các sự kiện nếu cần.

347

11.3. Các phương thức của ActiveX
Mặc định lớp điều khiển được sinh ra có hàm OnDraw() để thực hiện vẽ lên ứng dụng client, hàm OnResetState() để khôi phục trạng thái của ActiveX và hàm DoPropExchange() để thay đổi các giá trị thuộc tính khi nhập sửa thông qua hộp thoại từ lớp chứa giao diện thuộc tính - phần 5.

11.3.1. Thay đổi hàm OnDraw() và OnResetState()
Bấm đúp chuột vào tên hàm cần sửa sẽ xuất hiện mẫu hàm để viết thêm lệnh, ví dụ hàm OnDraw() sẽ có:

Hoặc thay đổi hàm OnResetState() sẽ có:

348

11.3.2. Xử lý thông điệp hoặc viết đè hàm thành viên
Chúng ta có thể thêm các thành viên hàm để xử lý thông điệp cho ActiveX theo từng bước sau: Chọn chức năng [View] [Class Wizard] sẽ xuất hiện hộp thoại sau

Chúng ta chọn tên lớp chứa ActiveX tương ứng, chọn tên hàm hoặc thông điệp cần xử lý trong ActiveX sau đó chọn [Add Function]. Ví dụ chọn tên thông điệp WM_TIMER sẽ xuất hiện hàm thành viên tương ứng trong danh sách [Member functions]:

Tiếp theo chọn chức năng [Edit Code] để viết lệnh cho hàm thành viên tương ứng. Ví dụ chọn hàm OnTimer sẽ xuất hiện mẫu sau:

349

11.3.3. Thêm phương thức thông thường trong lớp
Ngoài các phương thức để xử lý thông điệp, các phương thức viết đè của lớp cơ sở, chúng ta có thể thêm các phương thức xử lý khác bằng các thao tác sau: Bấm chuột phải vào tên lớp ActiveX sẽ xuất hiện:

Chọn chức năng [Add Member Function…] để thêm một phương thức, sẽ có hộp thoại sau:

350

Nhập tên kiểu của hàm vào ô [Function Type] và nhập tên hàm cùng với các tham số cần thêm vào ô [Function Declaration], chọn phạm vi cho hàm và kiểu hàm và chọn Ok. Ví dụ như trên sẽ xuất hiện mẫu hàm sau:

Tiếp theo chúng ta viết lệnh cho hàm để xử lý công việc tương ứng. Ngoài ra chúng ta có thể thêm các thành viên dữ liệu để lưu thông tin mô tả và xử lý cần thiết trong ActiveX.

Ví dụ 11.2
Lập trình tạo một điều khiển ActiveX để hiển thị đồng hồ của hệ thống khi ứng dụng client sử dụng nó. Chúng ta phải xử lý thông điệp WM_TIMER trong ActiveX, để khởi động chu kỳ thời gian chúng ta có thể thực hiện trong hàm xử lý thông điệp WM_CREATE. Các bước thực hiện là: Bước 1: Tạo một project kiểu ActiveX theo hướng dẫn như trên. Bước 2: Thêm hàm xử lý thông điệp WM_TIMER và WM_CREATE và viết lệnh như sau: Hàm xử lý thông điệp tạo cửa sổ WM_CREATE là:

351

Hàm xử lý thông điệp thời gian WM_TIMER là:

Bước 3: Viết lại hàm OnDraw() để hiện thời gian lên điều khiển ActiveX tương ứng.

Kết quả sau khi dịch và chạy thử ActiveX sẽ như sau:

352

11.3.4. Phương thức cho giao diện của ActiveX
Các phương thức xét ở trên chỉ là việc trong nội bộ của ActiveX, không sử dụng được từ ứng dụng client sử dụng ActiveX tương ứng. Để tạo phương thức trong giao diện của ActiveX và có thể dùng bởi các ứng ụng client ta làm như sau: Bước 1: Bấm chuột phải vào tên giao diện chính của ActiveX.

và chọn [Add Method…] sẽ xuất hiện:

353

Trong đó: + External name : tên ngoài của phương thức, tức là tên có trong giao diện để ứng dụng client có thể sử dụng để gọi phương thức. + Internal name : tên bên trong của phương thức, là tên chính thức. + Return type : kiểu dữ liệu trả về của phương thức. + Parametter list : danh sách các tham số, bao gồm tên tham số và kiểu của tham số. Sau khi nhập xong chọn Ok sẽ có một phương thức được định nghĩa trong giao diện chính, một hàm thành viên tương ứng trong lớp điều khiển ActiveX như sau:

Bước 2: Viết lệnh cho hàm ứng với phương thức được tạo ra. Bấm đúp chuột vào tên hàm sẽ xuất hiện mẫu viết lệnh như sau:

354

11.4. Các sự kiện của ActiveX
Tương tự các phương thức, các sự kiện dành cho ActiveX được tạo ra bằng công cụ Wizard của VisualC++, chúng ta có thể chọn các sự kiện có trong danh sách máy cung cấp hoặc tự tạo một sự kiện mới. Sự kiện của một ActiveX sẽ được viết mã lệnh mà chỉ định nghĩa ở giao diện, nó sẽ được viết lệnh bởi lập trình viên sử dụng trong ứng dụng client. Cách tạo một sự kiện cho ActiveX như sau: Bước 1: Bấm chuột phải vào giao diện sự kiện của ActiveX chúng ta sẽ có:

Chọn chức năng [Add Event…] sẽ xuất hiện hộp thoại để nhập các thông tin tạo sự kiện cho ActiveX sau:

Chọn tên sự kiện máy cung cấp trong danh sách hoặc gõ tên mới, nhập thêm các tham số nếu có, chọn Ok sẽ có thêm một sự kiện trong giao diện tương ứng sau:

355

11.5. Các thuộc tính của ActiveX
Các thuộc tính của ActiveX được định nghĩa trong giao diện chính, mỗi thuộc tính tạo ra sẽ tương ứng với một thành viên dữ liệu trong lớp điều khiển ActiveX. Cách tạo thuộc tính tương tự tạo phương thức ở trên. Bước 1: Bấm chuột phải vào giao diện chính của ActiveX chọn [Add Property…] sẽ xuất hiện:

Bước 2: Trong đó điền các thông tin để thêm thuộc tính như sau: + External name : tên thuộc tính trên giao diện, sử dụng để tương tác với ứng dụng client. + Type : kiểu thuộc tính. + Variable name : tên thành viên dữ liệu của lớp điều khiển tương ứng với thuộc tính. + Notification function : hàm chỉ báo tương ứng cho thuộc tính. + Member variable : thuộc tính sẽ là thành viên dữ liệu đơn thuần.
356

- Get/Set methods : thuộc tính sẽ có hàm xác định và thiết lập giá trị. Bước 3: Viết lệnh cho các hàm thành viên tương ứng với thuộc tính được tạo ra, có thể hàm chỉ báo thay đổi giá trị thuộc tính, hàm xác định hoặc thiết lập giá trị cho thuộc tính.

Ví dụ 11.3
Lập trình tạo một điều khiển ActiveX tương ứng là một ngôi sao n cánh, màu sắc và kích thước được thiết lập bằng thuộc tính, có các phương thức quay và dừng để có thể quay quanh tâm của ngôi sao đó với tốc độ quay truyền vào qua tham số. Thiết kế điều khiển ActiveX gồm: - Giao diện chính: có 3 thuộc tính số cánh (SoCanh), màu sắc (MauSac), kích thước (KichThuoc) và các phương thức Quay( float tốc_độ ), Dung(). - Giao diện sự kiện: không có. - Trong lớp ActiveX phải viết lại hàm OnDraw() để vẽ ngôi sao. Sau đây là sơ đồ mẫu thiết kế cho ActiveX tương ứng các thuộc tính, phương thức của giao diện, thành viên dữ liệu, thành viên hàm của lớp điều khiển. Các bạn hãy viết lệnh để thực hiện theo thiết kế sau:

357

11.6. Sử dụng ActiveX
Sau khi xây dựng xong các ActiveX, dịch ra tệp thi hành dạng *.OCX. Nếu một ứng dụng client sử dụng ActiveX thì nó sẽ tự động đăng ký vào hệ thống bởi hàm DllRegisterServer(). Việc sử dụng các ActiveX trong các ứng dụng client như Internet Explorer, các môi trường lập trình khác Visual Basic, Visual Foxpro,… Ví dụ sử dụng ActiveX trong trình duyệt Web Internet Explorer, trong trang Web bạn phải dùng thẻ HTML Object theo mẫu sau:
<object classid = "clsid:D4061177-B2F4-4429-BD18-6E6CBBE80CC5" id = "Bai21"> <param name = "_ExtentX" value="2646"> <param name = "_ExtentY" value="1323"> <param name = "_StockProps" value="0"> </object>

Trong đó classid là định danh của đối tượng lớp ứng với ActiveX được sử dụng, số hiệu này chính là giá trị định danh duy nhất được sử dụng để xây dựng ActiveX tương ứng - GUID. Kết quả trên Web sẽ xuất hiện đối tượng của ActiveX và hoạt động như sau:

Có thể sử dụng ActiveX vào trong ngôn ngữ VB theo cách sau: trên môi trường VB chọn Project Components xuất hiện hộp thoại:

358

Đánh dấu vào mục ứng với ActiveX đã đăng ký chọn Ok, sẽ xuất hiện trên thanh công cụ biểu tượng của ActiveX sau:

Tiếp theo bạn chọn biểu tượng và vẽ lên Form đối tượng của ActiveX tương ứng, kết quả sẽ có:

Trên đây là ví dụ sử dụng ActiveX vào các ứng dụng client, bạn đọc có thể ứng dụng nó vào bất kỳ môi trường nào có hỗ trợ ActiveX Container. Ngoài ra bạn đọc có thể thay đổi các ActiveX của ví dụ trên cho phù hợp hoặc tự xây dựng các ActiveX mới tùy ý.
359

11.7. Bài tập
Bài tập 11.1
Hãy dùng công cụ Wizard của Visual C++ để tạo một ActiveX, sau đó thêm các thành phần để ActiveX hoạt động như một đồng hồ có 3 kim: kim giây, kim phút và kim giờ.

Bài tập 11.2
Tạo một ActiveX có một quả bóng chuyển động và phản xạ trở lại trong vùng cửa sổ của ActiveX.

Bài tập 11.3
Tạo một ActiveX có các hình elíp đồng tâm và thay đổi mà theo thứ tự tạo cảm giác của gợn sóng.

Bài tập 11.4
Tạo một ActiveX mô phỏng dao động tắt dần của con lắc đơn.

360

TÀI LIỆU THAM KHẢO
Tiếng Anh [1] [2] [3] [4] Tiếng Việt [5] [6] Lập trình Windows bằng Visual C++, Đặng Văn Đức, Lê Quốc Hưng, NXB Giáo dục, 2001. C++ và lập trình hướng đối tượng, Phạm Văn Ất, NXB Khoa học và Kỹ thuật, 2000. MFC Programming from the Ground Up, Herbert Schildt, Tata McGraw-Hill Publishing Company Limited, 2000. Programming Microsoft Visual C++, David J. Kruglinski, George Sepherd và Scott Wingo. Microsoft Developer Network Library Edition, CDs-ROM, 2000-2004. Resoures on Google’s Search Engine, Internet.

361

Sign up to vote on this title
UsefulNot useful