BỘ GIAO THÔNG VẬN TẢI TRƢỜNG ĐẠI HỌC HÀNG HẢI BỘ MÔN: KHOA HỌC MÁ Y TÍNH KHOA: CÔNG NGHỆ THÔNG TIN

BÀI GIẢNG LẬP TRÌNH WINDOWS

TÊN HỌC PHẦN : Lập trình Windows MÃ HỌC PHẦN : 17214 TRÌNH ĐỘ ĐÀO TẠO : ĐẠI HỌC CHÍNH QUY DÙNG CHO SV NGÀNH : CÔNG NGHỆ THÔNG TIN

HẢI PHÕNG - 2010

Bài giảng môn học: Lâ ̣p trình Windows

Tên học phần: Lập trình Windows Loại học phần: 2

Bộ môn phụ trách giảng dạy: Khoa học Máy tính

Khoa phụ trách: CNTT

Mã học phần: 17214 Tổng số TC: 3

TS tiết Lý thuyết Thực hành/Xemina Tự học Bài tập lớn Đồ án môn học 60 30 30 0 0 0

Điều kiện tiên quyết:

Sinh viên phải học xong các học phần sau mới đƣợc đăng ký học phần này:

Lâ ̣p trình hƣớ ng đối tƣơ ̣ng, Cấu trú c dƣ̃ liê ̣u

Mục tiêu của học phần:

hành phần của hệ điều - Cung cấp các kiến thức cơ bản về lâ ̣p trình trƣ̣c quan trên hê ̣ điều hành Windows - Cung cấp các kiến thƣ́ c về truy câ ̣p và can thiê ̣p vào các t hành Windows

Nội dung chủ yếu

Các kiến thức về thao tác với file và thƣ mục , cơ sở dƣ̃ liê ̣u registry , các luồng , tiến trình, dịch vụ, các thƣ viện liên kết động và lập trình sockets trên Windows.

Nội dung chi tiết của học phần:

PHÂN PHỐI SỐ TIẾT TS LT TH/Xemina BT KT 0 0 0

hê ̣ điều hành

3 1 1 1

0

TÊN CHƢƠNG MỤC Chƣơng I. Các khái niệm cơ bản 1.1. Giới thiệu về môi trƣờ ng lâ ̣p trình trên Windows 1.1.1. Cở sở về hê ̣ điều hành Windows 1.1.2. Các phiên bản của Windows 1.1.3. Vai trò củ a Windows trên thi ̣ trƣờ ng phần mềm 1.2. Thƣ viê ̣n Win32 và Win64 1.2.1. Win32 API 1.2.2. Win64 API 1.3. Giớ i thiê ̣u về bô ̣ công cu ̣ Visual Studio 2005 Chƣơng II. Hê ̣ thố ng file và thƣ mục 2.1. Truy câ ̣p và sƣ̉ du ̣ng hê ̣ thống file trên môi trƣờ ng Windows 2.1.1. Hê ̣ thống file và thƣ mu ̣c củ a Windows 2.1.2. Các thao tác với file và thƣ mục trên Windows 2.1.3. Các vấn đề liên quan tới Unicode 2.2. Các ví dụ về thao tác vớ i file 2.2.1. Tạo file và xử lý các lỗi liên quan 4 2 1 4 2 1

i

Bài giảng môn học: Lâ ̣p trình Windows

PHÂN PHỐI SỐ TIẾT TS LT TH/Xemina BT KT

1

1

của file và

0

4 1 1 2 6 1 2 3

0

4 2 1 1 6 2 2 2

1 1 1 1

0

TÊN CHƢƠNG MỤC 2.2.2. Copy file 2.2.3. Hiển thi ̣ danh sách các file trong thƣ mu ̣c hiê ̣n thờ i 2.3. Quản lý file và thƣ mục nâng cao 2.3.1. Con trỏ file 2.3.2. Truy câ ̣p tớ i các thuô ̣c tính thƣ mu ̣c Chƣơng III. Hê ̣ thố ng cơ sở dƣ̃ liê ̣u Registry 3.1. Khái niệm và vai trò của CSDL Registry 3.1.1. Các khóa, các hive 3.1.2. Các kiểu dữ liệu 3.2. Quản lý CSDL Registry 3.2.1. Thay đổi khóa 3.2.2. Thêm mớ i khóa 3.2.3. Liê ̣t kê các khóa 3.3. Can thiê ̣p Windows qua Registry 3.3.1. Thay đổi giao diê ̣n 3.3.2. Thay đổi các thiết lâ ̣p đối vớ i các ổ đĩa 3.3.3. Thay đổi các thiết lâ ̣p vớ i ngƣờ i dù ng Chƣơng IV. Quản lý các tiến trình và luồng 4.1. Các tiến trình và luồng trên Windows 4.2. Các thao tác với tiến trình 4.2.1. Tạo tiến trình 4.2.2. Kết thú c và thoát khỏi mô ̣t tiến trình 4.2.3. Các thao tác với biến môi trƣờng của Windows 4.2.4. Ví dụ : Ghi nhâ ̣t ký thờ i gian thƣ̣c hiê ̣n của các tiến trình 4.3. Quản lý luồng (thread) trên Windows 4.3.1. Các khái niệm cơ bản 4.3.2. Mô hình Boss /Worker và các mô hình khác 4.3.3. Bô ̣ nhớ dành cho luồng 4.3.4. Độ ƣu tiên và các trạng thái của luồng 4.4. Mô ̣t số ví du ̣ về tiến trình và luồng 4.4.1. Tìm kiếm song song với các tiến trình 4.4.2. Thuâ ̣t toán sắp xếp trô ̣n bằng đa luồng Chƣơng V. Các dịch vụ của Windows 5.1. Tổng quan về di ̣ch vu ̣ trên Windows 5.2. Các thành phần của một dịch vụ 5.2.1. Hàm main() 5.2.2. Hàm ServiceMain() 5.2.3. Kiểm soát di ̣ch vu ̣ qua các Handler 5.3. Ví du: dịch vụ đơn giản trên Windows 4 1 1 6 2 2 1

ii

Bài giảng môn học: Lâ ̣p trình Windows

PHÂN PHỐI SỐ TIẾT TS LT TH/Xemina BT KT

2 2

1

0 0

4 1 0,5 0,5 2

0 0

4 0,5 0,5 0,5 2 0,5 4 1 1 2 4 0,5 1 2,5

TÊN CHƢƠNG MỤC 5.4. Quản lý các dịch vụ của Windows 5.4.1. Các phƣơng pháp kiểm soát các dịch vụ của Windows 5.4.2. Ví dụ : Điều khiển các di ̣ch vu ̣ củ a Windows Chƣơng VI. Lâ ̣p trình ma ̣ng vớ i Sockets 6.1. Khái niệm sockets trên Windows 6.2. Các hàm sockets phía server 6.3. Các hàm sockets phía client 6.4. Ứng dụng mạng đơn giản 6.4.1. Phía server 6.4.2. Phía client 6.5. Windows Sockets 2.0 Chƣơng VII. Thƣ viê ̣n liên kết đô ̣ng 7.1. Khái niệm và ứng dụng của thƣ viện liên kết đô ̣ng 7.2. Hê ̣ thống thƣ viê ̣n DLL củ a Windows 7.3. Các bƣớc tạo một thƣ viện DLL 7.3.1. Tạo thƣ viê ̣n DLL 7.3.2. Viết ƣ́ ng du ̣ng go ̣i tớ i thƣ viê ̣n DLL Nhiệm vụ của sinh viên :

Tham dự các buổi thuyết trình của giáo viên, tự học, tự làm bài tập do giáo viên giao,

tham dự các bài kiểm tra định kỳ và cuối kỳ.

Tài liệu học tập :

- Lê Hƣ̃u Đa ̣t. Lập trình Windows. NXB Giáo du ̣c. - Charles Petzold. Programming Windows, fifth edition. Microsoft Press. 1998.

- Johnson M. Hart. Windows System Programming Third Edition. Addison Wesley

Professional. 2004.

Hình thức và tiêu chuẩn đánh giá sinh viên:

- Hình thức thi cuối kỳ : Thi vấn đáp.

- Sinh viên phải đảm bảo các điều kiện theo Quy chế của Nhà trƣờng và của Bộ

Thang điểm: Thang điểm chữ A, B, C, D, F

Điểm đánh giá học phần: Z = 0,3X + 0,7Y.

iii

Bài giảng môn học: Lâ ̣p trình Windows

MỤC LỤC

LỜI NÓI ĐẦU ............................................................................................................................ 1

CHƢƠNG 1: CÁC KHÁI NIỆM CƠ BẢN ............................................................................... 2

1. Giới thiệu về môi trƣờng lập trình Windows .................................................................. 2

1.1 Cơ sở về hệ điều hành Windows ............................................................................ 2

1.2 Các phiên bản của hệ điều hành Windows ............................................................. 2

1.3 Vai trò của Windows trên thị trƣờng phần mềm .................................................... 3

2. Thƣ viện Win32 và Win64 .............................................................................................. 3

2.1 Win32 API .............................................................................................................. 3

2.2 Win64 API .............................................................................................................. 4

3. Các bƣớc phát triển ứng dụng trên Windows .................................................................. 4

3.1 Chƣơng trình Win32 đơn giản nhất. ....................................................................... 4

3.2 Chƣơng trình cƣ̉ a sổ đơn giản ................................................................................. 5 3.3 Quản lý các thông điệp ......................................................................................... 14

3.4 Vòng lặp xử lý thông điệp .................................................................................... 17

Bài tập: .............................................................................................................................. 20 CHƢƠNG 2: HỆ THỐ NG FILE VÀ THƢ MỤC .................................................................... 21

1. Truy câ ̣p và sƣ̉ du ̣ng hê ̣ thống file trên môi trƣờ ng Windows ....................................... 21

2. Các ví dụ về thao tác với file ......................................................................................... 21

2.1 Serialization .......................................................................................................... 21

2.2 Cài đặt một lớp Serializable .................................................................................. 24

3. Quản lý file và thƣ mục nâng cao .................................................................................. 40

Bài tập: .............................................................................................................................. 40 CHƢƠNG 3: HỆ THỐ NG CSDL REGISTRY ........................................................................ 41

1. Khái niệm và vai trò của CSDL Registry ...................................................................... 41

1.1 Các khóa, các hive ................................................................................................ 41

1.2 Các kiểu dữ liệu .................................................................................................... 42

2. Quản lý CSDL Registry ................................................................................................. 43

2.1 Thay đổi khóa ....................................................................................................... 43

2.2 Thêm mớ i khóa ..................................................................................................... 43

2.3 Liê ̣t kê các khóa .................................................................................................... 44

3. Can thiê ̣p Windows qua Registry .................................................................................. 44

3.1 Thay đổi giao diê ̣n ................................................................................................ 44

3.2 Thay đổi các thiết lâ ̣p đối vớ i các ổ đĩa ................................................................ 44

i

Bài giảng môn học: Lâ ̣p trình Windows

3.3 Thay đổi các thiết lâ ̣p vớ i ngƣờ i dù ng .................................................................. 44

Bài tập: .............................................................................................................................. 44

CHƢƠNG 4: QUẢN LÝ CÁC TIẾN TRÌNH VÀ LUỒNG .................................................... 45

1. Các tiến trình và luồng trên Windows ........................................................................... 45

2. Các thao tác với tiến trình ............................................................................................. 46

2.1. Tạo tiến trình ........................................................................................................ 46

2.2. Kết thúc và thoát khỏi một tiến trình ................................................................... 47

2.3. Các thao tác với biến môi trƣờng của Windows .................................................. 47

2.4. Ví dụ: Ghi nhâ ̣t ký thờ i gian thƣ̣c hiê ̣n củ a các tiến trình .................................... 47 3. Quản lý luồng (thread) trên Windows ........................................................................... 49

3.1. Các khái niệm cơ bản ........................................................................................... 49

3.2. Mô hình Boss/Worker và các mô hình khác ........................................................ 49

3.3. Bô ̣ nhớ dành cho luồng ........................................................................................ 49

3.4. Độ ƣu tiên và các trạng thái của luồng ................................................................ 50

4. Mô ̣t số ví du ̣ về tiến trình và luồng ............................................................................... 50 4.1. Tìm kiếm song song với các tiến trình................................................................. 50

4.2. Thuâ ̣t toán sắp xếp trô ̣n bằng đa luồng ................................................................ 52

Bài tập: .............................................................................................................................. 55

CHƢƠNG 5: CÁC DỊCH VỤ CỦA WINDOWS .................................................................... 56

1. Tổng quan về di ̣ch vu ̣ trên Windows ............................................................................. 56

2. Các thành phần của một dịch vụ ................................................................................... 56

2.1 Hàm main() ........................................................................................................... 56

2.2 Hàm ServiceMain() ............................................................................................... 56

2.3 Kiểm soát dịch vụ qua các Handler ...................................................................... 56

3. Ví du: dịch vụ đơn giản trên Windows .......................................................................... 57

4. Quản lý các dịch vụ của Windows ................................................................................ 60

4.1 Các phƣơng pháp kiểm soát các dịch vụ của Windows ........................................ 60

4.2 Ví dụ : Điều khiển các di ̣ch vu ̣ củ a Windows ....................................................... 60

Bài tập: .............................................................................................................................. 64

CHƢƠNG 6: LẬP TRÌNH SOCKET ....................................................................................... 65

1. Khái niệm sockets trên Windows .................................................................................. 65

2. Các hàm sockets phía server.......................................................................................... 65

3. Các hàm sockets phía client .......................................................................................... 66

4. Ứng dụng mang đơn giản .............................................................................................. 66

ii

Bài giảng môn học: Lâ ̣p trình Windows

4.1 Phía server ............................................................................................................. 66

4.2 Phía client ............................................................................................................. 72

5. Windows Sockets 2.0 .................................................................................................... 74

Bài tập: .............................................................................................................................. 74 CHƢƠNG 7: THƢ VIỆN LIÊN KẾ T ĐỘNG ......................................................................... 75

7.1. Khái niệm và ứng dụng của thƣ viện liên kết động .................................................... 75

7.2. Hệ thống thƣ viện liên kết động của Windows .......................................................... 75

7.3. Các bƣớc tạo một thƣ viện DLL ................................................................................. 76

7.4. Chia sẻ bô ̣ nhớ giƣ̃a các thƣ viê ̣n liên kết đô ̣ng .......................................................... 83

7.5. Các vấn đề khác về thƣ viện liên kết động ................................................................. 84

Bài tập: .............................................................................................................................. 85

TÀI LIỆU THAM KHẢO ........................................................................................................ 86

ĐỀ THI THAM KHẢO ............................................................................................................ 87

iii

Bài giảng môn học: Lâ ̣p trình Windows

LỜI NÓI ĐẦU

Hệ điều hành Windows của Microsoft là hệ điều hành đƣợc cài đặt nhiều nhất trên các máy PC hiện nay. Sự phổ biến của Windows và nền tảng phần cứng của Intel dẫn tới sự cần thiết phải có những hiểu biết sâu về chúng, đặc biệt đối với những lập trình viên. Mục đích của học phần này là cung cấp cho học viên một cái nhìn tổng quan, từ cơ bản tới chi tiết về các khía cạnh của lập trình trên hệ điều hành Windows, từ các chi tiết trong cấu trúc của một chƣơng trình tới các khái niệm cấp cao về tiến trình, luồng, xử lý song song, thƣ viện DLL, lập trình Socket, can thiệp vào cơ sở dữ liệu Registry ....

