Hướng dẫn
Cài đặt ban đầu
Tạo một project tương tự như ở phần tạo project.
Thao tác trên file WinMain
(file WinMain
đang rỗng):
#include <windows.h> const char* ClassName = "Win32 API from stdio.vn"; LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { }
Các cài đặt bao gồm:
ClassName
: tên chương trình, đặt tên ClassName là vì sau này khi thao tác với nhiều “đối tượng" khác nhau thì mỗi đối tượng thường có các window khác nhau, giống như một chương trình có nhiều cửa sổ, nhấn nút này sẽ nhảy ra cửa sổ khác.WndProc
: thủ tục tạo window.WinMain
: đã giải thích ở bài viết trước.
Bước 1: tạo một Window Class
Trong hàm WinMain()
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 = ClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if (!RegisterClassEx(&wc)) { MessageBox(NULL, "Cannot Register window", "Error", MB_ICONEXCLAMATION | MB_OK); return 0; }
Trong các hàm trên:
wc.cbSize
: kích thước trên bộ nhớ của window class.wc.style
: kiểu của Class, khác với kiểu của window (window style), thường có giá trị 0.wc.lpfnWndProc
: thủ tục của window (window procedure) là con trỏ trỏ tớiWinProc
.wc.cbClsExtra
,wc.cbWndExtra
: số lượng dữ liệu tối đa được cài đặt cho class, thường có giá trị 0.wc.hInstance
: quản lý thông tin của cửa sổ, tương đương với giá trị khai báo ởWinMain()
.wc.hIcon
: icon lớn của class, như đoạn code ở trên khai báo là icon có sẵn trong hệ thống.wc.hCursor
: con trỏ.wc.hbrBackground
: màu nền.wc.lpszMenuName
: con trỏ trỏ về dữ liệu của các thanh menu.wc.lpszClassName
: tên của class, như trên là cài đặt tên có sẵn khai báo ở đầu bài.wc.hIconSm
: tương tự như icon nhưng là icon nhỏ, hiện ở bên trái cùng chương trình.
if
để kiểm tra việc đăng ký Window Class
có thành công không, nếu không thành công thì hiện một cửa sổ thông báo với nội dung như trên.
Bước 2: sử dụng Window Class ở trên để tạo cửa sổ
HWND hwnd; hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, ClassName, "The title of stdio.vn", 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); return 0; } ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
Hàm trên dùng để đăng ký một handle
cho Window Class
ta vừa dựng. Đồng nghĩa việc tạo một handle
mà khi tương tác đến handle
này thì cũng có nghĩa ta đang tương tác với chính window class
đó.
Trong các hàm trên:
- Vế 1: 1 kiểu mở rộng của window style.
- Vế 2: Tên của Class, để hệ thống hiểu được ta đang thao tác với Class nào.
- Vế 3: Tiêu đề (Title Bar hoặc Caption ở bài viết 1).
- Vế 4: 1 kiểu của window style (tìm hiểu sau).
- Vế 5, vế 6: Tọa độ X, Y khi phần mềm bắt đầu (góc bên trái trên cùng), giá trị
CW_USEDEFAULT
là để hệ thống tự chọn giá trị cho nó. - Vế 7, vế 8: Chiều rộng và chiều cao của window.
- Vế 9: Cửa sổ “cha” của cửa sổ này. Khi đi sâu vào loạt bài viết này thì sẽ có khái niệm cha và con, hiểu đơn giản, trong một cửa sổ có nhiều thành phần. Ví dụ : 1 cửa sổ, trong cửa sổ đó có 1 nút thì nút đó là “con” của cửa sổ còn cửa sổ đó là “cha” của nút. Ở đây vì đây là cửa sổ đầu tiên (Top level) nên giá trị này là
NULL
. - Vế 10: Handle của menu chương trình, đặt
NULL
vì chưa có. - Vế 11: Instance của chương trình.
- Vế 12: Con trỏ dùng để gửi các thông tin bổ sung.
Tương tự bước 2, if
để kiểm tra việc đăng ký có thành công không.
Hàm ShowWindow()
để hiện thị cửa sổ, UpdateWindow()
để cập nhật, làm mới cho cửa sổ vừa tạo.
Bước 3: tạo vòng lặp message
Sau khi mọi thứ ở trên được khởi tạo. Vòng lặp Message sẽ “bắt” các thông tin “đối xử” với window và thực hiện nó.
Sử dụng vòng lặp vì khi thao tác trên console, bạn nhập lệnh hoặc giá trị rồi Enter, giá trị sẽ được đưa vào hệ thống ta gọi đó là lệnh. Với Win32 API, các lệnh này xảy ra liên tục nên nó được đưa vào hàng đợi (message queue). Hệ thống sẽ lấy những lệnh kế tiếp để thực thi và vòng lặp message sẽ làm việc này.
MSG Msg; while (GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam;
Hàm trên gồm:
GetMessage()
là hàm lấy thông tin từ message queue, khi tương tác với hệ thống, các tương tác được máy hiểu là các message và lưu vào một queue trong hệ thống, vòng lặp sẽ bắt các message này từ hệ thống.TranslateMessage()
– Hàm này sẽ dịch message, hiểu đơn giản nếu bạn gõ chữ thì chắc chắn bạn sẽ gõ phím, việc dịch sẽ tương tự như vậy.DispatchMessage()
– Hàm này sẽ xác định message này được gửi đến cửa sổ nào và thực thi nó, ví dụ như bạn có 2 cái cửa sổ A và B thì khi bạn tương tác lên cửa sổ A, A thì hàm sẽ thực hiện nó với A còn B thì không.
Việc trả về Msg.wParam
là để phục vụ cho hệ thống hiểu được tại sao lại dừng vòng lặp.
Đây làm bước cuối cùng thao tác trên WinMain()
, sau khi hết hàm, đóng khung lại ( ‘ } ‘ ).
Bước 4: thủ tục window – Window Procedure
Thủ tục window giống như bộ não của chương trình vậy, sau khi bắt thông tin ở vòng lặp, DispatchMessage()
sẽ gửi tin này đến window procedure của window ta đang tương tác.
Hàm bên dưới là để hiện thực khai báo WndProc
ở đầu đề bài:
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; }
Hàm trên có:
- Hàm switch là để xác định
Message
này là message như thế nào để xử lý. Như ví dụ ở trên nếu là messageAlt + F4
thì sẽ thực hiện như bên dưới. DestroyWindow()
Hàm sẽ gửi câu lệnhWM_DESTROY
đếnmessage queue
.- Khi
WM_DESTROY
được thực hiện, nó sẽ xóa các “con” của cửa sổ này trước khi thực hiện các thao tác kế tiếp. - Hàm
PostQuitMessage(0)
– Gửi lệnhWM_QUIT
đến hệ thống. - Hàm
DefWindowProc()
– hàm thực hiện các tác vụ mà ta đối xử với window, hiểu đơn giản, ta chỉ xử lý các lệnhmessage
đặc biệt còn cácmessage
còn lại ta để thư việnWin32 API
tự xử lý.
Tổng kết
#include <windows.h> const char* ClassName = "Win32 API form stdio.vn"; LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // Bước 1: Đăng ký 1 window class ... // Bước 2: Tạo window ... // Bước 3: Vòng lặp Message ... } // Bước 4: Tạo thủ tục window LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { ... }
Kết quả
Nhấn phím F5
hoặc chọn Debug -> Start Debugging
để thấy kết quả.
![](https://resources.stdio.vn/content/article/5ef62c875ef9e26f89a5c5f4/resources/res-1598003840-1598003840716.png)
Tại cửa sổ này, khi thao tác ví dụ như kéo mở rộng hoặc di chuyển, tất cả những hành động tương tác với window này sẽ được coi là message và đưa vào vòng lặp message ở bên trên. Khi vào vòng lặp thì DispatchMessage()
sẽ thực hiện hành động này và thể hiện ra window.
Lưu ý
Thử thay đổi vài giá trị để thấy sự khác biệt. Ví dụ như ở Bước 2 – vế 4: đổi lại là WS_CAPTION
rồi tiến hành build.