Search…

Lập Trình Win32 API - Phần 2: Tạo Cửa Sổ Cơ Bản

18/09/20207 min read
Hệ điều hành Windows cung cấp các cửa sổ Window. Có thể sử dụng Win32 API để tạo các cửa sổ này, là nền tảng cho ứng dụng về mặt UI.

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ới WinProc.
  • 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à message Alt + F4 thì sẽ thực hiện như bên dưới.
  • DestroyWindow() Hàm sẽ gửi câu lệnh WM_DESTROY đến message 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ệnh WM_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ệnh message đặc biệt còn các message còn lại ta để thư viện Win32 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ả.

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.

Download project demo Win32 hoàn chỉnh

Bài chung series

IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024