Tài liệu này dựa trên những kinh nghiệm và nghiên cứu mà tác giả đã đúc rút, thu thập trong quá trình giảng dạy môn học Lập trình C trên Windows, cùng với sự tham khảo của các tài liệu của các đồng nghiệp , các tác giả trong và ngoài nƣớc , tƣ̀ điển trƣ̣c tuyến Wikipedia . Với bẩy chƣơng đƣợc chia thành các chủ đề khác nhau từ các khái niê ̣m cơ bản cho tới các thao tác với hệ thống file, thƣ mục, hệ thống CSDL Registry, quản lý tiến trình và luồng, lập trình quản lý dịch vụ, lập trình socket, thƣ viện liên kết động DLL… hy vọng sẽ cung cấp cho các em sinh viên , các bạn độc giả một tài liệu bổ ích . Mặc dù đã rất cố gắng song vẫn không tránh khỏi một số thiếu sót , hy vọng sẽ đƣợc các bạn bè đồng nghiệp , các em sinh viên , các bạn độc giả góp ý chân thành để tôi có thể hoàn thiện hơn nữa tài liệu này.

Xin gửi lời cảm ơn chân thành tới các bạn bè đồng nghiệp và Ban chủ nhiệm khoa

Công nghệ Thông tin đã tạo điều kiện giúp đỡ để tài liệu này có thể hoàn thành.

Hải phòng, tháng 06 năm 2010

Tác giả

Nguyễn Hƣ̃u Tuân

1

Bài giảng môn học: Lâ ̣p trình Windows

Chƣơng 1: Các khái niệm cơ bản

Tài liệu này đƣợc biên soạn để cung cấp cho ngƣời ho ̣c những kiến thức cơ bản vế việc

viết các chƣơng trình sử dụng giao diện lập trình API trên môi trƣờng Win32. Ngôn ngữ đƣợc sử dụng là ngôn ngữ C, hầu hết các trình biên dịch C++ hiện nay đều có thể dịch đƣợc các chƣơng trình mẫu trình bày trong tài liệu này. Hầu hết tất cả các thông tin đƣợc trình bày trong tài liệu này đều có thể ứng dụng cho bất cứ ngôn ngữ nào có thể truy cập các hàm API, chẳng hạn nhƣ Java, Assembly, và Visual Basic.

Tài liệu này đƣợc biên soạn không phải để dạy các bạn độc giả lập trình bằng ngôn ngữ C, hoặc dạy chúng ta sử dụng bất cứ một trình biên dịch cụ thể nào (chẳng hạn nhƣ Borland C++, Visual C++, …) tuy nhiên trong phần phụ lục tôi sẽ dành một chút để cung cấp cho các bạn một số chú ý về một số trình biên dịch mà tôi đã sử dụng.

1. Giới thiệu về môi trƣờng lập trình Windows

1.1 Cơ sở về hệ điều hành Windows

Hệ điều hành Windows là một hệ điều hành dành cho ngƣời dùng cuối (End User Operating System) với các tính năng cơ bản sau: đa nhiệm, giao diện đồ họa, plug and play và quan trọng nhất là Windows Interface Based - tức là giao diện các chƣơng trình chạy trên Windows đều có dạng các cửa sổ.

1.2 Các phiên bản của hệ điều hành Windows

Do cách dùng tiếng Anh và việc hiểu tiếng Anh dẫn tới việc nhiều ngƣời hiểu về các phiên bản của hệ điều hành Windows chƣa chính xác. Ví dụ có bạn cho rằng Windows XP Professional Edition và Windows XP Home Edition là hai phiên bản khác nhau của hệ điều hành Windows. Thực ra nhƣ vậy vừa đúng lại vừa không đúng, đúng là Windows XP Professional Edition và Windows XP Home Edition là hai Edition khác nhau của cùng 1 Version Windows XP, có lẽ sai là vì hiểu từ Edition và Version sai. Version có nghĩa là một phiên bản, thƣờng đi kèm với các số hiệu của phiên bản (1.0, 1.2. … 5.0) và thƣờng là một thay đổi lớn đối với bản thân phần mềm, ví dụ nhƣ đối với Windows thì có 3 thay đổi lớn: thay đổi về kiến trúc nền tảng của hệ điều hành (tức là phần kernel của hệ điều hành), hai là cập nhật các bản vá (patch) cho các lỗi của phiên bản trƣớc đó đối với tất cả các phần của hệ điều hành, ba là các phần mới của hệ điều hành (có thể là các ứng dụng đi kèm hoặc hỗ trợ thêm các công nghệ mới, ví dụ nhƣ đối với Windows là chuẩn Wi-Fi, DVD, dot NET framework hay các ứng dụng nhƣ Windows Media Player, IE …).

Còn Edition là ấn bản khác nhau của cùng một phiên bản, các Edition thƣờng gắn với các yếu tố về địa lý, ngôn ngữ khác nhau (ví dụ nhƣ Compact Edition nghĩa là bản rút gọn, Standard Edition là bản chuẩn, Ultimate Edition là bản có các tính năng cao cấp nhất …). Đối với hệ điều hành Windows các Edition khác nhau thƣờng phân biệt bởi các tính năng của chúng, do nhắm tới việc phục vụ các đối tƣợng khác nhau nên Microsoft bỏ đi một số tính năng không cần thiết và tăng thêm các tính năng mà đối tƣợng ngƣời dùng hay dùng ví dụ nhƣ bản Home Edition nhắm tới ngƣời dùng gia đình nên các tính năng đồ họa, video, âm thanh phải tốt, còn bản Professional nhắm tới các ngƣời dùng chuyên nghiệp có trình độ cao nên các tính năng hệ thống sẽ cao hơn.

Windows có các phiên bản sau đây:

Windows 1.01 Windows 2.03

2

Bài giảng môn học: Lâ ̣p trình Windows

Windows 2.11 Windows 3.0 Windows 3.1x Windows For Workgroups 3.1 Windows NT 3.1 Windows For Workgroups 3.11 Windows 3.2 (released in Simplified Chinese only) Windows NT 3.5 Windows NT 3.51 Windows 95 Windows NT 4.0 Windows 98 Windows 98 SE Windows 2000 Windows Me Windows XP Windows XP 64-bit Edition 2003 Windows Server 2003 Windows XP Professional x64 Edition Windows Fundamentals for Legacy PCs Windows Vista Windows Home Server Windows Server 2008 Windows 7

Tất nhiên là mỗi Version trên lại có nhiều Edition khác nhau. Phần nhân (Kernel – Core) của hệ điều hành luôn là phần quan trọng nhất của một hệ điều hành. Đối với Windows nhân gồm 3 thành phần: các dịch vụ chạy ở mức nhân (kernel-mode service, để phân biệt với các dịch vụ chạy ở mức ứng dụng) gồm các thƣ viện chính của hệ điều hành, các thƣ viện thực hiện quản lý tiến trình, lập lịch, quản lý vào ra dữ liệu trên đĩa cứng, bộ nhớ. Phần 2 là các thƣ viện làm việc với các phần cứng ở mức chung, phần 3 là các Diver.

1.3 Vai trò của Windows trên thị trƣờng phần mềm

Do sự thống trị của hãng Microsoft nói riêng và sự phổ biến gần nhƣ độc tôn của hệ điều hành Windows nói chung ở Việt Nam nên Windows đóng vai trò hết sức quan trọng trong việc phát triển phần mềm ở Việt Nam. Về bản chất các chƣơng trình đều phải thực hiện trên một nền tảng (platform) nhất định bao gồm các chi tiết từ phần cứng cho tới phần mềm, tuy nhiên đối với đa số ứng dụng, các lập trình viên cần quan tâm nhiều nhất tới hệ điều hành mà ứng dụng sẽ chạy.

2. Thƣ viện Win32 và Win64

2.1 Win32 API

Win32 API hay thƣờng đƣợc viết tắt là Win32 là phiên bản 32 bit tƣơng ứng với hệ điều hành 32 bit của Windows. Win32 bao gồm các hàm đƣợc cài đặt của hệ thống, chẳng hạn nhƣ các hàm trong hệ thống Win16 bit, dƣới dạng các file thƣ viện DLL của hệ thống. Lõi (core) của Win32 là các file thƣ viện kernel32.dll, user32.dll và gdi32.dll. Win32 đầu tiên đƣợc đƣa ra cùng với hệ điều hành Windows NT. Phiên bản đầu tiên của Win32 đƣợc phát hành cùng với hệ điều hành Windows 95 (gọi là Win32c - compatible), và sau này chỉ còn là Win32.

3

Bài giảng môn học: Lâ ̣p trình Windows

2.2 Win64 API

Win64 là phiên bản 64 bit của thƣ viện Win32, là một biến thể dành cho các nền tảng 64 bit của hệ điều hành Windows (ví dụ hiện tại là AMD64 và IA-64). Cả hai phiên bản 32 và 64 bit của một ứng dụng đều có thể đƣợc biên dịch từ một mã chƣơng trình chung, mặc dù có một số khác biệt trong hai phiên bản này. Sự khác nhau cơ bản của hai phiên bản là các con trỏ địa chỉ của Win32 là 32 bit, còn với Win64 là 64 bit.

3. Các bƣớc phát triển ứng dụng trên Windows

3.1 Chƣơng trình Win32 đơn giản nhất.

Nếu bạn là một ngƣời mới bắt đầu lập trình trên môi trƣờng Win32 thì thao tác đầu tiên mà bạn cần phải làm đƣợc đó là dịch một chƣơng trình cơ bản trên môi trƣờng Windows. Hãy gõ đoạn chƣơng trình trên vào và nếu mọi thứ suôn sẻ bạn sẽ có một chƣơng trình đơn giản nhất (vô nghĩa) trong số các chƣơng trình mà bạn đã làm.

Hãy nhớ là dịch chƣơng trình này theo kiểu dịch một chƣơng trình viết bằng ngôn ngữ C, không phải ngôn ngữ C++. Trong hầu hết các trƣờng hợp điều này đơn giản thực hiện bằng cách đổi phần tên mở rộng từ cpp thành c. Chẳng hạn bạn có thể đặt tên cho file chƣơng trình sau là test.c và bắt đầu làm việc.

#include

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

MessageBox(NULL, "Goodbye, cruel world!", "Note", MB_OK);

return 0;

}

Nếu nhƣ chƣơng trình không làm việc bƣớc đầu tiên mà bạn cần làm là đọc tất cả các lỗi (error) mà chƣơng trình báo lên và nếu nhƣ bạn không hiểu ý nghĩa của chúng hãy tra trong bất cứ một quyển sách dạy lập trình hoặc các sách hƣớng dẫn đi kèm với trình biên dịch mà bạn đang sử dụng. Hãy chắc chắn là bạn có đầy đủ các file mà trình biên dịch yêu cầu. Thật không may là tôi không thể giúp nhiều trong trƣờng hợp này vì các lỗi phụ thuộc vào trình biên dịch và ngƣời sử dụng (trong việc gây lỗi cũng nhƣ sửa lỗi).

Bạn có thể nhận đƣợc một vài cảnh báo từ trình biên dịch về việc chƣơng trình không sử dụng các tham số đƣợc cung cấp cùng với hàm WinMain(). Nhƣng không sao điều quan trọng là bạn dịch chƣơng trình thành công và có một chƣơng trình Win32 thực sự. Chúng ta sẽ phân tích các đoạn mã chƣơng trình kỹ càng hơn.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

WinMain() là hàm chính của một chƣơng trình trên môi trƣờng Windows giống nhƣ hàm main trên môi trƣờng DOS và UNIX. Đó là nơi các đoạn mã chƣơng trình sẽ đƣợc thực thi. Các tham số của hàm main gồm có:

HINSTANCE hInstance

4

Bài giảng môn học: Lâ ̣p trình Windows

Tham số này quản lý module chƣơng trình (file .exe trong bộ nhớ). Hệ điều hành sử

dụng tham số này để quản lý chƣơng trình khi thực hiện.

HINSTANCE hPrevInstance

Luôn là NULL đối với các chƣơng trình Win32.

LPSTR lpCmdLine

Các tham số dòng lệnh của chƣơng trình đƣợc truyền dƣới dạng một xâu. Không bao

gồm tên chƣơng trình.

int nCmdShow

Một số nguyên có thể truyền cho hàm ShowWindow(). Chúng ta sẽ bàn về tham số

này sau.

Tham số hInstance đƣợc dùng cho các công việc đại loại nhƣ nạp (load) các tài nguyên và bất cứ tác vụ nào khác đƣợc thực hiện theo kiểu từng module cơ bản. Một module là một file EXE hoặc file DLL đƣợc nạp vào chƣơng trình của bạn. Đối với hầu hết (không phải tất cả) các chƣơng trình đƣợc trình bày trong tài liệu này, chỉ có một module mà chúng ta cần quan tâm đó là file chƣơng trình (EXE).

Các qui ƣớc gọi hàm

WINAPI chỉ định qui ƣớc gọi hàm và đƣợc định nghĩa là _stdcall. Nếu nhƣ bạn không hiểu ý nghĩa của chúng thì cũng không cần bận tâm nhiều vì chúng không ảnh hƣởng tới tài liệu này chỉ cần nhớ là đây là một điều cần thiết (kiểu của hàm WinMain() là int, nhƣng kiểu gọi hàm là WINAPI).

Các kiểu dữ liệu Win32.

Các bạn sẽ thấy rằng rất nhiều từ khóa thông thƣờng hoặc các kiểu dữ liệu đều có các cách định nghĩa cụ thể trên môi trƣờng Windows, UINT là unsigned int, LPSTR là char *, vân vân. Việc chọn lựa sử dụng cách nào thực sự phụ thuộc vào bạn. Nếu bạn thích sử dụng char * hơn là LPSTR thì hãy sử dụng cách đó nhƣng cần phải kiểm soát đƣợc việc sử dụng của mình (cần nắm rõ các kiểu dữ liệu trƣớc khi thay chúng bằng một kiểu khác).

Bạn chỉ cần nắm vững một số nguyên tắc và sẽ hiểu ý nghĩa của các tên này: LP là long pointer. Trên môi trƣờng Win32 phần long này không quan trọng và hoàn toàn có thể bỏ qua. Còn nếu bạn không hiểu con trỏ là gì thì có lẽ bạn nên học lại về C hoặc bỏ qua chúng và tiếp tục.

Tiếp đến là LPCSTR: long pointer const là một hằng xâu, tức là một xâu không thể thay đối, ngoài ra giữa hai kiểu LPSTR và LPCSTR còn có thể có thêm một chữ T nhƣng nếu bạn không có ý định sử dụng các font Unicode trong chƣơng trình thì nó cũng chẳng có ý nghĩa gì đặc biệt.

3.2 Chƣơng trình cƣ̉ a sổ đơn giả n

Ví dụ: chƣơng trình gồm 1 cƣ̉ a sổ đơn giản trên Windows Đôi khi mo ̣i ngƣờ i hỏi nhau trong khi ho ̣ chát vớ i nhau bằng phần mềm IRC là “Làm thế nào để ta ̣o ra mô ̣t cƣ̉ a sổ ?”. Tôi e rằng điều này hoàn toàn không phải là mô ̣t điều đơn giản. Không khó khi ba ̣n biết mình đang làm gì nhƣng có khá ít nhƣ̃ng điều ba ̣n cần biết để tạo ra một cửa sổ , và những điều đó hoàn toàn có thể giải thích bằng một đoạn văn bản ngắn hoă ̣c khoảng mô ̣t vài phú t chat bằng IRC.

