文章目錄
1. GUI入口函數
GUI應用程序的入口函數是 WinMain , 這是一個自定義的入口函數。WinMain函數採用的是windows標準調用方式。下面是簡單的windows程序:
#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
::MessageBox(nullptr, TEXT("Hello, Win32 Application"), TEXT("First Win32 Window"), MB_OK);
return 0;
}
系統傳給WinMain函數幾個參數定義如下:
- hInstance: 制定當前模塊的實例句柄。在win32下,模塊的實例句柄和模塊句柄是一樣的,只是說法不同。所以可以通過下面語句獲取當前執行模塊的實例句柄。
hInsatnce = (HINSTANCE)GetModuleHandle(nullptr);
GetModuleHandle 函數的唯一參數是模塊名稱,函數返回這個模塊句柄。如果傳遞nullptr的話函數返回可執行文件所在模塊的模塊句柄。
- lpCmdLine 是命令行參數。其值是由CreateProcess函數的第二個參數指定。
- nCmdShow 指定窗口初始化的顯示方式。
當Windows向程序發送消息時,他調用程序的一個函數,這個函數的參數精確的描述了Windows發送的消息。在程序中稱爲窗口函數或消息處理函數。它是一個自定義的回掉函數,原型如下:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
hwnd 標識了消息的到達窗口
uMsg 參數時一個被命名的常量(消息ID)
下面是一個簡單示例
#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd = ::FindWindow(nullptr, TEXT("無標題 - 記事本"));
if (hWnd != nullptr)
::SendMessage(hWnd, WM_CLOSE, 0, 0);
return 0;
}
FindWindow 函數會查找窗口類名和窗口標題與指定字符串相匹配的窗口。返回找到的窗口句柄;SendMessage 用於向窗口中發送消息。
2. 創建窗口
下面是創建一個窗口的完整代碼
#include <windows.h>
#include <iostream>
LRESULT CALLBACK MainWindProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wndClass;
wndClass.cbSize = sizeof(wndClass);
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = MainWindProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = ::LoadIcon(nullptr, IDI_APPLICATION);
wndClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
wndClass.lpszMenuName = nullptr;
wndClass.lpszClassName = TEXT("MainWClass");
wndClass.hIconSm = nullptr;
// 註冊窗口類
::RegisterClassEx(&wndClass);
// 創建窗口
HWND hwnd = ::CreateWindowEx(\
0, \
TEXT("MainWClass"), \
TEXT("My First Window"), \
WS_OVERLAPPEDWINDOW, \
CW_USEDEFAULT, \
CW_USEDEFAULT, \
CW_USEDEFAULT, \
CW_USEDEFAULT, \
nullptr, \
nullptr, \
hInstance, \
nullptr);
if (hwnd == nullptr)
{
std::cout << "Create Window Error!" << std::endl;
return -1;
}
// 顯示窗口
::ShowWindow(hwnd, nCmdShow);
// 刷新窗口客戶區
::UpdateWindow(hwnd);
MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK MainWindProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = ::BeginPaint(hwnd, &ps);
std::wstring str = L"This is Test Text!";
::TextOut(hdc, 0, 0, str.c_str(), str.size());
::EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
::PostQuitMessage(0);
return 0;
}
return ::DefWindowProc(hwnd, message, wParam, lParam);
}
上面的代碼是創建一個窗口程序的一般步驟,具體流程如下:
- 註冊窗口類(RegisterClassEx)
- 創建窗口(CreateWindowEx)
- 在桌面顯示窗口(ShowWindow)
- 更新窗口客戶端(UpdateWindow)
- 進入消息處理和獲取循環。首先獲取消息(GetMessage),如果有消息到達,則消息分派給回調函數進行出路(DispatchMessage)。如果消息是MW_QUIT,則GetMessage函數返回False,整體消息循環結束。具體的處理過程在MainWndProc函數中進行。
一.註冊窗口類
註冊窗口類使用函數RegisterClassEx,一個窗口類定義了窗口的一些主要屬性,如圖標、光標、背景色和負責處理消息的窗口函數等。這些屬性定義在WNDCLASSEX結構中。
typedef struct _WNDCLASSEX{
UINT cbSize; // WNDCLASSEX結構的大小
UINT style; // 從窗口派生的窗口具有的風格
WNDPROC lpfnWndProc; // 消息處理函數指針
int cbClsExtra; // 指定緊跟在窗口類結構後的附加字節數
int cbWndExtra; // 指定緊跟在窗口示例後的附加字節數
HINSTANCE hInstance; // 本模塊的實例句柄
HICON hIcon; // 窗口左上角圖標的句柄
HCURSOR hCursor; // 光標的句柄
HBRUSH hbrBackground; // 背景畫刷的句柄
LPCSTR lpszMenuName; // 菜單名
LPCSTR lpszClassName; // 該窗口類的名稱
HICON hIconSm; // 小圖標句柄
}WNDCLASSEX;
(1) 指定窗口類的風格
wndClass.style = CS_HREDRAW | CS_VREDRAW; // 指定如果大小改變就重畫
前綴CS_意爲class style,在WINUSER.H中定義了全部可選樣式。
#define CS_VREDRAW 0x0001
#define CS_HREDRAW 0x0002
#define CS_DBLCLKS 0x0008
#define CS_OWNDC 0x0020
#define CS_CLASSDC 0x0040
#define CS_PARENTDC 0x0080
#define CS_NOCLOSE 0x0200
#define CS_SAVEBITS 0x0800
#define CS_BYTEALIGNCLIENT 0x1000
#define CS_BYTEALIGNWINDOW 0x2000
#define CS_GLOBALCLASS 0x4000
(2)指定窗口處理函數地址
wndClass.lpfnWndProc = MainWindProc;
WNDCLASSEX 結構成員lpfnWndProc 指定了基於此類窗口的窗口函數。當窗口收到消息時Windows即自動調用這個函數通知應用程序。
(3) 本程序的實例句柄傳給hInstance成員
wndClass.hInstance = hInstance;
(4) 設置圖標和光標
wndClass.hIcon = ::LoadIcon(nullptr, IDI_APPLICATION);
wndClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW);
LoadIcon 函數裝載了一個預定義圖標,命名爲 IDI_APPLICATION
LoadCursor 函數裝載了一個預定義的光標,命名爲 IDC_ARROW
(5)指定窗口重畫客戶區畫刷
wndClass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); // 使用白色畫刷
(6)指定窗口類名稱
wndClass.lpszClassName = TEXT("MainWClass"); // 窗口類名稱
填充完WNDCLASSEX 結構,就可以進行註冊了。RegisterClassEx 函數調用失敗將返回0
::RegisterClassEx(&wndClass);
二、創建窗口
要創建窗口,使用函數 CreateWindowEx 。
HWND hwnd = ::CreateWindowEx(\
0, \ // dwExStyle, 擴展樣式
TEXT("MainWClass"), \ // 類名
TEXT("My First Window"), \ // 標題
WS_OVERLAPPEDWINDOW, \ // 窗口風格
CW_USEDEFAULT, \ // X, 初始X座標
CW_USEDEFAULT, \ // Y, 初始Y座標
CW_USEDEFAULT, \ // nWidth, 寬度
CW_USEDEFAULT, \ // nHeight, 高度
nullptr, \ // hWndParent, 父窗口句柄
nullptr, \ // hMenu,菜單句柄
hInstance, \ // hInstance, 程序實例句柄
nullptr); // lpParam, 用戶數據
函數調用成功將返回窗口句柄,失敗返回nullptr。
第四個參數dwStyle的值是 WS_OVERLAPPEDWINDOW,即重疊窗口。由他指定的窗口有標題欄、系統菜單、可以改變大小的邊框,以及最大化、最小化和關閉按鈕。這是一個標準的窗口樣式。下面是一些常見風格定於:
- WS_BORDER 創建一個單邊框窗口
- WS_CAPTION 創建一個有標題框的窗口
- WS_CHID 創建一個子窗口。這個風格不能與WS_POPVP合用
- WS_DISABLED 創建一個初始狀態爲禁止的子窗口。
- WS_DLGFRAME 創建一個帶對話框邊框風格的窗口,這種風格的窗口不能帶標題條
- WS_HSCROLL 創建一個有水平滾動條的窗口
- WS_VSCROLL 創建一個有垂直滾動條的窗口
- WS_ICONIC 創建一個初始狀態爲最小化狀態的窗口,與WS_MINIMIZE風格相同
- WS_MAXIMIZE 創建一個具有最大化按鈕的窗口。
- WS_OVERLAPPED 產生一個層疊的窗口。
- WS_OVERLAPPEDWINDOW 創建一個具有WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_THICKFRAME、WS_MINIMZEBOX、WS_MAXMIZEBOX 風格的層疊窗口。
- WS_POPUP 創建一個彈出式窗口
- WS_SIZEBOX 創建一個可調邊框的窗口。
- WS_SYSMENU 創建一個在標題條上帶有窗口菜單的窗口。
- WS_THICKFRAME 創建一個具有可調邊框的窗口
- WS_VISIBLE 創建一個初始狀態爲可見的窗口
三、在桌面顯示窗口
::ShowWindow(hwnd, nCmdShow);
ShowWindow 函數用於指定窗口的顯示狀態。nCmdShow 取值可以是 SW_SHOW 、SW_HIDE、SW_MINIMIZE
四、更新窗口客戶區
::UpdateWindow(hwnd);
如果指定窗口的更新區域不爲空的話,UpdateWindow 函數通過向這個窗口發送一個WM_PAINT 消息更新它的客戶區。當窗口顯示在屏幕上時,窗口的客戶區被 WNDCLASSEX 中指定的刷子擦去,調用 UpdateWindow 函數將促使客戶區重畫,以顯示其內容。
五、進入無限的消息循環
Windows爲每一個線程維護一個消息隊列,每當有一個輸入發生,Windows就把用戶輸入翻譯成消息放在消息隊列中。GetMessage 函數可以從消息隊列中取一個消息填充MSG結構。
如果消息隊列中沒有消息,這個函數會一直等下去,直到有消息進入消息隊列爲止。
typedef struct tagMSG {
HWND hwnd; // 消息要發向的窗口句柄
UINT message; // 消息標識符,以WM_開頭的預定義值(Windows Message)
WPARAM wParam; // 消息的參數之一
LPARAM lParam; // 消息的參數之二
DWORD time; // 消息放入消息隊列的時間
POINT pt; // 這是一個POINT數據結構,表示消息放入消息隊列時鼠標的位置
} MSG,
GetMessage 函數從消息隊列中取得的消息如果不是WM_QUIT ,則返回非零值。一個WM_QUIT 消息會使GetMessage 函數返回0,從而消息循環結束。
::TranslateMessage(&msg);
此調用把鍵盤輸入翻譯成可調用的消息。
::DispatchMessage(&msg);
DispatchMessage 函數分發一個消息到對應窗口的窗口函數。MainWndProc 處理消息後把控制權交給Windows,此時DispatchMessage 函數仍然繼續工作,當他返回時,消息隊列從調用GetMessage 函數開始進入下一輪。
3. 處理消息代碼
所有消息不做處理的消息都必須返回一個名爲DefWindowProc的函數讓Windows做默認處理,從DefWindowProc 函數返回的值也必須從消息處理函數返回。
每當客戶區變爲無效,消息處理函數就會收到一個新的WM_PAINT 消息。
WM_DESTORY 是窗口必須處理的一個消息,當用戶關閉窗口時,消息處理函數就會收到一 個 WM_DESTORY消息。當接收到這個消息的時候,說明窗口正在銷燬。
::PostQuitMessage(0);
PostQuitMessage函數會會向消息隊列中插入一個 WM_QUIT 消息。GetMessage 函數如果從消息隊列中獲得到消息時 WM_QUIT ,它將返回0。從而退出消息循環。如果不使用函數 PostQuitMessage 發送WM_QUIT 消息,則界面雖然被銷燬了,但是消息循環還在繼續,程序還沒有結束。