創建一個Win32窗口


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);
}

上面的代碼是創建一個窗口程序的一般步驟,具體流程如下:

  1. 註冊窗口類(RegisterClassEx
  2. 創建窗口(CreateWindowEx
  3. 在桌面顯示窗口(ShowWindow
  4. 更新窗口客戶端(UpdateWindow
  5. 進入消息處理和獲取循環。首先獲取消息(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_OVERLAPPEDWS_CAPTIONWS_SYSMENUWS_THICKFRAMEWS_MINIMZEBOXWS_MAXMIZEBOX 風格的層疊窗口。
  • WS_POPUP 創建一個彈出式窗口
  • WS_SIZEBOX 創建一個可調邊框的窗口。
  • WS_SYSMENU 創建一個在標題條上帶有窗口菜單的窗口。
  • WS_THICKFRAME 創建一個具有可調邊框的窗口
  • WS_VISIBLE 創建一個初始狀態爲可見的窗口

三、在桌面顯示窗口

::ShowWindow(hwnd, nCmdShow);

ShowWindow 函數用於指定窗口的顯示狀態。nCmdShow 取值可以是 SW_SHOWSW_HIDESW_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 消息,則界面雖然被銷燬了,但是消息循環還在繼續,程序還沒有結束。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章