5

Bài giảng môn học: Lâ ̣p trình Windows

u vì thế ở đây tôi sẽ trình bày đoa ̣n mã

Tôi luôn muốn làm trƣớ c và ho ̣c kỹ càng sa chƣơng trình ta ̣o ra mô ̣t cƣ̉ a sổ trƣớ c và giải thích sau:

#include

const char g_szClassName[] = "myWindowClass"; /* tên lớp cửa sổ */

// Step 4: the Window Procedure

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam,

LPARAM lParam)

{

switch(msg)

{

case WM_CLOSE:

DestroyWindow(hwnd);

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

// để windows xử lý các thông điệp còn lại

return DefWindowProc(hwnd, msg, wParam, lParam);

}

return 0;

}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

WNDCLASSEX wc;

HWND hwnd;

MSG Msg;

//Step 1: Registering the Window Class

wc.cbSize = sizeof(WNDCLASSEX);

6

Bài giảng môn học: Lâ ̣p trình Windows

wc.style = 0;

wc.lpfnWndProc = WndProc; /* hàm xử lý thông điệp cửa sổ */

wc.cbClsExtra = 0;/* không cần thông tin thêm cho cửa sổ */

wc.cbWndExtra = 0;

wc.hInstance = hInstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); /* màu nền */

/*wc.hbrBackground = GetStockObject(WHITE_BRUSH); màu nền trắng */

wc.lpszMenuName = NULL; /* không có hệ thống thực đơn */

wc.lpszClassName = g_szClassName; /* tên lớp cửa sổ */

wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

/* đăng ký lớp cửa sổ */

if(!RegisterClassEx(&wc))

{

MessageBox(NULL, "Window Registration Failed!", "Error!",

MB_ICONEXCLAMATION | MB_OK);

return 0;

}

// Step 2: Creating the Window

// Tạo ra một thể nghiệm của lớp cửa sổ cho ứng dụng

hwnd = CreateWindowEx(

WS_EX_CLIENTEDGE,

g_szClassName,

"The title of my window",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,

NULL, NULL, hInstance, NULL);

if(hwnd == NULL)

{

MessageBox(NULL, "Window Creation Failed!", "Error!",

MB_ICONEXCLAMATION | MB_OK);

7

Bài giảng môn học: Lâ ̣p trình Windows

return 0;

}

// Hiển thị cửa sổ

ShowWindow(hwnd, nCmdShow);

UpdateWindow(hwnd);

// Step 3: The Message Loop

// Step 3: Tạo vòng lặp xử lý thông điệp

while(GetMessage(&Msg, NULL, 0, 0) > 0)

{

TranslateMessage(&Msg);

DispatchMessage(&Msg);

}

return Msg.wParam;

}

Hình 1 – Cƣ̉ a sổ của chƣơng trình khi chạy

Đây có lẽ là mô ̣t chƣơng trình windows đơn giản nhất mà ba ̣n có thể viết và chƣ́ c năng

của chƣơng trình đơn giản là tạo ra một cửa sổ cũng rất đơn giản . Bạn nên gõ chƣơng trình và biên di ̣ch để cha ̣y thƣ̉ và hãy đảm bảo là không có lỗi gì xảy ra . Bƣớ c 1: Đăng ký (Registering) lớ p cƣ̉ a sổ (Window).

Mô ̣t lớ p cƣ̉ a sổ (Window Class) chƣ́ a các thông tin về kiểu cƣ̉ a sổ

, bao gồm thủ tu ̣c Window củ a nó (thủ tục này kiểm soát cƣ̉ a sổ khi nó đƣơ ̣c ta ̣o ra và đáp ƣ́ ng la ̣i các sƣ̣ kiê ̣n ngƣờ i dù ng tác đô ̣ng lên cƣ̉ a sổ ), các icon lớn và nhỏ của cửa sổ, và màu nền của cửa sổ. Theo sổ tù y thích mà không cách này bạn có thể đăng ký một lớp và sau đó tạo ra bao nhiêu cửa cần chỉ rõ tất cả các thuô ̣c tính đó thêm mô ̣t lần nào nƣ̃a . Hầu hết các thuô ̣c tính mà ba ̣n thiết lâ ̣p trong lớ p cƣ̉ a sổ (window) có thể thay đổi đƣợc theo một cách rất cơ bản (thổ mô ̣c) nếu nhƣ ba ̣n thích. Và cần chú ý là lớp cửa sổ này không liên quan gì tới khái niệm các lớp trong C++.

const char g_szClassName[] = "myWindowClass";

Biến trên lƣu tên củ a lớ p cƣ̉ a sổ củ a chú ng ta , chúng ta sẽ sử dụng nó để đăng ký lớp

cƣ̉ a sổ củ a chú ng ta vớ i hê ̣ thống.

8

Bài giảng môn học: Lâ ̣p trình Windows

WNDCLASSEX wc;

wc.cbSize = sizeof(WNDCLASSEX);

wc.style = 0;

wc.lpfnWndProc = WndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hInstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

wc.lpszMenuName = NULL;

wc.lpszClassName = g_szClassName;

wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

if(!RegisterClassEx(&wc))

{

MessageBox(NULL, "Window Registration Failed!", "Error!",

MB_ICONEXCLAMATION | MB_OK);

return 0;

}

Đó là đoa ̣n mã chƣơng trình chú ng ta sƣ̉ du ̣ng trong hàm WinMain() để đăng ký lớ p cƣ̉ a sổ củ a chú ng ta . Chúng ta sẽ điền đầy đủ các thành viên của cấu trúc WNDCLASSEX và gọi tớ i hàm RegisterClassEx().

Các thành viên của cấu trúc ảnh hƣởng đến lớp cửa sổ gồm có:

cbSize: kích thƣớc của cấu trúc

style: Class Style (CS_*), để không nhầm lẫn với Window Style (WS_*). Thông thƣờ ng

thuô ̣c tính này đƣơ ̣c gán bằng 0.

lpfnWndProc: con trỏ trỏ tớ i thủ tu ̣c quản lý cƣ̉ a sổ cho lớ p cƣ̉ a sổ này.

cbClsExtra: Lƣơ ̣ng dƣ̃ liê ̣u thêm đƣơ ̣c cấp phát trong bô ̣ nhớ cho mỗi cƣ̉ a sổ thuô ̣c loa ̣i

này. Thông thƣờ ng cũng đƣơ ̣c gán bằng 0.

hInstance: Quản lý một thể nghiệm (instance) của ứng dụng (tham số đầu tiên củ a hàm WinMain()).

hIcon: Icon lớ n (thƣờ ng là 32x32) hiển thị khi chúng ta nhấn tổ hợp phím Alt+TAB.

9

Bài giảng môn học: Lâ ̣p trình Windows

hCursor: Con trỏ sẽ xuất hiê ̣n khi con trỏ chuô ̣t di chuyển qua vù ng cƣ̉ a sổ củ a chƣơng trình.

hbrBackground: Tham số sƣ̉ du ̣ng để thiết lâ ̣p mầu nền củ a cƣ̉ a sổ .

lpszMenuName: tên củ a tài nguyên menu đƣơ ̣c sƣ̉ du ̣ng cho các cƣ̉ a sổ củ a lớ p.

lpszClassName: Tên để đi ̣nh danh lớ p cƣ̉ a sổ

hIconSm: Icon nhỏ (16x16) hiển thi ̣ trên thanh task bar và góc trên bên trái củ a cƣ̉ a sổ .

Bạn không nên lo lắng nếu chƣa hiểu về các thuộc tính này, chúng sẽ đƣợc giải thích ở

các phần tiếp theo của tài liệu này . Mô ̣t điều khác cần phải nhớ là không nên cố nhớ nhƣ̃ng thuô ̣c tính này. Tôi rất ít khi (không muốn nói là không bao giờ ) nhớ các thuô ̣c tính cũng nhƣ các tham số của các hàm , điều đó hoàn toàn là mô ̣t cố gắng lãng phí thờ i gian và công sƣ́ c . Nếu nhƣ ba ̣n biết các hàm ba ̣n cần sƣ̉ du ̣ng trong mô ̣t chƣơng trình nào đó , hãy tra trong các tài liệu help. Nếu ba ̣n không có cá c file help hãy down chú ng về , các file kiểu này trên mạng không hề thiếu . Cuối cù ng thì ba ̣n sẽ biết các tham số củ a các hàm mà ba ̣n thƣờ ng xuyên sƣ̉ dụng.

Tiếp đến chú ng ta go ̣i tớ i hàm RegisterClassEx () và kiểm tra xem hàm này có thành

công hay không , nếu nhƣ hàm trả về sai chú ng ta sẽ hiển thi ̣ mô ̣t thông báo thất ba ̣i và dƣ̀ ng chƣơng trình.

Bƣớ c 2: Tạo ra cửa sổ chƣơng trình

, chúng ta có thể tạo ra các cửa sổ trong Khi đã đăng ký lớ p cƣ̉ a sổ

chƣơng trình. Các bạn nên tra các tham số của hàm CreateWindowEx () (bạn luôn nên làm vậy khi làm việc với các hàm API mới), nhƣng tôi sẽ giải thích mô ̣t chú t ở đây:

HWND hwnd;

hwnd = CreateWindowEx(

WS_EX_CLIENTEDGE,

g_szClassName,

"The title of my window",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,

NULL, NULL, hInstance, NULL);

Tham số đầu tiên (WS_EX_CLIENTEDGE) là kiểu cửa sổ mở rộng (extended), trong

trƣờ ng hơ ̣p này tham số này đƣợc sử dụng để tạo thành một đƣờng viền chìm bên trong xung quanh cƣ̉ a sổ . Có thể đặt bằng 0 nếu chú ng ta muốn khác . Cũng nên thay đổi các giá trị khác để xem chúng làm việc nhƣ thế nào.

Tiếp theo chú ng ta cần có tên lớ p (g_szClassName), tham số này báo cho hê ̣ thống biết loại cửa sổ mà chúng ta muốn tạo ra . Vì chúng ta muốn tạo ra một cửa sổ từ lớp mà chúng ta vƣ̀ a đăng ký nên chú ng ta sƣ̉ du ̣ng tên củ a lớ p cƣ̉ a sổ đó . Sau đó chú ng ta chỉ rõ tên củ a cƣ̉ a sổ hoă ̣c tiêu đề (xâu ký tƣ̣ ) sẽ xuất hiện trong vai trò là tham số Caption hoặc Title Bar của cƣ̉ a sổ củ a chú ng ta.

10

Bài giảng môn học: Lâ ̣p trình Windows

Tham số chú ng ta sƣ̉ du ̣ng WS_OVERLAPPEDWINDOW trong vai trò tham số Window Style. Có khá ít các tham số kiểu này do đó bạn nên kiểm tra và thay đổi giá trị của chúng để xem kết quả họat động nhƣ thế nào. Chúng ta sẽ bàn thêm về các tham số này sau.

Bốn tham số tiếp theo (CW_USEDEFAULT, CW_USEDEFAULT, 320, 240) là tọa độ X và Y củ a góc trên bên trái củ a cƣ̉ a sổ và các kích thƣớ c dài rô ̣ng củ a nó . Tôi đã thiết lâ ̣p giá trị của X và Y là CW _USEDEFAULT để windows tƣ̣ cho ̣n vi ̣ trí để đă ̣t cƣ̉ a sổ chƣơng trình khi cha ̣y. Hãy nhớ l à bên trái của màn hình là một giá trị 0 của X và nó tăng dần khi sang phải. Đỉnh củ a màn hình tƣơng ƣ́ ng vớ i giá tri ̣ 0 của Y tăng dần theo chiều đi xuống . Các đơn vị là các điểm ảnh , là đơn vị nhỏ nhất mà màn hình có thể hiển thi ̣ đƣơ ̣c ta ̣i đô ̣ phân giải hiê ̣n tại.

Tiếp theo (NULL, NULL, g_hInst, NULL) chúng ta có một handle quản lý cửa sổ cha của cửa sổ đƣợc sinh ra , mô ̣t handle quản lý menu , mô ̣t handle cho thể nghiê ̣m củ a ƣ́ ng du ̣ng và một con trỏ trỏ tớ i dƣ̃ liê ̣u cƣ̉ a sổ . Trong windows các cƣ̉ a sổ trên màn hình đƣơ ̣c sắp xếp theo mô ̣t cấu trú c phân cấp theo kiểu các cƣ̉ a sổ cha và con . Khi chú ng ta nhìn thấy mô ̣t nú t trên mô ̣t cƣ̉ a sổ , nút đó là con và nó đƣợc chứ a trong cƣ̉ a sổ là cha củ a nó . Trong ví du ̣ này của chúng ta handle của cửa sổ cha là NULL vì chúng ta không có cửa sổ cha nào cả , đây là cƣ̉ a sổ chính hoă ̣c cƣ̉ a sổ ở cấp cao nhất củ a chú ng ta . Menu cũng là NULL vì chú ng t a chƣa có menu chƣơng trình nào . Handle dành cho thể nghiê ̣m củ a chƣơng trình đƣơ ̣c gán là giá tri ̣ đƣơ ̣c truyền cho tham số đầu tiên củ a hàm WinMain (). Dữ liệu cửa sổ (thƣờ ng là chú ng ta chẳng bao giờ sƣ̉ du ̣ng chú ng ) có thể đƣợ c sƣ̉ du ̣ng để gƣ̉ i các dƣ̃ liê ̣u khác tớ i cƣ̉ a sổ đang đƣơ ̣c ta ̣o ra và cũng là NULL.

Nếu ba ̣n đang băn khoăn về giá tri ̣ NULL bí hiểm này thì hãy yên tâm vì nó đơn giản chỉ là 0 (zero). Thƣ̣c sƣ̣ trong C nó đƣơ ̣c đi ̣nh nghĩa là ((void*)0) vì nó đƣợc sử dụng với các con trỏ. Vì thế cho nên bạn có thể sẽ gặp các cảnh báo khi biên dịch chƣơng trình nếu sử dụng NULL cho các giá tri ̣ kiểu nguyên , tùy thuộc vào trình biên dịch và cấp độ cảnh báo đƣợc thiết lâ ̣p vớ i trình biên di ̣ch. Bạn có thể bỏ qua các cảnh báo hoặc đơn giản là sử dụng giá trị 0 để thay thế.

Nguyên nhân số 1 mà mọi ngƣời không biết tại sao chƣơng trình của mình lại có lỗi là do ho ̣ không kiểm tra cá c giá tri ̣ trả về củ a lờ i go ̣i hàm mà ho ̣ thƣ̣c hiê ̣n trong chƣơng trình của mình. CreateWindow() sẽ trả về sai trong một số trƣờng hợp ngay cả khi bạn là một lập trình viên có nhiều kinh nghiệm , chỉ đơn giản bởi vì có rất n hiều lỗi mà chú ng ta rất dễ ta ̣o ra chúng. Cho tớ i khi ba ̣n ho ̣c đƣơ ̣c cánh nhanh chóng xác đi ̣nh các lỗi kiểu nhƣ vâ ̣y , hãy tự cho mình một cơ hội để có thể tìm ra các lỗi trong chƣơng trình , hãy luôn kiểm tra các giá trị tr ả về khi go ̣i tớ i các hàm.

if(hwnd == NULL)

