用 Win32 API 設計視窗程式
本文將帶你了解 Win32 API 的基本架構,並透過一個最小化範例理解:
註冊視窗類別(Register Window Class)
建立視窗(Create Window)
執行訊息迴圈(Message Loop)
處理視窗訊息(Window Procedure)
程式完整架構
一個 Win32 視窗程式通常包含:
WinMain()
├─ RegisterClassEx()
├─ CreateWindowEx()
├─ ShowWindow()
└─ Message Loop
↓
WndProc()
其中:
WinMain()是程式進入點WndProc()是視窗訊息處理中心Message Loop 持續接收使用者操作
1. 定義視窗類別名稱
首先建立一個全域常數,用來識別視窗類別。
const char g_szClassName[] = "myWindowClass";
之後註冊視窗與建立視窗時,都會使用這個名稱。
2. 建立視窗程序(Window Procedure)
Windows 採用「訊息驅動(Message Driven)」架構。
所有使用者操作:
點擊滑鼠
按下鍵盤
關閉視窗
都會轉換成訊息(Message)。
這些訊息會送到 WndProc() 處理。
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;
}
WM_CLOSE
當使用者:
點擊右上角 X
按下 Alt + F4
系統會送出:
WM_CLOSE
收到後呼叫:
DestroyWindow(hwnd);
開始銷毀視窗。
WM_DESTROY
當視窗真正被銷毀後:
WM_DESTROY
會被觸發。
此時通知程式結束:
PostQuitMessage(0);
它會送出:
WM_QUIT
讓訊息迴圈停止執行。
DefWindowProc()
若訊息不是我們要處理的:
return DefWindowProc(
hwnd,
msg,
wParam,
lParam
);
交給 Windows 預設處理。
例如:
視窗拖曳
最小化
最大化
調整大小
都由系統自動完成。
3. WinMain:程式進入點
Windows GUI 程式不使用 main()。
而是:
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
這是所有 Win32 視窗程式的起點。
4. 註冊視窗類別
建立:
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);
各欄位用途
| 欄位 | 功能 |
|---|---|
| lpfnWndProc | 指向訊息處理函式 |
| hIcon | 大圖示 |
| hIconSm | 小圖示 |
| hCursor | 滑鼠游標 |
| hbrBackground | 背景顏色 |
| lpszClassName | 類別名稱 |
註冊類別
if(!RegisterClassEx(&wc))
{
MessageBox(
NULL,
"Window Registration Failed!",
"Error!",
MB_ICONEXCLAMATION | MB_OK
);
return 0;
}
若註冊失敗:
RegisterClassEx()
回傳 0。
此時跳出錯誤訊息並結束程式。
5. 建立視窗
註冊完成後即可建立視窗。
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
g_szClassName,
"The title of my window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
320,
240,
NULL,
NULL,
hInstance,
NULL
);
參數說明
| 參數 | 功能 |
|---|---|
| WS_EX_CLIENTEDGE | 凹陷邊框 |
| g_szClassName | 使用的類別 |
| 視窗標題 | 顯示在標題列 |
| WS_OVERLAPPEDWINDOW | 標準視窗樣式 |
| 320 | 寬度 |
| 240 | 高度 |
建立後畫面如下:
+----------------------+
| The title of my ... |
+----------------------+
| |
| |
| Client Area |
| |
+----------------------+
檢查建立是否成功
if(hwnd == NULL)
{
MessageBox(
NULL,
"Window Creation Failed!",
"Error!",
MB_ICONEXCLAMATION | MB_OK
);
return 0;
}
若回傳 NULL 表示建立失敗。
6. 顯示視窗
建立完成後仍然是隱藏狀態。
需要呼叫:
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
ShowWindow()
讓視窗顯示。
ShowWindow(hwnd, nCmdShow);
UpdateWindow()
要求立即重繪。
UpdateWindow(hwnd);
避免剛開啟時出現空白畫面。
7. 訊息迴圈(Message Loop)
這是整個程式的核心。
while(GetMessage(
&Msg,
NULL,
0,
0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
GetMessage()
取得訊息:
GetMessage(...)
例如:
滑鼠移動
鍵盤輸入
視窗關閉
重繪請求
TranslateMessage()
將鍵盤訊息轉換成字元訊息。
TranslateMessage(&Msg);
例如:
按下 A
↓
WM_CHAR
DispatchMessage()
把訊息送到:
WndProc()
處理。
DispatchMessage(&Msg);
流程如下:
使用者操作
↓
GetMessage
↓
TranslateMessage
↓
DispatchMessage
↓
WndProc
8. 程式結束
當收到:
PostQuitMessage(0);
時:
GetMessage()
會回傳:
0
訊息迴圈結束:
while(GetMessage(...) > 0)
跳出後回傳:
return Msg.wParam;
程式正式結束。
完整執行流程
WinMain
│
├─ RegisterClassEx
│
├─ CreateWindowEx
│
├─ ShowWindow
│
└─ Message Loop
│
▼
WndProc
│
├─ WM_CLOSE
│ ↓
│ DestroyWindow
│
└─ WM_DESTROY
↓
PostQuitMessage
↓
WM_QUIT
↓
Message Loop 結束
結語
這個範例雖然只有數十行程式碼,但已經涵蓋 Win32 GUI 程式最重要的四個核心概念:
視窗類別(Window Class)
視窗建立(Window Creation)
訊息迴圈(Message Loop)
訊息處理(Window Procedure)
理解這個架構後,就可以進一步加入:
按鈕(Button)
功能表(Menu)
繪圖(GDI)
鍵盤與滑鼠事件
自訂控制項
逐步建立完整的 Windows 桌面應用程式。
留言