{

MessageBox(NULL, "Window Creation Failed!", "Error!",

MB_ICONEXCLAMATION | MB_OK);

return 0;

}

lê ̣, chúng ta sẽ hiển thị cửa sổ lên màn hình của windows Sau khi chú ng ta đã ta ̣o ra cƣ̉ a sổ và kiểm tra để đảm bảo là chúng ta có một handle hợp (hê ̣ điều hành ) bằng cách sƣ̉ du ̣ng

11

Bài giảng môn học: Lâ ̣p trình Windows

tham số cuối cù ng củ a hàm WinMain () và sau đó cập nhật nó để đảm bảo là tự nó đƣợc vẽ lại mô ̣t cách hơ ̣p lê ̣ trên màn hình.

ShowWindow(hwnd, nCmdShow);

UpdateWindow(hwnd);

Tham số nCmdShow là tù y cho ̣n , bạn có thể đơn giản gán nó là SW _SHOWNORMAL mỗi khi chú ng ta làm viê ̣c vớ i nó .Tuy nhiên sƣ̉ du ̣ng tham số đƣơ ̣c truyền vào h àm WinMain() sẽ tạo cơ hội cho các ngƣời dùng sử dụng chƣơng trình của chúng ta có thể chỉ định họ muốn hoă ̣c không muốn củ a sổ củ a chú ng ta bắt đầu vớ i kích thƣớ c cƣ̉ a sổ lớ n nhất hoă ̣c nhỏ nhất … Ba ̣n sẽ thấy các tù y chọn này trong các tham số của các shortcut của tới các cửa sổ .

Bƣớ c 3: Vòng lặp xử lý thông điệp

Đây chính là trái tim củ a toàn bô ̣ chƣơng trình , hầu hết nhƣ̃ng thƣ́ mà chƣơng trình củ a chúng ta xử lý là nằm ở đây.

while(GetMessage(&Msg, NULL, 0, 0) > 0)

{

TranslateMessage(&Msg);

DispatchMessage(&Msg);

}

return Msg.wParam;

. Hàm GetMessage () lấy mô ̣t thông điê ̣p tƣ̀ hàng đơ ̣i thông điê ̣p củ a ƣ́ ng du ̣ng củ a ba ̣n Bất kỳ thờ i điểm nào ngƣờ i dù ng di chuyển con chuô ̣t , gõ bàn phím , click chuô ̣t trên menu của cửa sổ chƣơng trình, hoă ̣c làm bất cƣ́ mô ̣t thao tác nào đa ̣i loa ̣i nhƣ vâ ̣y , các thông điệp sẽ đƣơ ̣c sinh ra và thêm chú ng vào hàng đơ ̣i thông điê ̣p củ a chƣơng trình. Bằng cách go ̣i tớ i hàm GetMessage() bạn đang yêu cầu thông điệp tiếp theo loại bỏ khỏi hàng đợi và trả nó về cho bạn xử lý . Nếu nhƣ không có thông điê ̣p nào hàm GetMesage () sẽ chuyển sang blocks . Nếu bạn không quen với thuâ ̣t ngƣ̃, điều đó có nghĩa là nó sẽ đơ ̣i cho tớ i khi có mô ̣t thông điê ̣p tiếp theo để xƣ̉ lý (lấy về cho chƣơng trình).

TranslateMessage() thƣ̣c hiê ̣n thêm mô ̣t số xƣ̉ lý trên các sƣ̣ kiê ̣n bàn phím chẳng ha ̣n nhƣ sinh ra các thông điê ̣p WM _CHAR cù ng vớ i các thông điê ̣p WM _KEYDOWN. Cuối cùng DispatchMessage() gƣ̉ i thông điê ̣p tớ i cƣ̉ a sổ mà thông điê ̣p cần đƣơ ̣c gƣ̉ i tớ i (xƣ̉ lý theo kiểu hƣớ ng sƣ̣ kiê ̣n ). Đó có thể là cƣ̉ a sổ chính củ a chƣơng trình hoă ̣c có th ể là một cửa sổ khác, hoă ̣c là mô ̣t điều khiển , và trong một số trƣờng hợp là một cửa sổ đƣợc tạo ra ở hậu trƣờ ng bở i hê ̣ thống hoă ̣c mô ̣t chƣơng trình khác . Đây không phải là điều mà các ba ̣n cần phải lo lắng vì tất cả nhƣ̃ng gì chú ng ta bàn tớ i là lấy thông điê ̣p và gƣ̉ i nó đi , hê ̣ thống sẽ xƣ̉ lý để đảm bảo thông điê ̣p đó đƣơ ̣c gƣ̉ i đến đú ng nơi sẽ nhâ ̣n nó .

Bƣớ c 4: Thủ tục cửa sổ.

Nếu nhƣ vòng lă ̣p thông điê ̣p là trái tim củ a chƣơng

trình thì thủ tục cửa sổ là bộ não của chƣơng trình. Đây là nơi mà tất cả các thông điê ̣p đƣơ ̣c gƣ̉ i tớ i cƣ̉ a sổ củ a chú ng ta đƣơ ̣c xƣ̉ lý.

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam,

LPARAM lParam)

12

Bài giảng môn học: Lâ ̣p trình Windows

{

switch(msg)

{

case WM_CLOSE:

DestroyWindow(hwnd);

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

return DefWindowProc(hwnd, msg, wParam, lParam);

}

return 0;

}

Thủ tục cƣ̉ a sổ đƣơ ̣c go ̣i tớ i để xƣ̉ lý các thông điê ̣p , tham số HWND là để quản lý cƣ̉ a sổ củ a ba ̣n, cũng chính là đối tƣợng của thông điệp . Điều này là quan tro ̣ng bở i vì ba ̣n có thể có hai hoặc nhiều hơn nữa các cửa sổ cùng lớ p và chú ng sẽ sƣ̉ du ̣ng cù ng mô ̣t thủ tu ̣c cƣ̉ a sổ (WndProc()). Sƣ̣ khác nhau ở đây là tham số hwnd sẽ thay đổi tù y thuô ̣c vào cƣ̉ a sổ là cƣ̉ a sổ nào. Chẳng ha ̣n khi chú ng ta lấy về mô ̣t thông điê ̣p WM _CLOSE chú ng ta sẽ hủ y cƣ̉ a sổ. Và vì chúng ta sử dụng handle nhận đƣợc để quản lý cửa sổ qua tham số thứ nhất nên các cửa sổ khác sẽ không bị ảnh hƣởng, chỉ có cửa sổ nhận thông điệp là sẽ bị hủy.

WM_CLOSE đƣơ ̣c gƣ̉ i đến khi ngƣờ i dù ng nhấn chu ột vào nút đóng cửa sổ hoặc nhấn tổ hơ ̣p phím Alt +F4. Thao tác này sẽ làm cho cƣ̉ a sổ bi ̣ hủ y theo mă ̣c đi ̣nh , nhƣng tôi muốn xƣ̉ lý nó mô ̣t cách dƣ́ t khoát , vì điều này có thể liên quan tới một số thao tác khác chẳng hạn , ngắt các kết nối cơ sở dƣ̃ liê ̣u , các kết nối mạng vân vân nhƣ đóng các file dƣ̃ liê ̣u đang mở trƣớ c khi thoát khỏi chƣơng trình.

() hê ̣ thống sẽ gƣ̉ i thông điê ̣p Khi chú ng ta go ̣i tớ i hàm DestroyWindow

WM_DESTROY tớ i cƣ̉ a sổ sẽ bi ̣ hủy bỏ, trong trƣờ ng hơ ̣p này là cƣ̉ a sổ củ a chú ng ta , và sau đó hủ y tất cả các cƣ̉ a sổ con trƣớ c khi loa ̣i bỏ cƣ̉ a sổ củ a chú ng ta khỏi hê ̣ thống . Vì chỉ có mô ̣t cƣ̉ a sổ trong chƣơng trình củ a chú ng ta nên cũng không có nhiề u viê ̣c phải làm và chú ng ta muốn thoát khỏi chƣơng trình , vì thế hàm PostQuitMessage() đƣơ ̣c go ̣i tớ i. Hàm này sẽ gửi thông điê ̣p WM _QUIT tớ i vòng lă ̣p thông điê ̣p . Chúng ta không bao nhận đƣợc thông điệp này vì nó làm cho hàm GetMessage() trả về FALSE , và nhƣ bạn sẽ thấy trong đoạn mã vòng lă ̣p xƣ̉ lý thông điê ̣p củ a chú ng ta , khi điều đó xảy ra chú ng ta dƣ̀ ng viê ̣c xƣ̉ lý các thông điê ̣p và trả về giá trị cuối cùng , tham số wParam củ a thông điê ̣p WM _QUIT là giá tri ̣ đƣơ ̣c truyền qua hàm PostQuitMessage (). Giá trị trả về chỉ thực sự có ích nếu chƣơng trình của chúng ta đƣơ ̣c thiết kế để mô ̣t chƣơng trình khác go ̣i và chú ng ta muốn trả về mô ̣t giá tri ̣ cu ̣ thể .

Bƣớ c 5: không có bƣớ c 5

Đến đây là hết , chúng ta không có bƣớc 5 và tôi hy vọng các bạn đã hiểu đƣợc ít nhiều

cấu trú c cơ bản củ a mô ̣t chƣơng trình trên Windows.

13

Bài giảng môn học: Lâ ̣p trình Windows

3.3 Quản lý các thông điệp

Ví dụ: window_click.

Vâ ̣y là chú ng ta đã có mô ̣t cƣ̉ a sổ , nhƣng nó sẽ không làm bất cƣ́ điều gì ngoa ̣i trƣ̀ nhƣ̃ng gì mà hàm DefWindowProc () cho phép nó làm , chẳng ha ̣n nhƣ thay đổi kích thƣớ c , phóng to, thu nhỏ, vân vân. Quả là không thú vị tí nào.

Trong phần tiếp theo tô i sẽ hƣớ ng dẫn các ba ̣n sƣ̉ a đổi nhƣ̃ng gì chú ng ta đã viết để ta ̣o

ra mô ̣t cái gì đó mớ i mẻ.

Trƣớ c tiên cần đảm bảo là ba ̣n đã biên di ̣ch và cha ̣y chƣơng trình simple _window thành công, chúng ta sẽ copy nó sang ví dụ mới và sƣ̉ a đổi mã nguồn (code) của chƣơng trình.

Chúng ta sẽ thêm cho chƣơng trình khả năng hiển thị tên của chƣơng trình khi ngƣời dùng kích chuột vào cửa sổ của chƣơng trình . Thƣ̣c chất củ a khả năng mớ i này là chú ng ta sẽ tƣ̣ xƣ̉ lý thông điệp nhấn chuột của ngƣời dùng, tất nhiên là trong hàm WndProc(), tƣ̀ :

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam,

LPARAM lParam)

{

switch(msg)

{

case WM_CLOSE:

DestroyWindow(hwnd);

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

return DefWindowProc(hwnd, msg, wParam, lParam);

}

return 0;

}

Khi chú ng ta muốn xƣ̉ lý các thông điê ̣p nhấn chuô ̣t chú ng ta cần có t

hêm các handle : WM_LBUTTONDOWN (hoă ̣c WM _RBUTTONDOWN, WM_MBUTTONDOWN cho các thao tác nhấn chuô ̣t phải và giƣ̃a):

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam,

LPARAM lParam)

{

switch(msg)

{

case WM_LBUTTONDOWN: // <-

14

Bài giảng môn học: Lâ ̣p trình Windows

// <- we just added this stuff

break; // <-

case WM_CLOSE:

DestroyWindow(hwnd);

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

return DefWindowProc(hwnd, msg, wParam, lParam);

}

return 0;

}

Thƣ́ tƣ̣ xƣ̉ lý các thông điê ̣p là quan tro ̣ng và cần nhớ là đối vớ i các thông điê ̣p khác (ngoài WM_DESTROY và WM_QUIT) cần có thêm câu lê ̣nh break sau khi xƣ̉ lý xong thông điê ̣p.

Trƣớ c tiên tôi sẽ trình bày đoa ̣n mã lê ̣nh mà chú ng ta sẽ thêm vào

(hiển thi ̣ tên củ a chƣơng trình củ a chú ng ta ) và sau đó tôi sẽ tích hợp đoạn mã đó vào chƣơng trình của chúng ta. Trong các phần sau củ a chƣơng trình t ôi sẽ chỉ cho các ba ̣n đoa ̣n mã và để các ba ̣n tƣ̣ tích hơ ̣p đoa ̣n mã đó vào các chƣơng trình . Điều này vƣ̀ a tốt cho tôi : tôi sẽ không phải gõ đi gõ la ̣i các đoạn mã lệnh và vừa tốt cho các bạn : các bạn có cơ hội thực hàn h nhƣ̃ng hiểu biết củ a mình để nâng cao kỹ năng thực hành . Còn nếu nhƣ bạn không chắc chắn hãy tra trong mã nguồn chƣơng trình đi kèm vớ i tài liê ̣u này.

GetModuleFileName(hInstance, szFileName, MAX_PATH);

szFileName, "This program is:", MB_OK |

MessageBox(hwnd, MB_ICONINFORMATION);

Hàm WndProc() của chúng ta bây giờ sẽ nhƣ sau:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam,

LPARAM lParam)

{

switch(msg)

{

case WM_LBUTTONDOWN:

// BEGIN NEW CODE

{

char szFileName[MAX_PATH];

HINSTANCE hInstance = GetModuleHandle(NULL);

15

Bài giảng môn học: Lâ ̣p trình Windows

GetModuleFileName(hInstance, szFileName, MAX_PATH);

MessageBox(hwnd, szFileName, "This program is:", MB_OK |

MB_ICONINFORMATION);

}

// END NEW CODE

break;

case WM_CLOSE:

DestroyWindow(hwnd);

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

return DefWindowProc(hwnd, msg, wParam, lParam);

}

return 0;

}

Hãy chú ý tới cặp dấu { và } mớ i. Các cặp dấu này là bắt buộc khi chúng ta khai báo các biến trong câu lê ̣nh switch. Bƣớc tiếp theo tất nhiên là dịch chƣơng trình , chạy thử và xem kết quả của chƣơng trình.

Chúng ta có thể chú ý là ở đây tôi đã sử dụng hai biến khi gọi hàm

GetModuleFileName(), tham số thƣ́ nhất là mô ̣t handle tham chiếu tớ i mô ̣ t chƣơng trình đang chạy, nhƣng chú ng ta có thể lấy handle đó ở đâu ra ? Ở đây một lân nữa chúng ta lại sử dụng mô ̣t hàm API khác GetModuleHandle(), thâ ̣t may mắn là đối vớ i hàm này nếu chú ng ta truyền tham số là NULL vào thì kết quả trả về sẽ là handle trỏ tớ i file đƣơ ̣c sƣ̉ du ̣ng để ta ̣o ra tiến trình đã gọi hàm, đó chính xác là cái mà chú ng ta cần. Và do đó chúng ta có câu lệnh:

HINSTANCE hInstance = GetModuleHandle(NULL);

Tham số thƣ́ hai khi go ̣ i hàm GetModuleFileName () là một con trỏ xâu để chứa đƣờng , kiểu củ a nó là

dẫn (kết quả củ a hàm ) tớ i chƣơng trình có handle là tham số thƣ́ nhất LPTRSTR (hoă ̣c LPSTR) và do LPSTR hoàn toàn tƣơng tự nhƣ char * nên chú ng ta khao báo mô ̣t xâu nhƣ sau:

char szFileName[MAX_PATH];

MAX_PATH là mô ̣t macro thuô ̣c windows .h đi ̣nh nghĩa đô ̣ dài tối đa củ a mô ̣t xâu để chƣ́ a đô ̣ dài đƣờ ng dẫn tớ i mô ̣t file trên windows.

16

Bài giảng môn học: Lâ ̣p trình Windows

3.4 Vòng lặp xử lý thông điệp

Nhƣ̃ng kiến thƣ́ c về vòng lặp xử lý thông điệp và cấu trúc của việc gửi một thông điệp

là cần thiết để viết bất cứ ứng dụng nào. Cho đến thờ i điểm hiê ̣n ta ̣i chú ng ta mớ i xem xét mô ̣t chút về quá trình xử lý thông điệp , trong phần này tôi sẽ trình bày với các bạn kỹ càng hơn về cả quá trình xử lý thông điệp.

Thế nào là mô ̣t thông điê ̣p?

Mô ̣t thông điê ̣p (message) là một giá trị nguyên (số). Nếu ba ̣n tra trong các file header

(đây là mô ̣t thói quen tốt khi làm việc với các hàm API) bạn sẽ thấy các dòng khai báo sau:

#define WM_INITDIALOG 0x0110

#define WM_COMMAND 0x0111

#define WM_LBUTTONDOWN 0x0201

vân vân. Các thông điệp đƣợc sử dụng để tru yền thông hầu nhƣ mo ̣i thƣ́ trên hê ̣ điều

hành windows ít nhất là tại các cấp độ cơ bản . Nếu ba ̣n muốn mô ̣t cƣ̉ a sổ hoă ̣c mô ̣t điều khiển (là một dạng cửa sổ đặc biệt ) thƣ̣c hiê ̣n mô ̣t công viê ̣c nào đó , bạn sẽ phải gửi cho nó mô ̣t thông điê ̣p. Nếu mô ̣t cƣ̉ a sổ khác muốn ba ̣n làm điều gì đó nó sẽ gƣ̉ i tớ i cho ba ̣n mô ̣t thông điê ̣p. Nếu mô ̣t sƣ̣ kiê ̣n xảy ra chẳng ha ̣n nhƣ ngƣờ i dù ng gõ bàn phím , di chuyển chuô ̣t , nhấn chuô ̣t lên mô ̣t button , thì các thôn g điê ̣p sẽ đƣơ ̣c hê ̣ thống (hê ̣ điều hành windows ) gƣ̉ i đến cho các cƣ̉ a sổ chi ̣u tác đô ̣ng củ a sƣ̣ kiê ̣n đó . Nếu ba ̣n là mô ̣t trong các cƣ̉ a sổ nhƣ thế , bạn sẽ tiếp nhâ ̣n và xƣ̉ lý thông điê ̣p, có các hành vi thích hợp.

Mỗi mô ̣t thông điê ̣p có thể có nhiều nhất là hai tham số

, wParam và lParam . Nguyên bản wParam là 16 bit và lParam là 32 bit, nhƣng trên các hê ̣ thống Win 32 chúng đều là 32 bit. Không phải tất các thông điê ̣p đều sƣ̉ du ̣ng hai tham số này , và mỗi thông điê ̣p sƣ̉ du ̣ng chú ng theo các cách khác nhau . Chẳng ha ̣n thông điê ̣p WM _CLOSE không sƣ̉ du ̣ng cả hai tham số trên, và bạn có thể bỏ qua chúng . Thông điê ̣p WM_COMMAND sƣ̉ du ̣ng cả hai tham số trên , (nếu thích hơ ̣p ) và wParam chƣ́ a hai giá tri ̣ , HIWORD(wParam) là thông điệp báo hiệu . lParam là HWND LOWORD(wParam) là định danh điều khiển hoặc menu gửi thông điệp (window handle ) của điều khiển gửi thông điệp hoặc NULL nếu nhƣ các thông điệp không phải đƣợc gƣ̉ i đi tƣ̀ mô ̣t điều khiển nào đó .

HIWORD() và LOWORD là các macro đƣợc định nghĩa bởi windows lấy ra 2 byte cao (High Word) của một giá trị 4 byte = 32 bit (0xFFFF0000) và hai byte thấp (0x0000FFFF) tƣơng ƣ́ ng. Trên các hê ̣ thống Win 32 mô ̣t WORD là mô ̣t giá tri ̣ 16 bit còn DWORD (Double WORD) là một giá trị 32 bit.

Để gƣ̉ i mô ̣t thông điê ̣p ba ̣n có thể sƣ̉ du ̣ng hàm PostMessage

WM_CLOSE chẳng ha ̣n nhƣ

. Chú ý rằng wParam và () hoă ̣c SendMessage (). PostMessage() đă ̣t (put) thông điê ̣p vào hàng đơ ̣i thông điê ̣p và trả về ngay lâ ̣p tƣ́ c. Điều đó có nghĩa là mỗi khi chúng ta gọi tới hàm PostMessage () nó sẽ gửi thông điệp đi ngay nhƣng việc thông điê ̣p đó có thƣ̣c hiê ̣n ngay hay không hoă ̣c thâ ̣m chí có thƣ̣c hiê ̣n hay không thì còn chƣa chắc chắn. Hàm SendMessage() gƣ̉ i thông điê ̣p trƣ̣c tiếp tớ i cho cƣ̉ a sổ nhâ ̣n thông điê ̣p và sẽ không kết thúc cho tới khi thông điệp đó đƣợc xử lý xong. Nếu chú ng ta muốn đóng mô ̣t cƣ̉ a sổ chú ng ta có thể gƣ̉ i tớ i cƣ̉ a sổ đó mô ̣t thông điê ̣p PostMessage(hwnd, WM_CLOSE, 0, 0); điều này có tác đô ̣ng tƣơng tƣ̣ nhƣ viê ̣c chú ng ta nhấn chuô ̣t lên biểu tƣơ ̣ng trên góc trên bên phải củ a cƣ̉ a sổ

17

Bài giảng môn học: Lâ ̣p trình Windows

lParam đều bằng 0 trong trƣờ ng hơ ̣p này . Điều này là do nhƣ chú ng ta đã nói trƣớ c chú ng không có ý nghĩa gì đối vớ i thông điê ̣p WM_CLOSE.

Các hộp thoại (Dialog).

Khi ba ̣n đã bắt đầu sƣ̉ du ̣ng các hô ̣p thoa ̣i

. SendDlgItemMessage() và một vài

, bạn sẽ cần gửi các thông điệp tới các điều khiển để có thể truyền thông vớ i chú ng . Bạn có thể làm điều này bằng cách trƣớc hết lấy handle quản lý điều khiển bằng hàm GetDlgItem () và sau đó sử dụng hàm SendMessage () hoă ̣c có thể sƣ̉ du ̣ng hàm SendDlgItemMessage () (hàm này kết h ợp công việc của cả hai hàm trên). Bạn sẽ cung cấp cho hàm một handle của một cửa sổ và một ID con và hàm sẽ lấy handle con củ a hô ̣p thoa ̣i và gƣ̉ i thông điê ̣p tớ i cho nó hàm API tƣơng tự khác chẳng ha ̣n nhƣ GetDlgItemText () sẽ làm việc trên tất cả các cửa sổ chƣ́ không chỉ vớ i các hô ̣p thoa ̣i.

Thế nào là hàng đơ ̣i thông điê ̣p?

Giả sử bạn đang bận túi bụi với việc xử lý thông điệp WM _PAINT và đô ̣t nhiên ngƣờ i dùng thƣ̣c hiê ̣n hàng loa ̣t thao tác trên bàn phím củ a máy tính . Điều gì sẽ xảy ra ? Bạn sẽ bị ngăt viê ̣c đang vẽ để xƣ̉ lý các thao tác bàn phím củ a ngƣờ i dù ng hoă ̣c các thao tác đó sẽ bi ̣ bỏ qua? Tất cả các cách giải quyết nhƣ vâ ̣y đều có vấn đề , giải pháp ở đây là sử dụng một hàng đơ ̣i để lƣu các thông điêp cần xƣ̉ lý , khi các thông điê ̣p đƣơ ̣c gƣ̉ i đến chú ng sẽ đƣơ ̣c thêm vào i hàng đơ ̣i . Và để đảm hàng đợi và khi các thông điệp đƣợc xử lý chúng sẽ đƣợc loại bỏ khỏ , các bảo các thông điệp không bị bỏ qua nếu nhƣ bạn đang bận xử lý một thông điệp nào đó thông điê ̣p khác sẽ đƣơ ̣c chờ trong hàng đơ ̣i cho tớ i khi tớ i lƣơ ̣t chú ng đƣơ ̣c xƣ̉ lý .

Thế nào là mô ̣t vòng lă ̣p thông điê ̣p (vòng lặp xử lý thông điệp – Message Loop)

while(GetMessage(&Msg, NULL, 0, 0) > 0)

{

TranslateMessage(&Msg);

DispatchMessage(&Msg);

}

1. Vòng lặp thông điệp gọi tới hàm GetMessage (), hàm này sẽ kiểm tra hàng đợi thông điê ̣p củ a ba ̣n. Nếu nhƣ hàng đơ ̣i thông điê ̣p là rỗng chƣơng trình củ a ba ̣n về cơ bản sẽ dừng và đợi cho tới khi có một thông điệp mới (trạng thái Block).

2. Khi mô ̣t sƣ̣ kiê ̣n xảy ra làm cho mô ̣t thông điê ̣p đƣơ ̣c thêm vào hàng đơ ̣i

(chẳng ha ̣n nhƣ hê ̣ thống ghi nhâ ̣n mô ̣t sƣ̣ kiê ̣n nhấn chuô ̣t ) hàm GetMessage () sẽ trả về mô ̣t giá tri ̣ nguyên dƣơng chỉ ra rằng có mô ̣t thông điê ̣p cần xƣ̉ lý , và các thông tin về thông điê ̣p đó sẽ đƣơ ̣c điền vào cấu trú c MSG truyền cho hàm. Hàm sẽ trả về 0 nếu nhƣ thông điê ̣p là WM_QUIT và là mô ̣t giá tri ̣ âm nếu nhƣ có lỗi xảy ra.

3. Chúng ta nhận đƣợc thông điệp (qua biến Msg ) và truyền cho hàm

. Bƣớ c này thƣ̣c sƣ̣ không bắt buô ̣c nhƣng mô ̣t số thông

TranslateMessage(), hàm này thực hiện xử lý thêm một chút , dịch các thông tin của thông điê ̣p thành các thông điê ̣p ký tƣ̣ điê ̣p sẽ không làm viê ̣c đƣơ ̣c nếu không có bƣớ c này.

4. Sau đó chú ng ta chuyển thông điê ̣p cho hàm DispatchMessage (). Hàm này sẽ

nhâ ̣n thông điê ̣p kiểm tra cƣ̉ a sổ đích củ a thông điê ̣p và tìm hàm xƣ̉ lý thông điê ̣p

18

Bài giảng môn học: Lâ ̣p trình Windows

(Window Procedure) của cửa sổ đó. Sau đó nó sẽ go ̣i tớ i hàm xƣ̉ lý thông điê ̣p củ a cƣ̉ a sổ , gƣ̉ i các tham số : handle củ a cƣ̉ a sổ , thông điê ̣p và các tham số wParam, lParam.

6. 5. Trong hàm xƣ̉ lý thông điê ̣p ba ̣n sẽ kiểm tra thông điê ̣p và các tham số củ a nó và làm bất cứ điều gì mà bạn thích với chúng . Nếu ba ̣n không muốn xƣ̉ lý các thông điê ̣p mô ̣t cách chi tiết cụ thể, bạn hầu nhƣ chỉ việc gọi tới hàm DefWindowProc (), hàm này sẽ thƣ̣c hiê ̣n các hành đô ̣ng mă ̣c đi ̣nh cho ba ̣n (điều này thƣờ ng có nghĩa là chẳng làm gì cả ). Sau khi ba ̣n đã kết thú c viê ̣c xƣ̉ lý thông điê ̣p, hàm xử lý thông điê ̣p củ a ba ̣n trả về, hàm DispatchMessage() trả về và chúng ta qua trở lại đầu vòng lặp.

Đây là mô ̣t khái niê ̣m cƣ̣c kỳ quan tro ̣ng củ a các chƣơng trình trên Windows

. Thủ tục xƣ̉ lý thông điê ̣p củ a ba ̣n không đƣơ ̣c go ̣i mô ̣t c ách thần bí bởi hệ thống , mà chính bạn đã gọi tớ i chú ng mô ̣t cách gián tiếp thông qua viê ̣c go ̣i tớ i hàm DitpatchMessage (). Nếu nhƣ ba ̣n muốn, bạn có thể sử dụng hàm GetWindowLong () trên handle củ a cƣ̉ a sổ đích củ a thông điê ̣p để tìm ra thủ tục xử lý cửa sổ của nó và gọi tới hàm đó một cách trực tiếp:

while(GetMessage(&Msg, NULL, 0, 0) > 0)

{

fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd,

WNDPROC GWL_WNDPROC);

fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);

}

Tôi đã thƣ̉ cách đó vớ i đoa ̣n mã chƣơng trình trƣớ c củ a chú ng ta và nó hoa ̣t đô ̣ng tốt

, tuy nhiên có rất nhiều vấn đề chẳng ha ̣n nhƣ các chuyển đổi Unicode /ANSI, các lời gọi tới các điều khiển thời gian vân vân m à hàm này không phù hợp , và khả năng rất cao là nó sẽ break vớ i hầu hết các chƣơng trình trƣ̀ các chƣơng trình đơn giản . Vì thế không nên thử dùng hàm này, trƣ̀ khi ba ̣n chỉ muốn thƣ̉ nó .

Chú ý là chúng ta sử dụng hàm Get WindowLong() để lấy thủ tục xử lý cửa sổ của cửa sổ . Tại sao chúng ta không đơn giản là gọi tới hàm WndProc () mô ̣t cách trƣ̣c tiếp ? Vòng lặp g chƣơng các thông điệp của chúng ta chịu trách nhiệm đáp ứng cho tất cả các cửa sổ tron trình của chúng ta , điều này bao gồ m cả các thƣ́ chẳng ha ̣n nhƣ các nú t (button) và các hộp danh sách vớ i các hàm xƣ̉ lý thông điê ̣p củ a chú ng , vì thế chúng ta cần đảm bảo là chúng ta gọi đến đúng hàm xử lý cửa sổ củ a các thành phần đó (đây thƣ̣c sƣ̣ là mô ̣t ví du ̣ củ a khái niê ̣m đa thể trong lâ ̣p trình hƣớ ng đối tƣơ ̣ng ). Vì các cửa sổ có thể sử dụng chung một hàm xử lý thông điê ̣p nên tham số đầu tiên (handle củ a cƣ̉ a sổ ) đƣơ ̣c dù ng để chỉ cho hàm xƣ̉ lý thông điê ̣p biết cƣ̉ a sổ nào là dành cho thông điê ̣p nào.

Nhƣ ba ̣n có thể thấy ƣ́ ng du ̣ng củ a ba ̣n dành phần lớ n thờ i gian củ a nó cho vòng lă ̣p xƣ̉

. Đó chính xác là nhƣ̃ng gì mà hàm

, hoă ̣c trả về qua hàm lý thông điệp , nơi mà ba ̣n có thể hân hoan gƣ̉ i các thông điê ̣p tớ i các cƣ̉ a sổ sẽ xƣ̉ lý chú ng . Nhƣng ba ̣n cần làm gì khi muốn thoát khỏi chƣơng trình ? Vì chúng ta sử dụng một vòng lặp while(), nếu nhƣ hàm GetMessage () trả về FALSE , vòng lặp sẽ thoát và chúng ta sẽ tới cuối hàm WinMain() và thoát khỏi chƣơng trình PostQuitMessage() đã làm. Nó đặt một thông điệp WM _QUIT vào hàng đơ ̣i thônmg điê ̣p và thay vì trả về mô ̣t giá tri ̣ dƣơng, hàm GetMesage() điền đầy đủ các thông tin cho cấu trú c Msg và trả về 0. Tại thời điểm này thành viên wParam của cấu trúc Msg chứa giá trị mà bạn đã truyền cho hàm PostQuitMessage () và bạn cũng có thể bỏ qua nó WinMain(), giá trị đó sẽ đƣợc dùng nhƣ là mã thoát chƣơng trình khi tiến trình kết thúc.

19

Bài giảng môn học: Lâ ̣p trình Windows

Chú ý: GetMessage() sẽ trả về -1 nếu nhƣ gă ̣p mô ̣t lỗi . Bạn cần nhớ kỹ điều này nếu không có thể chƣơng trình sẽ gă ̣p tru ̣c tră ̣c , mă ̣c dù hàm GetMessage() có kiểu BOOL song nó vẫn có thể trả về mô ̣t giá tri ̣ không phải là TRUE hay FALSE vì BOOL có nghĩa là UINT (unsigned int). Các ví dụ sau có vẻ sẽ làm việc tốt:

while(GetMessage(&Msg, NULL, 0, 0))

while(GetMessage(&Msg, NULL, 0, 0) != 0)

while(GetMessage(&Msg, NULL, 0, 0) == TRUE)

Thƣ̣c tế tất cả các trƣờ ng hơ ̣p đó đều sai. Bạn có thể chú ý là tôi vẫn sử dụng trƣờng hợp 1 cho tất cả các ví du ̣ cho tớ i thờ i điểm này , và nhƣ tôi đã đề cập nó vẫn làm việc tốt khi mà hàm GetMessage () không bao giờ trả về FALSE . Tuy nhiên không thể đảm bảo là hàm GetMessage() không gă ̣p lỗi trong tất cả các trƣờ ng hơ ̣p và ba ̣n nên sƣ̉ du ̣ng cách viết nhƣ sau:

while(GetMessage(&Msg, NULL, 0, 0) > 0)

Bài tập:

Bài tập 1: Viết chƣơng trình mô phỏng chƣơng trình máy tính với các chức năng tính toán đơn giản trên Windows.

Bài tập 2: Viết chƣơng trình với hệ thống menu cho phép thay đổi màu nền

20

Bài giảng môn học: Lâ ̣p trình Windows

Chƣơng 2: Hê ̣ thố ng file và thƣ mục

1. Truy câ ̣p và sƣ̉ du ̣ng hê ̣ thố ng file trên môi trƣờ ng Windows

Hầu hết các ƣ́ ng du ̣ng ngày nay đều cho phép ngƣờ i dù ng lƣ̣a cho ̣n ghi la ̣i nhƣ̃ng gì chƣơng trình đã ta ̣o ra . Nhƣ̃ng thƣ́ cần ghi la ̣i có thể là các tài liê ̣u , dƣ̃ liê ̣u chƣơng trình hoă ̣c mô ̣t thiết lâ ̣p nào đó đối vớ i chƣơng trình . Trong bài thƣ̣c hành này chú ng ta sẽ khám phá các hỗ trơ ̣ củ a Visual C ++ cho viê ̣c cài đă ̣t các chƣ́ c năng này mô ̣t cách dễ dàng . Cụ thể chúng ta sẽ học:

 Cách Visual C++ sƣ̉ dung các luồng C++ để ghi lại dữ liệu trong chƣơng trình

 Cách thức sử dụng file dạng nhị phân

 Cách làm cho các đối tƣợng chƣơng trình có khả năng serialize

 Cách thức lƣu các biến có kiểu khác nhau vào cùng một file

2. Các ví dụ về thao tác với file

2.1 Serialization

Có hai phần của serialization . Khi dƣ̃ liê ̣u ƣ́ ng du ̣ng đƣơ ̣c lƣu trên đĩa hê ̣ thống dƣớ i dạng một file thì đó gọi là serialization . Khi tra ̣ng thái củ a ƣ́ ng du ̣ng đƣơ ̣c phu ̣c hồi tƣ̀ file đó đƣơ ̣c go ̣i là deserialization . Sƣ̣ kết hơ ̣p củ a hai phần này làm nên cái go ̣i là serialization củ a các đối tƣơng ứng dụng trong Visual C++.

2.1.1 Các lớp CArchive và CFile

Serialization trong các ƣ́ ng du ̣ng củ a Visual C ++ đƣơ ̣c thƣ̣c hiê ̣n qua lớ p CArchive. Lớ p

CArchive đƣơ ̣c thiết kế để làm viê ̣c vớ i các luồng vào ra cho mô ̣t đối tƣơ ̣ng CFile nhƣ minh họa trong hình vẽ sau:

Lớ p này sƣ̉ du ̣ng các luồng củ a C ++ để cho phép các luồng dữ liệu đƣợc nạp từ hoặc . Lớ p CArchive không thể tồn ta ̣i nếu không có mô ̣t

lƣu la ̣i thành các file mô ̣t cách hiê ̣u quả đối tƣơ ̣ng CFile gắn vớ i nó .

Lớ p CArchive có thể chƣ́ a dƣ̃ liê ̣u trong rất nhiều kiểu file khác nhau , tất cả chú ng đều

là hâ ̣u duê ̣ củ a lớ p CFile . Theo mă ̣c đi ̣nh App Wizard sẽ bao gói tất cả các chƣ́ c năng để ta ̣o và mở các đối tƣợng CFile thông thƣờng để sử dụng với CArchive . Nếu chú ng ta muốn hoă ̣c

21

Bài giảng môn học: Lâ ̣p trình Windows

cần thiết phải làm viê ̣c vớ i mô ̣t trong các lo ại file này chúng ta cần phải viết thêm các đoạn mã chƣơng trình cần thiết để có thể làm việc với các loại file khác nhau.

2.1.2 Hàm Serialize

Lớ p CArchive đƣơ ̣c sƣ̉ du ̣ng trong hàm Serialize trên các đối tƣơ ̣ng document và dƣ̃ liê ̣u trong các ƣ́ ng du ̣ng Visual C ++. Khi mô ̣t ƣ́ ng du ̣ng đo ̣c hoă ̣c ghi lên mô ̣t file hàm Serialize củ a đối tƣơ ̣ng document sẽ đƣơ ̣c go ̣i đến , truyền đối tƣơ ̣ng CArchive đƣơ ̣c sƣ̉ du ̣ng để ghi hoặc đọc từ file. Trong hàm Serialize thƣ́ tƣ̣ logic đƣơ ̣c tuân theo để xác đi ̣nh xem đó là thao tác đo ̣c hay ghi bằng cách go ̣i tớ i các hàm IsStoring hoă ̣c IsLoading . Giá trị trả về từ hai hàm trên sẽ xác định xem ứng dụng cần ghi hay đọc từ luồng vào ra của lớp CArchive . Mô ̣t hàm Serialize điển hình sẽ có cấu trúc nhƣ sau:

void CAppDoc::Serialize(CArchive& ar)

{

// Is the archive being written to?

if (ar.IsStoring())

{

// Yes, write my variable

ar << m_MyVar;

}

else

{

// No, read my variable

ar >> m_MyVar;

}

}

Chúng ta có thể đặt một hàm Serialize trong bất cứ lớp nào mà chúng ta tạo ra để sau đó gọ đến hàm Serialize từ hàm Serialize của lớp document . Nếu chú ng ta đă ̣t các đối tƣơ ̣ng do tƣơ ̣ng chẳng ha ̣n nhƣ CObArray nhƣ trong ƣ́ ng du ̣ng chúng ta tạo ra vào một mảng các đối trƣớ c đây 3 ngày chúng ta có thể gọi tới hàm Serialize của mảng từ hàm Serialize của lớp document. Đối tƣợng mảng sẽ , khi tớ i lƣơ ̣t nó , gọi tới hàm Serialize của các đối tƣợng đƣơ ̣c chƣ́ a bên trong nó .

2.1.3 Các đối tƣợng có thể Serialize

Khi chú ng ta ta ̣o ra lớ p CLine trong bài thƣ̣c hành số 10 chúng ta cần phải thêm 2 macro trƣớ c khi chú ng ta có thể ghi la ̣i hoă ̣c na ̣p dƣ̃ liê ̣u tƣ̀ các file . Hai macro này , DECLARE_SERIAL và IMPLEMENT _SERIAL sẽ bao gói chƣ́ c năng cần thiết trong các lớ p của chúng ta để hàm Serialize có thể hoạt động đúng đắn.

Macro thƣ́ nhất DECLARE _SERIAL đƣơ ̣c đă ̣t ngay dòng lê ̣nh đầu tiên trong khai báo lớ p (khái niê ̣m này giống nhƣ khái niê ̣m giao diê ̣n củ a Java ), nó nhận một tham số là tên của lớ p, tác dụng của nó là sẽ thêm vào lớp của chúng ta các khai báo toán tử và hàm cần thiết để chƣ́ c năng serialization hoa ̣t đô ̣ng đú ng đắn.

22

Bài giảng môn học: Lâ ̣p trình Windows

Ví dụ nhƣ sau:

class CMyClass : public CObject

{

DECLARE_SERIAL (CMyClass)

public:

virtual void Serialize(CArchive &ar);

CMyClass();

virtual ~CMyClass();

};

Macro thƣ́ hai IMPLEMENTATION _SERIAL đƣơ ̣c đă ̣t trong phần cài đă ̣t củ a lớ p do chúng ta ta ̣o ra . Macro này cần phải đă ̣t bên ngoài bất cƣ́ các hàm thành viên nào củ a lớ p vì nó sẽ thêm mã của các hàm cần thiết cho lớp tƣơng ứng với khai báo của macro DECLARE_SERIAL.

Macro này nhâ ̣n 3 tham số . Tham số thƣ́ nhất là tên lớp , giống nhƣ macro thƣ́ nhất . Tham số thƣ́ hai là tên củ a lớ p cơ sở (lớ p cha). Tham số thƣ́ ba là mô ̣t số version có thể đƣơ ̣c sƣ̉ du ̣ng để xác đi ̣nh mô ̣t file có là đú ng version để thƣ̣c hiê ̣n thao tác đo ̣c vớ i ƣ́ ng du ̣ng của chúng ta hay không . Số version này phải là mô ̣t số dƣơng nên đƣơ ̣c tăng lên mỗi lần phƣơng thƣ́ c serializeation củ a lớ p đƣơ ̣c thay đổi thay bất cƣ́ cách thƣ́ c nào làm thay đổi dƣ̃ liê ̣u đƣơ ̣c ghi hoă ̣c đo ̣c tƣ̀ file. Ví dụ về khai báo của macro thứ hai này nhƣ sau:

// MyClass.cpp: implementation of the CMyClass class.

#include "stdafx.h"

#include "MyClass.h"

#ifdef _DEBUG

#undef THIS_FILE

static char THIS_FILE[]=__FILE__;

#define new DEBUG_NEW

#endif

IMPLEMENT_SERIAL (CMyClass, CObject, 1)

//////////////////////////////////////////////////////////////////////

// Construction/Destruction

//////////////////////////////////////////////////////////////////////

23

Bài giảng môn học: Lâ ̣p trình Windows

CMyClass::CMyClass()

{

}

CMyClass::~CMyClass()

{

}

2.1.4 Cài đă ̣t hàm Serialize

Cùng với hai macro chúng ta cần cài đặt hàm Serialize trong lớp của chúng ta . Hàm này nên đƣơ ̣c khai báo là hàm kiểu void vớ i mô ̣t tham số (CArchive &ar), kiểu public và là hàm virtual. Khi cài đă ̣t hàm này ch úng ta thƣờng sử dụng cách tiếp cận giống nhƣ cách tƣơng tự đƣơ ̣c dù ng trong lớ p document ở trên , có nghĩa là cần phải kiểm tra đó là thao tác ghi hay đọc trƣớ c khi thƣ̣c hiê ̣n thao tác.

2.2 Cài đặt một lớp Serializable

Khi chú ng ta bắt đầu thiết kế mô ̣t ƣ́ ng du ̣ng mớ i

. Cách tổ chức dữ liệu nhƣ thế nào nế u

, mô ̣t trong nhƣ̃ng điều đầu tiên mà chúng ta cần thiết kế là cách thức chứa dữ liệu trong lớp document , dƣ̃ liê ̣u mà chƣơng trình sẽ tạo ra và thao tác với . Nếu chú ng ta ta ̣o ra mô ̣t ƣ́ ng du ̣ng da ̣ng data -oriented chƣ́ a mô ̣t tâ ̣p dƣ̃ liê ̣u tƣ̀ ngƣờ i dù ng chẳng ha ̣n nhƣ mô ̣t ƣ́ ng du ̣ng cở sở dƣ̃ liê ̣u chẳng ha ̣n làm thế nào để chƣ́ a các dƣ̃ liê ̣u đó trong bô ̣ nhớ củ a chƣơng trình chúng ta làm việc với một ứng dụng soạn thảo văn bản hoặc ứng dụng xử lý bảng tính ?

Khi chú ng ta đã xác đi ̣nh đƣơ ̣c cách thiết kế các cấu trú c dƣ̃ liê ̣u mà chƣơng trình sẽ

t nhất để thƣ̣c hiê ̣n chƣ́ c năng serialize cho dùng chúng ta có thể quyết định đƣợc cách tố chƣơng trình và các lớ p trong chƣơng trình . Nếu chú ng ta quyết đi ̣nh lƣu trƣ̣c tiếp tất cả dƣ̃ u và liê ̣u củ a chƣơng trình trong lớ p document thì tất cả nhƣ̃ng gì chú ng ta cần là ghi dƣ̃ liê ̣ đo ̣c dƣ̃ liê ̣u vớ i đối tƣơ ̣ng CArchive trong hàm Serialize củ a lớ p document . Nếu chú ng ta ta ̣o ra lớ p riêng để lƣu các dƣ̃ liê ̣u củ a chƣơng trình (nhƣ chú ng ta đã làm ở bài thƣ̣c hành số 10) chúng ta cần phải thêm chức năng se rializable cho các lớ p này để chú ng có thể tƣ̣ ghi và đo ̣c tƣ̀ file.

Trong ƣ́ ng du ̣ng mà chú ng ta sẽ xây dƣ̣ng ở bài thƣ̣c hành này chú ng ta sẽ viết mô ̣t chƣơng trình cơ sở dƣ̃ liê ̣u da ̣ng đơn giản , flat-file để minh ho ̣a cách thƣ́ c kết hơ ̣p các kiểu dƣ̃ liê ̣u khác nhau trong mô ̣t luồng dƣ̃ liê ̣u trong ƣ́ ng du ̣ng serialization . Ứng dụng của chúng ta sẽ hiển thị một số các trƣờng dữ liệu có kiểu khác nhau , ghi và đo ̣c dƣ̃ liê ̣u tƣ̀ mô ̣t luồng dƣ̃ liê ̣u duy nhất vớ i đối tƣơ ̣ng CArchive.

2.2.1 Thiết kế giao diê ̣n chƣơng trình

Chúng ta có thể tạo ra các lớp của riêng mình , các lớp này có thể serialized , có thể sử dụng với các ứng dụng SDI hoặc MDI. Nói ngắn gọn bất cứ ứng dụng nào làm việc với bất cứ kiểu dƣ̃ liê ̣u nào, dù đó là một cơ sở dữ liệu hoặc một tài liệu đều có thể serialized.

Chúng ta tiến hành các bƣớc sau:

Tạo một project dạng SDI có tên là Serialize 1.

Trong phần Document Template String gõ phần File Extension là fdb 2.

24

Bài giảng môn học: Lâ ̣p trình Windows

3. Phần Advanced features bỏ dấu check ActiveX control

Phần Generated Classes cho ̣n lớ p cơ sở củ a lớ p CSerialzeView là 4. CFormView

5. Nhấn Finish , chúng ta sẽ nhận đƣợc thông báo là chƣơng trình sẽ

không có chƣ́ c năng in ấn, nhấn Yes để tiếp tu ̣c

Sau khi ta ̣o mô ̣t ƣ́ ng du ̣ng SDI hoă ̣c MDI trong đó lớ p view kế thƣ̀ a tƣ̀ lớ p CFormView chúng ta cần thiết kế form view của chƣơng trình trình , quá trình này cũng giống nhƣ thiết kế các hộp thoại trong các ƣ́ ng du ̣ng da ̣ng Dialog based nhƣng chú ng ta không cần có các nú t để thoát khỏi chƣơng trình hoặc hủy bỏ quá trình thực hiện nhƣ trên các hộp thoại thông thƣờng . a sổ đƣơ ̣c đă ̣t trên các hê ̣ thống Vớ i mô ̣t ƣ́ ng du ̣ng SDI hoă ̣c MDI chƣ́ c năng ghi và thoát cƣ̉ menu chƣơng trình hoă ̣c trên các thanh công cu ̣.

Chú ý : Nếu chú ng ta làm viê ̣c vớ i các ƣ́ ng du ̣ng da ̣ng Dialog based App Wizard sẽ không cung cấp các đoa ̣n mã serialization cho ƣ́ ng du ̣ng chú ng ta cần p hải tự thực hiện điều này nếu muốn.

Thiết kế form củ a chƣơng trình gồm các điều khiển nhƣ bảng sau:

Object Property Setting

Static Text ID IDC_STATIC

Caption &Name:

Edit Box ID IDC_ENAME

Static Text ID IDC_STATIC

Caption &Age

Edit Box ID IDC_EAGE

Static Text ID IDC_STATIC

Caption Marital Status:

Radio Button ID IDC_RSINGLE

Caption &Single

Group Checked

Radio Button ID IDC_RMARRIED

Caption &Married

Radio Button ID IDC_RDIVORCED

Caption &Divorced

Radio Button ID IDC_RWIDOW

Caption &Widowed

Check Box ID IDC_CBEMPLOYED

Caption &Employed

Button ID IDC_BFIRST

Caption &First

25

Bài giảng môn học: Lâ ̣p trình Windows

Button ID IDC_BPREV

Caption &Previous

Button ID IDC_BNEXT

Caption Nex&t

Button ID IDC_BLAST

Caption &Last

Static Text ID IDC_SPOSITION

Caption Record 0 of 0

Giao diê ̣n chƣơng trình sau khi thiết kế trong nhƣ sau:

lớ p CWnd , mă ̣c dù lớ p document thì không phải

Khi chú ng ta phát triển các ƣ́ ng du ̣ng hoă ̣c cƣ̉ a sổ kiểu dialog -based chú ng ta sẽ gắn các biến cho các điều khiển trên cƣ̉ a sổ củ a lớ p hô ̣p thoa ̣i . Tuy nhiên đối vớ i mô ̣t ƣ́ ng du ̣ng da ̣ng SDI hoă ̣c MDI chú ng ta sẽ gắn cho lớ p nào ? Vì hàm UpdateData là một hàm của lớp CWnd và lớp view là một hậu duệ của , nên lớ p view sẽ là nơi logic nhất để đă ̣t các biến gắn vớ i các điều khiển trên cƣ̉ a sổ chƣơng trình.

Để gán các biến cho các điều khiển củ a chƣơng trình chú ng ta mở Class Wizard v à gắn

các biến cho các điều khiển và chỉ định lớp chứa chúng là lớp view cụ thể ở ứng dụng này là CSerializeView. Các biến đƣợc gán cho các điểu khiển nhƣ sau:

Object Name Category Type

IDC_CBEMPLOYED m_bEmployed Value BOOL

IDC_EAGE m_iAge Value int

IDC_ENAME m_sName Value CString

IDC_RSINGLE m_iMaritalStatus Value int

26

Bài giảng môn học: Lâ ̣p trình Windows

IDC_SPOSITION m_sPosition Value CString

Nếu nhƣ chú ng ta kiểm tra mã chƣơng trình củ a lớ p view chú ng ta sẽ thấy rằng không có hàm OnDraw. Nếu chú ng ta sƣ̉ du ̣ng lớ p tổ tiên CFormView cho ƣ́ ng du ̣ng SDI hoă ̣c MDI chún g ta không cần lo lắng về hàm OnDraw . Thay vào đó chú ng ta sẽ đối xƣ̉ vớ i lớ p view rất giống vớ i lớ p hô ̣p thoa ̣i nhƣ trong mô ̣t cƣ̉ a s - ổ hộp thoại hoặc một ứng dụng dạng dialog based. Sƣ̣ khác nhau chủ yếu ở đây là dƣ̃ liê ̣u chƣơng trình mà chú ng ta cần sƣ̉ du ̣ng để gán cho các điều khiển củ a cƣ̉ a sổ chƣơng trình không phải là củ a lớ p view mà là củ a lớ p document. Do đó chú ng ta cần xâu dƣ̣ng các tƣơng tác giƣ̃a các lớ p này để truyền các dƣ̃ liê ̣u giƣ̃a chú ng.

2.2.2 Tạo lớp Serializable

Khi chú ng ta ta ̣o ra mô ̣t ƣ́ ng du ̣ng da ̣ng form -based chú ng ta sẽ giả sƣ̉ rằng chƣơng trình

sẽ chứa nhiều bản ghi trong form và ngƣờ i dù ng có thể duyê ̣t qua các bản ghi này để thƣ̣c hiê ̣n các thay đổi và ghi la ̣i . Ngƣờ i dù ng cũng có thể thêm vào các bản ghi mớ i và loa ̣i bỏ bản ghi nào đó . Thách thức đối với chúng ta trong viê ̣c xây dƣ̣ng ƣ́ ng du ̣ng là làm thế nào để biểu diễn các bản ghi đó để có thể hỗ trơ ̣ tất cả các chƣ́ c năng củ a chƣơng trình .

Mô ̣t cách tiếp câ ̣n là ta ̣o ra mô ̣t lớ p bao gói tất cả các bản ghi và sau đó chƣ́ a tất cả các bản ghi trong một mảng giống nhƣ cách mà chúng ta đã thực hiện với các ứng dụng trong ba bài thực hành trƣớc đây . Lớ p này sẽ cần kế thƣ̀ a tƣ̀ lớ p CObject và cần chƣ́ a các biến cho tất cả các biến tƣơng ứng với các điều k hiển đƣơ ̣c thêm vào lớ p view cù ng vớ i phƣơng thƣ́ c đo ̣c và ghi tất cả các biến này. Cùng với việc thêm vào các phƣơng thức đọc và ghi các biến chúng ta cần phải làm cho lớ p này là mô ̣t lớ p serialiable bằng các thêm vào hàm Serialize cho lớ p cùng với hai macro đã mô tả ở trên.

Tạo một lớp cơ bản

, các biến

Nhƣ các ba ̣n có thể nhớ tƣ̀ bài thƣ̣c hành số 10 khi chú ng ta muốn ta ̣o ra mô ̣t lớ p mớ i chúng ta cần chọn tab Class View và nhấn chuột phải chọn Add | Add Class. Sau đó trong hô ̣p thoại tiếp theo chọn đó là một Generic class , chúng ta sẽ học nhiều hơn về các lớp này trong bài thực hành số 16. Tiếp theo chú ng ta gõ tên lớ p là CPerson , lớ p cơ sở củ a lớ p là lớ p CObject. Sau khi ta ̣o xong lớ p chú ng ta tiếp tu ̣c thêm các biến thành viên cho lớ p thành viên này cần phải khớp với các biến đƣợc gán cho các điều khiển trên cửa sổ chƣơng trình trong lớp view nhƣ trong bảng sau:

Name Type

m_bEmployed BOOL

m_iAge int

m_sName CString

m_iMaritalStatus int

Cài đặt phƣơng thức đọc và ghi cho lớp CPerson

Sau khi đã ta ̣o ra lớ p CPerson chú ng ta sẽ cung cấp mô ̣t phƣơng tiê ̣n để đo ̣c và ghi các biến cho lớ p. Cách dễ nhất để cung cấp các chức năng này là thêm các hàm inline trong định nghĩa của lớp , chúng ta có thể thêm một tập cá c hàm inline để thiết lâ ̣p và lấy giá tri ̣ củ a các

27

Bài giảng môn học: Lâ ̣p trình Windows

biến (riêng biê ̣t). Ví dụ chúng ta có thể mở file header (Person.h) và sửa lại khai báo của lớp CPerson nhƣ sau:

class CPerson : public CObject

{

public:

// Functions for setting the variables

void SetEmployed(BOOL bEmployed) { m_bEmployed = bEmployed;}

void SetMaritalStat(int iStat) { m_iMaritalStatus = iStat;}

void SetAge(int iAge) { m_iAge = iAge;}

void SetName(CString sName) { m_sName = sName;}

// Functions for getting the current settings of the variables

BOOL GetEmployed() { return m_bEmployed;}

int GetMaritalStatus() { return m_iMaritalStatus;}

int GetAge() {return m_iAge;}

CString GetName() {return m_sName;}

CPerson();

virtual ~CPerson();

private:

BOOL m_bEmployed;

int m_iMaritalStatus;

int m_iAge;

CString m_sName;

};

Hàm cấu tử mặc định đƣợc Visual C++ tƣ̣ đô ̣ng cung cấp và chú ng ta không cần sƣ̉ a đổi

(vớ i Visual 6.0 thì vẫn cần thêm hàm này vào).

2.2.3 Cài đặt hàm Serialize

Tiếp đến chú ng ta sẽ thêm hàm Serialize cho lớ p bắt đầu bằng viê ̣c thêm

2 macro vào , sau đó thêm hàm

đú ng vi ̣ trí củ a chú ng trong các file khai báo và cài đă ̣t củ a lớ p CPerson virtual, public Serialize(CArchive &ar) cho lớ p CPerson nhƣ sau:

void CPerson::Serialize(CArchive &ar)

{

// Call the ancestor function

CObject::Serialize(ar);

// Are we writing?

28

Bài giảng môn học: Lâ ̣p trình Windows

if (ar.IsStoring())

// Write all of the variables, in order

ar << m_sName << m_iAge << m_iMaritalStatus << m_bEmployed;

else

// Read all of the variables, in order

ar >> m_sName >> m_iAge >> m_iMaritalStatus >> m_bEmployed;

}

Về bản chất hàm này cũng khá đơn giản , ban đầu nó go ̣i tớ i phƣơng thƣ́ c Serialize củ a lớ p cha để xác đi ̣nh xem đó là thao tác đo ̣c hay ghi dƣ̃ liê ̣u , sau đó nó go ̣i tớ i hàm IsStoring để xác định nếu là ghi dữ liệu thì thực hiện ghi các biến thành viên của lớp còn ngƣợc lại thì thực hiê ̣n thao tác đo ̣c (chú ý thứ tự của các biến là quan trọng).

Chú ý: chúng ta biết là một ứng dụng không phải khi nào cũng có thể đọc hoặc ghi dữ

liê ̣u thành công nên trên thƣ̣c tế khi chú ng ta tiến hành thƣ̣c hiê ̣n hàm trên sẽ có mô ̣t Exception đƣơ ̣c throw để kiểm soát lỗi nhƣng ở đây chúng ta tạm thời bỏ qua vấn đề này (xem phu ̣ lu ̣c A cuốn Teach yourself Visual C++ 6.0 in 21 days để biết thêm chi tiết).

Cài đặt các chức năng cho lớp document

Khi chú ng ta xây dƣ̣ng mô ̣t ƣ́ ng du ̣ng da ̣ng form -based trong đó form nằm trên cƣ̉ a sổ là

không gian chính để ngƣờ i dù ng có thể tƣơng tác vớ i ƣ́ ng du ̣ng có mô ̣t giả sƣ̉ không đƣơ ̣c đề câ ̣p rõ ràng là ƣ́ ng du ̣ng củ a chú ng ta sẽ cho phép ngƣờ i dù ng làm viê ̣c vớ i nhiều bản ghi . Điều này có nghĩa là chúng ta cần hỗ trợ các tính năng lƣu và duyệt qua các bản ghi này . Viê ̣c lƣu các bản ghi có thể thƣ̣c hiê ̣n dễ dàng bằng cách sƣ̉ du ̣ng mô ̣t mảng nhƣ chú ng ta đã tƣ̀ ng làm trong bài thực hành số 10. Cách làm này cho phép chú ng ta có thể thêm vào mô ̣t số lƣơ ̣ng bản ghi không hạn chế (không biết có đú ng không nƣ̃a). Viê ̣c duyê ̣t qua các bản ghi đƣơ ̣c thƣ̣c hiê ̣n qua bốn thao tác là First (duyê ̣t bản ghi đầu tiên), Last (bản ghi cuối cùng), Previous (bản ghi trƣớ c) và Next (bản ghi tiếp theo). Chúng ta cần một chức năng thông báo để xác định bản ghi nào đang đƣơ ̣c hiển thi ̣.

Để lƣu trƣ̃ và hỗ trơ ̣ các tính năng này lớ p document cần hai biến : mô ̣t mảng và mô ̣t chỉ số bản ghi hiê ̣n ta ̣i nhƣ bảng sau:

Name Typ

e

m_iCurP int

osition

m_oaPeo

CO bArray ple

Mô ̣t viê ̣c khác chú ng ta cần làm là include file header củ a lớ p CPerson vào file cài đă ̣t của lớp document (vị trí trƣớc các file header của lớp document và view) nhƣ sau:

#include "stdafx.h"

#include "Serialize.h"

#include "Person.h"

29

Bài giảng môn học: Lâ ̣p trình Windows

#include "SerializeDoc.h"

#include "SerializeView.h"

Thêm mô ̣t bả n ghi mớ i Trƣớ c khi chú ng ta có thể duyê ̣t qua các bản ghi trong chƣơng trình chú ng ta cần xây dƣ̣ng chƣ́ c năng thêm mô ̣t bản ghi mớ i cho mảng các đối tƣơ ̣ng . Cách tiếp cận tƣơng tự nhƣ bài thực hành sẽ đƣợc sử dụng , và vì các bản ghi mặc định đều có các trƣờng dữ liệu là rỗng nên chú ng ta chỉ cần sƣ̉ du ̣ng hàm cấu tƣ̉ mă ̣c đi ̣nh do Visual C ++ cung cấp là đủ , đồng thờ i mỗi khi thêm vào mô ̣t bản ghi mớ i chú ng ta sẽ gán bản ghi hiê ̣n ta ̣i là bản ghi mớ i đó (hàm này là private).

CPerson* CSerializeDoc::AddNewRecord(void)

{

// Create a new CPerson object

CPerson *pPerson = new CPerson();

try

{

// Add the new person to the object array

m_oaPeople.Add(pPerson);

// Mark the document as dirty

SetModifiedFlag();

// Set the new position mark

m_iCurPosition = (m_oaPeople.GetSize() - 1);

}

// Did we run into a memory exception?

catch (CMemoryException* perr)

{

// Display a message for the user, giving them the

// bad news

AfxMessageBox("Out of memory", MB_ICONSTOP | MB_OK);

// Did we create a line object?

if (pPerson)

{

// Delete it

delete pPerson;

pPerson = NULL;

}

30

Bài giảng môn học: Lâ ̣p trình Windows

// Delete the exception object

perr->Delete();

}

return pPerson;

}

Tƣơng tƣ̣ nhƣ bài thƣ̣c hành số 10 chúng ta cần các hàm lấy tổng số bản ghi , số thƣ́ tƣ̣ bà đối tƣợng tƣơng ứng với bản ghi hiện tại nhƣ sau (các hàm này là public):

int CSerializeDoc::GetTotalRecords(void)

{

return m_oaPeople.GetCount();

}

int CSerializeDoc::GetCurRecordNbr(void)

{

return m_iCurPosition + 1;

}

CPerson* CSerializeDoc::GetCurRecord(void)

{

// Are we editing a valid record number?

if (m_iCurPosition >= 0)

// Yes, return the current record

return (CPerson*)m_oaPeople[m_iCurPosition];

else

// No, return NULL

return NULL;

}

Các chức năng tiếp theo cần đƣợc cài đặt là các hàm cho phép thực hiện các thao tác lấy

các bản ghi của mảng một cách tƣơng đối (đầu, cuối, trƣớ c, sau):

CPerson* CSerializeDoc::GetFirstRecord(void)

{

// Are there any records in the array?

if (m_oaPeople.GetSize() > 0)

{

31

Bài giảng môn học: Lâ ̣p trình Windows

// Yes, move to position 0

m_iCurPosition = 0;

// Return the record in position 0

return (CPerson*)m_oaPeople[0];

}else

// No records, return NULL

return NULL;

}

CPerson* CSerializeDoc::GetNextRecord(void)

{

// After incrementing the position marker, are we

// past the end of the array?

if (++m_iCurPosition < m_oaPeople.GetSize())

// No, return the record at the new current position

return (CPerson*)m_oaPeople[m_iCurPosition];

else

// Yes, add a new record

return AddNewRecord();

}

CPerson* CSerializeDoc::GetPrevRecord(void)

{

// Are there any records in the array?

if (m_oaPeople.GetSize() > 0)

{

// Once we decrement the current position,

// are we below position 0?

if (--m_iCurPosition < 0)

// If so, set the record to position 0

m_iCurPosition = 0;

// Return the record at the new current position

return (CPerson*)m_oaPeople[m_iCurPosition];

}else

// No records, return NULL

32

Bài giảng môn học: Lâ ̣p trình Windows

return NULL;

}

CPerson* CSerializeDoc::GetLastRecord(void)

{

// Are there any records in the array?

if (m_oaPeople.GetSize() > 0)

{

// Move to the last position in the array

m_iCurPosition = (m_oaPeople.GetSize() - 1);

// Return the record in this position

return (CPerson*)m_oaPeople[m_iCurPosition];

}else

// No records, return NULL

return NULL;

}

Tiếp đến là hàm Serialize cho mảng các đối tƣơ ̣ng củ a lớ p document (CSerializeDoc):

void CSerializeDoc::Serialize(CArchive& ar)

{

// Pass the serialization on to the object array

m_oaPeople.Serialize(ar);

}

Hàm làm công tác môi trƣờng, dọn dẹp tất cả mọi thứ trƣớc khi bắt đầu một tài liệu mới

(hàm này đƣợc gọi tới khi chƣơng trình kết thú c hoă ̣c trƣớ c khi mô ̣t tài liê ̣u mớ i đƣơ ̣c mở ):

void CSerializeDoc::DeleteContents()

{

// TODO: Add your specialized code here and/or call the base class

// Get the number of lines in the object array

int liCount = m_oaPeople.GetSize();

int liPos;

// Are there any objects in the array?

if (liCount)

{

33

Bài giảng môn học: Lâ ̣p trình Windows

// Loop through the array, deleting each object

for (liPos = 0; liPos < liCount; liPos++)

delete m_oaPeople[liPos];

// Reset the array

m_oaPeople.RemoveAll();

}

CDocument::DeleteContents();

}

Chúng ta có thể thấy các bƣớc thực hiện hoàn toàn giống với bài thực hành số 10, và

cũng cần nhắc lại một chú ý đó là : cần phải chuyển đối tƣơ ̣ng lấy tƣ̀ mảng CObArray thành kiểu CPerson vì đó là mô ̣t biến kiểu CObject.

Tiếp theo cần phải sƣ̉ a la ̣i hàm tƣơng ƣ́ ng vớ i sƣ̣ kiê ̣n OnNewDocument:

BOOL CSerializeDoc::OnNewDocument()

{

if (!CDocument::OnNewDocument())

return FALSE;

// TODO: add reinitialization code here

// (SDI documents will reuse this document)

// If unable to add a new record, return FALSE

if (!AddNewRecord())

return FALSE;

// Get a pointer to the view

POSITION pos = GetFirstViewPosition();

CSerializeView* pView = (CSerializeView*)GetNextView(pos);

// Tell the view that it's got a new data set

if (pView)

pView->NewDataSet();

return TRUE;

}

Khi mô ̣t tài liê ̣u mớ i bắt đầu chƣơng trình sẽ đƣa ra mô ̣t form rỗng sẵn sàng để nhâ ̣p thông tin mớ i , và để bản ghi này có thể sẵn sàng nhận thông tin chú ng ta thêm vào mô ̣t bản ghi trong mảng các đối tƣơ ̣ng và khi mô ̣t bản ghi mớ i đƣơ ̣c thêm vào mảng chú ng ta cần thay đổi viê ̣c hiển thi ̣ để chỉ ra rằng bản ghi mớ i đó tồn ta ̣i ngƣơ ̣c la ̣i các hiển thi ̣ sẽ tiếp tu ̣c vớ i bả n

34

Bài giảng môn học: Lâ ̣p trình Windows

(và ngƣời dùng có thể băn khoăn tại sao ứng dụng của

ghi cuối cù ng tƣ̀ tâ ̣p bản ghi trƣớ c chúng ta không bắt đầu với một tập bản ghi mới).

o

Khi mở mô ̣t tâ ̣p dƣ̃ liê ̣u sẵn có chú ng ta không cần thêm vào bất cƣ́ bản ghi mớ i nà nhƣng vẫn cần phải cho đối tƣơ ̣ng view biết rằng nó cần phải làm tƣơi bản ghi đƣơ ̣c hiển thi ̣ cho ngƣờ i dù ng . Do đó chú ng ta có thể thêm đoa ̣n mã tƣơng tƣ̣ cho hàm OnOpenDocument nhƣ sau (bỏ phần đầu có chức năng thêm vào một bản ghi mới) nhƣ sau:

BOOL CSerializeDoc::OnOpenDocument(LPCTSTR lpszPathName)

{

if (!CDocument::OnOpenDocument(lpszPathName))

return FALSE;

// TODO: Add your specialized creation code here

// Get a pointer to the view

POSITION pos = GetFirstViewPosition();

CSerializeView* pView = (CSerializeView*)GetNextView(pos);

// Tell the view that it's got a new data set

if (pView)

pView->NewDataSet();

return TRUE;

}

Đó là tất cả các công viê ̣c chuẩn bi ̣, tổ chƣ́ c và xƣ̉ lý dƣ̃ li ệu của lớp document, tiếp đến chúng ta sẽ làm việc với lớp view để tƣơng tác với ngƣời dùng.

Điều đầu tiên cần chú ý là các include trong các file mã nguồn cần theo đú ng thƣ́ tƣ̣ (giống bài thƣ̣c hành số 10): lớ p CPerson t rƣớ c, sau đó tớ i lớ p document và cuối cù ng là lớ p view và các chỉ thi ̣ include này chỉ thƣ̣c hiê ̣n trong các file cài đă ̣t lớ p (khác với C/C++ thông thƣờ ng)

#include "stdafx.h"

#include "Serialize.h"

#include "Person.h"

#include "SerializeDoc.h"

#include "SerializeView.h"

Vì số lƣợng các thao tác đối với các bản ghi là khá nhiều nên cũng giống nhƣ bài thực

hành 10 (sƣ̉ du ̣ng 1 biến lƣu điểm hiê ̣n ta ̣i củ a con trỏ chuô ̣t ) trong bài thƣ̣c hành này để cho tiê ̣n chú ng ta thêm mô ̣t biến thành viên kiểu CPerson * có tên là m_pCurPerson cho lớ p View.

35

Bài giảng môn học: Lâ ̣p trình Windows

Hàm đầu tiên mà chúng ta sẽ thực hiện là hàm hiển thị dữ liệu

, nhƣng chƣ́ c năng này àm một hàm riêng để sau đó gọi 10 về chƣ́ c năng ) ( hàm đƣơ ̣c sƣ̉ du ̣ng trong hầu hết các tƣơng tác nên chú ng ta sẽ l đến hàm này (giống hàm Draw củ a lớ p CLine trong bài thƣ̣c hành private):

void CSerializeView::PopulateView(void)

{

// Get a pointer to the current document

CSerializeDoc* pDoc = GetDocument();

if (pDoc)

{

// Display the current record position in the set

m_sPosition.Format("Record %d of %d", pDoc->GetCurRecordNbr(),

pDoc->GetTotalRecords());

}

// Do we have a valid record object?

if (m_pCurPerson)

{

// Yes, get all of the record values

m_bEmployed = m_pCurPerson->GetEmployed();

m_iAge = m_pCurPerson->GetAge();

m_sName = m_pCurPerson->GetName();

m_iMaritalStatus = m_pCurPerson->GetMaritalStatus();

}

// Update the display

UpdateData(FALSE);

}

Tiếp đến là các hàm duyệt qua các bản ghi , đồng thờ i cũng là các hàm xƣ̉ lý các sƣ̣ kiê ̣n

tƣơng ƣ́ ng vớ i các nú t lê ̣nh:

void CSerializeView::OnBnClickedBfirst()

{

// TODO: Add your control notification handler code here

// Get a pointer to the current document

CSerializeDoc * pDoc = GetDocument();

if (pDoc)

{

36

Bài giảng môn học: Lâ ̣p trình Windows

// Get the first record from the document

m_pCurPerson = pDoc->GetFirstRecord();

if (m_pCurPerson)

{

// Display the current record

PopulateView();

}

}

}

void CSerializeView::OnBnClickedBlast()

{

// TODO: Add your control notification handler code here

// Get a pointer to the current document

CSerializeDoc * pDoc = GetDocument();

if (pDoc)

{

// Get the last record from the document

m_pCurPerson = pDoc->GetLastRecord();

if (m_pCurPerson)

{

// Display the current record

PopulateView();

}

}

}

void CSerializeView::OnBnClickedBprev()

{

// TODO: Add your control notification handler code here

// Get a pointer to the current document

CSerializeDoc * pDoc = GetDocument();

if (pDoc)

{

37

Bài giảng môn học: Lâ ̣p trình Windows

// Get the last record from the document

m_pCurPerson = pDoc->GetPrevRecord();

if (m_pCurPerson)

{

// Display the current record

PopulateView();

}

}

}

void CSerializeView::OnBnClickedBnext()

{

// TODO: Add your control notification handler code here

// Get a pointer to the current document

CSerializeDoc * pDoc = GetDocument();

if (pDoc)

{

// Get the last record from the document

m_pCurPerson = pDoc->GetNextRecord();

if (m_pCurPerson)

{

// Display the current record

PopulateView();

}

}

}

Tiếp đến chú ng ta cần mô ̣t hàm reset la ̣i lớ p view mỗi khi mô ̣t bản ghi mớ i đƣơ ̣c bắt đầu hoă ̣c đƣơ ̣c mở để ngƣờ i không tiếp tu ̣c nhìn thấy tâ ̣p bản ghi cũ . Chúng ta có thể gọi tới hàm xƣ̉ lý sƣ̣ kiê ̣n củ a nú t First để buô ̣c lớ p view đƣa ra bản ghi đầu tiên trong tâ ̣p bản ghi . Để làm điều này chú ng ta thêm mô ̣t hàm void (pubic) tên là NewDataSet nhƣ sau:

void CSerializeView::NewDataSet(void)

{

OnBnClickedBfirst();

}

38

Bài giảng môn học: Lâ ̣p trình Windows

Đến đây chú ng ta có thể di ̣ch và cha ̣y chƣơng trình nhƣng các ba ̣n sẽ thấy chỉ các các nút duyệt qua các bản ghi là có tác dụng còn các điều khiển khác của form là không có tác dụng gì. Điều nà y là do chú ng ta chƣa có các hàm xƣ̉ lý các điều khiển trên form . Cần thêm các hàm xử lý các sự kiện với các điều khiển trên form chƣơng trình nhƣ sau:

Hàm xử lý dấu check Employed:

void CSerializeView::OnBnClickedCbemployed()

{

// TODO: Add your control notification handler code here

UpdateData(TRUE);

// If we have a valid person object, pass the data changes to it

if (m_pCurPerson)

m_pCurPerson->SetEmployed(m_bEmployed);

}

hàm xử lý các sự kiện cho các nút Radio:

void CSerializeView::OnBnClickedMaritalstatus()

{

// TODO: Add your control notification handler code here

UpdateData(TRUE);

// If we have a valid person object, pass the data changes to it

if (m_pCurPerson)

m_pCurPerson->SetMaritalStat(m_iMaritalStatus);

}

Đối với các trƣờng tên và tuổi chúng ta cần xử lý sự kiện EN _CHANGE và go ̣i tớ i các

hàm SetName, SetAge tƣơng ƣ́ ng củ a lớ p CPerson nhƣ sau:

void CSerializeView::OnEnChangeEname()

{

// TODO: Add your control notification handler code here

UpdateData(TRUE);

// If we have a valid person object, pass the data changes to it

if (m_pCurPerson)

m_pCurPerson->SetName(m_sName);

}

void CSerializeView::OnEnChangeEage()

{

39

Bài giảng môn học: Lâ ̣p trình Windows

// TODO: Add your control notification handler code here

UpdateData(TRUE);

// If we have a valid person object, pass the data changes to it

if (m_pCurPerson)

m_pCurPerson->SetAge(m_iAge);

}

Hãy dịch chạy thử và nhập dữ liệu vào để thấy các chức năng của chƣơng trình đã chạy

đú ng (đây quả là mô ̣t bài thƣ̣c hành không dễ dàng).

3. Quản lý file và thƣ mục nâng cao

Bài tập:

Bài tập 1: Viết chƣơng trình mô phỏng tìm kiếm file.

Bài tập 2: Viết chƣơng trình liệt kê tất cả các thông tin về các file và thƣ mục trong một thƣ mục.

40