Windows程序設計中的窗口與消息

本文用更容易理解的結構和例子,重寫了《Windows程序設計》中的“窗口與消息”一節,並且包含了這一節所有的知識點。
我不是有經驗的windows程序員,但是可以保證的是,這篇文章比原文更加容易理解,並且你並不會因爲跳過此章而錯過任何知識。
原文最大的問題是在開始羅列了用到的函數和字符串,而一些通俗易懂的文章都會盡量的迴避這種情況的發生。除此之外,文章的結構還是不錯的。
“基礎知識和Unicode寬字符”可以選讀,不必需。所以我把它放在了最後。

窗口與消息

窗口是Windows程序的核心。

一個窗口包含什麼?

  • 一個典型的窗口包含一個顯示有程序名稱的標題欄、一個菜單欄。可能還帶一個工具欄(toolbar)和一個滾動條(scrollbar)。
  • 另外一種類型的窗口是對話框,這種窗口可以不帶標題欄。
  • 其實窗口還包括各種各樣的按鈕(push buton)、單選按鈕(radio button)、複選框(checkbox)、列表框(listbox)、滾動條(scroll bar)、文本框等,這些對象都可用於裝飾對話框。

每一個這些對象都是一個窗口。 更準確地說,這些對象都被稱爲“子窗口”或“控件窗口”或“子窗口控件”。

用戶如何與窗口交互?消息在其中扮演什麼角色?

程序員的視角與用戶的視角非常一致。用戶對窗口的輸入以“消息”的形式傳遞給窗口,而窗口也藉助消息來與其他窗口進行通信。深入理解“消息”這個概念是學習Windows編程過程中的一個重要環節。

下面給出Windows消息的一個例子。衆所周知,大多數Windows程序的窗口尺寸都是可以調整的,即用戶可用鼠標抓取窗口的邊框,並通過拖動操作改變窗口的尺寸。通常程序都會通過改變窗口的內容來對這種改變做出響應。你可能已經猜測到(你很可能是正確的),在用戶調整窗口尺寸的過程中所產生的所有瑣碎的代碼都是由Windows而非應用程序進行處理的。但是應用程序顯然“知道”該窗口尺寸已發生變化,因爲它能夠對自身的顯示格式加以改變。

但是應用程序是如何獲知用戶對窗口尺寸已做調整的呢?對習慣於傳統字符模式編程的程序員來說,操作系統不具備將這類消息傳遞給用戶的機制。因此這個問題是理解Windows體系結構的關鍵。當用戶改變窗口的尺寸時,Windows便嚮應用程序發送一條攜帶新窗口尺寸相關信息的消息,接着應用程序對自身的內容進行調整以反映出窗口尺寸的變化。

“Windows嚮應用程序發送了一條消息。”我希望你能反覆品味這句話。它到底有什麼含義?這裏,我們正在討論的是程序代碼而非電報系統。那麼操作系統是如何將消息傳遞給應用程序的?

當我提到“Windows嚮應用程序發送了一條消息。”這句話時,我其實是在說Windows調用了該程序內部的一個函數——這個函數是你寫的,而且是該程序的核心。此函數的參數描述了由Windows所發送並由你的程序所接收的特定消息。這個函數被稱爲“窗口過程”。

窗口過程可以是你的應用程序中的某個函數,也可以位於一個動態鏈接庫中。

窗口類標識了用於傳遞給窗口的消息的窗口過程

窗口總是依據“窗口類”來創建的。窗口類標識了用於處理傳遞給窗口的消息的窗口過程。窗口類的使用允許多個窗口共享同一窗口類,因而多個窗口可以使用相同的窗口過程。例如,Windows程序中的所有按鈕都基於相同的窗口類,與該窗口類關聯的窗口過程位於一個Windows動態鏈接庫中,它可對所有傳遞到按鈕窗口的消息進行處理。

窗口過程用於處理傳遞給窗口的消息。通常這些消息用於將用戶的鼠標或鍵盤輸入通知給窗口。例如,正是通過這種途徑按鈕窗口能夠獲知它被“單擊”。而當窗口尺寸被調整或當窗口表面需要重繪時,也有相應的消息來通知窗口。

當Windows程序開始執行時,Windows首先爲該程序創建一個“消息隊列”(message queue)。該消息隊列中存放着應用程序可能創建的所有窗口的消息。Windows應用程序中一般都包含一小段稱爲“消息循環”(message loop)的代碼,該段代碼用於從消息隊列中檢索消息,並將其分發給相應的窗口過程。其他消息則不經過消息隊列直接發送給窗口過程。

創建自己的窗口

程序的整體架構

現在,讓我們通過下面這個例子更好的理解窗口和消息創建的過程。

當然,現在僅僅需要對這段程序有個大致印象——這個程序由WinMain主函數和WndProc窗口過程構成。後面的文章將會帶領你看懂這個程序。

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);	//窗口過程


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static TCHAR szAppName[] = TEXT("HelloWin");
	HWND         hwnd;
	MSG          msg;
	WNDCLASS     wndclass;

	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);//加載圖標
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);//加載鼠標光標
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//獲取一個圖形對象
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = szAppName;

	//RegisterClass(&wndclass)用於註冊窗口類,參數是指向WNDCLASS類型的結構指針
	if (!RegisterClass(&wndclass))
	{
		MessageBox(NULL, TEXT("This program requires Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindow(szAppName,                  // window class name
		TEXT("The Hello Program"), // window caption
		WS_OVERLAPPEDWINDOW,        // window style
		CW_USEDEFAULT,              // initial x position
		CW_USEDEFAULT,              // initial y position
		CW_USEDEFAULT,              // initial x size
		CW_USEDEFAULT,              // initial y size
		NULL,                       // parent window handle
		NULL,                       // window menu handle
		hInstance,                  // program instance handle
		NULL);                     // creation parameters

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC         hdc;
	PAINTSTRUCT ps;
	RECT        rect;

	switch (message)
	{
	case WM_CREATE:
		PlaySound(TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC);
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		GetClientRect(hwnd, &rect);

		DrawText(hdc, TEXT("Hello, Windows 98!"), -1, &rect,
			DT_SINGLELINE | DT_CENTER | DT_VCENTER);

		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

下面是這個僅僅80行的代碼段的顯示效果:

在這裏插入圖片描述

客戶區是一塊應用程序可以自由繪圖並向用戶傳達可視輸出的區域。

不僅如此,你還可以用鼠標按住標題欄在屏幕上來回拖動,你可以用鼠標對窗口尺寸進行調整。當窗口尺寸發生改變時,程序會自動將文本字符串調整到客戶區的中心。你可單擊最大化按鈕使HELLOWIN窗口充滿整個屏幕。你可單擊最小化按鈕將窗口從屏幕上縮隱。你也可通過系統菜單(位於標題欄最左端的小圖標)來調用這些選項。你還可從系統菜單中選擇Close選項或單擊標題欄最右端的關閉按鈕或雙擊系統菜單圖標來關閉窗口,從而結束程序。

在執行這個程序之前,需要在連接器中增加WINMM.LIB(多媒體庫文件)和光盤文件中的HELLOWIN.WAV文件。

註冊前的準備

在關心程序具體調用的Windows函數之前,我們先看一下其中出現的新的數據類型,這對於讀懂程序是很有幫助的。

在HELLOWIN.C中使用的一些標識符屬於新數據類型,這些類型也是在Windows頭文件中通過typedef 或#define語句定義的。這樣做的動機在於減輕Windows 程序從16系統移植到32位系統的工作量。如今看來,實際效果與期望還是存在一定的距離,不過這種思想還是很值得推薦的。

有時,這些新數據類型純粹是出於簡便的考慮而作的縮寫。例如,UINT類型=unsigned int,在Windows98中,這種類型表示一個32位的值。WinMain的第三個參數類型爲PSTR=char*,該類型表示一個指向非寬字符串的指針。

除此之外,WORD表示16位的無符號短整型;LONG表示32位的有符號長整型;LRESULT等價於LONGWINAPICALLBACK都定義爲_stdcall,指定了Windows和應用程序之間的調用順序。

還有四種新的數據類型:MSG、WNDCLASS、PAINSTRUCT和RECT,分別表示消息結構、窗口類結構、繪製結構和矩形結構。

我們在創建窗口的時候,僅僅需要調用CreateWindow函數就可以。下面是一個例子:

hwnd = CreateWindow(szAppName,                  // window class name
		TEXT("The Hello Program"), // window caption
		WS_OVERLAPPEDWINDOW,        // window style
		CW_USEDEFAULT,              // initial x position
		CW_USEDEFAULT,              // initial y position
		CW_USEDEFAULT,              // initial x size
		CW_USEDEFAULT,              // initial y size
		NULL,                       // parent window handle
		NULL,                       // window menu handle
		hInstance,                  // program instance handle
		NULL);                     // creation parameters

這裏的szAppName被稱爲“窗口類名稱。”

整個程序的執行流程如下:
創建窗口類WNDCLASS–>註冊窗口類–>使用窗口類創建窗口–>顯示窗口–>進入消息循環隊列–>使用窗口過程處理消息–>結束程序

第二步,註冊窗口類

在創建應用程序窗口之前,必須調用函數RegisterClass來註冊窗口類。該函數只需要一個參數,即一個指向WNDCLASS類型的結構的指針。

對於WNDCLASS類型,定義如下:

首先是ASCII版本——
在這裏插入圖片描述
然後是Unicode版本——
在這裏插入圖片描述
正如我們知道的那樣,這個版本與該結構的ASCII版本相比,唯一的區別在於最後兩個字段被定義爲指向寬字符的常量字符串,而不是指向ASCIⅡ字符的常量字符串。

在WinMain中,我們通常用如下形式定義一個WNDCLASS類型的結構:

WNDCLASS wndclass;

然後對該結構的10個字段進行初始化,並調用RegisterClass函數。

在WNDCLASS結構中,兩個最重要的字段是第二個字段和最後一個字段。第二個字段(lpfnWndProc)是用於基於該窗口類的所有窗口的窗口過程的地址。在HELLOWIN.C中,該窗口過程就是WndProc。最後一個字段是窗口類的名稱,允許用戶任意命名。當程序只創建一個窗口時,窗口類的名稱通常與程序名相同。

在我們的程序中,窗口類名稱被這樣定義:
static TCHAR szAppName[] = TEXT("HelloWin"); wndclass.lpszClassName = szAppName;

另外幾個字段描述了窗口類的其他一些特性。下面依次考察WNDCLASS結構的每個字段。

首先是第一個字段:

wndclass.style = CS_HREDRAW | CS_VREDRAW;

前綴爲CS的標識符定義如下:

#define CS_VREDRAW 0×0001
#define CS_HREDRAW 0×0002
#define CS_KEYCVTWINDOW 0×0004
#define CS_DBLCLKS 0×0008
#define CS_OWNDC 0x0020
#define CS_CLASSDC 0x0040
#define CS_PARENTDC 0×0080
#define CS_NOKEYCVT0×0100
#define CS_NOCLOSE 0×0200
#define CS_SAVEBITS 0×0800
#define CS_BYTEALIGNCLIENT 0×1000
#define CS_BYTEALIGNWINDOW 0×2000
#define CS_GLOBALCLASS 0×4000
#define CS_IME 0x00010000

以這種方式定義的標識符常常稱爲“位標記”(bit flag),因爲每個標識符都只能影響複合值中的一位。在這些類風格中,只有少數會被經常使用。程序HELLOWIN使用了兩個標識符,以指定無論何時窗口的水平尺寸(CS_HREDRAW)或垂直尺寸(CS_VREDRAW)被改變,所有基於該窗口類的窗口都將被重新繪製。在調整HELLOWIN的窗口尺寸時,可以發現,文本字符串被重新繪製在窗口新的中心位置上。正是這兩個標識符確保了這種結果。稍後我們將瞭解窗口尺寸的變化是如何通知給窗口過程的。

第二個字段:

wndclass.lpfnWndProc = WndProc;

該語句將該窗口類的窗口過程設爲WndProc函數,即HELLOWIN.C中的第二個函數。這個函數將處理傳遞給所有基於該窗口類創建的窗口的所有消息。在C語言中,當在語句中按這種方式使用函數名時,我們引用的實際上是指向函數的指針。

下面兩個字段用於在類結構和Windows內部維護的窗口結構中預留一些額外的空間:

wndclass.cbClsExtra=0;
wndclass.cbwndExtra=0;

應用程序可以根據需要來使用這些額外的空間。HELLOWIN中沒有用到這個特性,因此將這兩個參數賦爲0。否則,正如匈牙利標記法所表示的,這些字段可以被設爲一個“字節數”。(在本書第7章的CHECKER3程序中,將用到cbWndExtra字段。)

接下來的字段表示應用程序的實例句柄:

wndclass.hInstance = hInstance;

下面的語句爲所有基於該窗口類的窗口設定一個圖標:

wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);

爲了獲取預定義圖標的句柄,需要調用函數Loadlcon,並將函數的第一個參數設爲NULL。而在從保存在磁盤中的應用程序的可執行文件中加載自定義圖標時,該參數必須設爲hlnstance,即相應程序的實例句柄。第二個參數用於標識該圖標。對預定義圖標來說,該參數是一個前綴爲IDI的標識符。這些標識符都在頭文件WINUSER.H中定義,而前綴IDI表示“圖標的標識符”(IDfor an icon)。IDI_APPLICATION圖標只是一個窗口的小圖片。
Loadlcon函數返回該圖標的句柄。我們其實並不關心句柄的實際取值,因爲它只是被用來設定hlcon字段。該字段的定義位於WNDCLASS結構中,其類型爲HICON,表示“圖標的句柄”(handle to an icon)。

wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

LoadCursor函數載入一個預定義的鼠標指針(稱爲IDC_ARROW),並返回指針的句柄。該句柄被用來設定WNDCLASS結構的hCursor字段。當鼠標指針出現在這類窗口的客戶區內時,將變成一個小箭頭。

下一個字段爲這類窗口的客戶區指定了背景色。字段名hbrBackground的前綴hbr表示“畫刷的句柄”(handle to a brush)。畫刷是一個圖形學術語,表示用於區域填充的像素着色模式。Windows有幾個標準的畫刷,又稱“庫存”畫刷。下面對GetStockObject的調用返回一個白色畫刷的句柄:

wndclass.hbrBackground=GetStockobject(WHITE_BRUSH);

這意味着窗口客戶區的背景將被填充爲白色;這是一種十分常見的選擇。

接下來的一個字段指定了窗口類的菜單。由於程序HELLOWIN不帶任何菜單,所以該字段被設爲NULL:

wndclass.1pszMenuName=NULL;

最後,必須爲窗口類賦予一個名稱。對一個小程序來說,這個名稱可以簡單地用程序名錶示,如保存在變量szAppName中的字符串“HelloWin”:

wndclass.1pszClassName=szAppName;

這個字符串既可由ASCII字符組成,也可由Unicode字符組成,具體取決於UNICODE標識符是否已被定義。

當WNDCLASS結構的所有10個字段完成初始化之後,程序HELLOWIN調用函數RegisterClass來完成該窗口類的註冊。該函數的唯一參數是一個指向WNDCLASS結構的指針。實際上,系統存在兩種不同的用於註冊窗口類的函數:RegisterClassA和RegisterClassW,它們的參數分別是一個指向WNDCLASSA結構的指針和一個指向WNDCLASSW結構的指針。程序利用哪個函數註冊窗口類,決定着傳遞給窗口的消息是包含ASCII文本還是UNICODE文本。

這就引發了一個問題:如果在定義了UNICODE標識符的情況下編譯程序,程序會調用RegisterClassW函數。在Windows NT下運行程序時,這沒有問題。但是在Windows98下運行程序時,RegisterClassW函數實際上並沒有真正實現。該函數雖然有一個入口,但它僅是簡單地返回零值,表示出現了一個錯誤。這是在Windows98下運行的UNICODE程序通知用戶發生錯誤並終止的絕佳機會。本書大部分示例程序都採用以下方式處理RegisterClass函數調用:

	if (!RegisterClass(&wndclass))
	{
		MessageBox(NULL, TEXT("This program requires Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

函數MessageBoxW能夠正常運行,因爲它是Windows98中所實現的少有的幾個UNICODE函數之一。

顯然,這段代碼假定RegisterClass沒有因其他原因而出現調用失敗,如因WNDCLASS結構的lpfmWndProc字段爲NULL。利用函數GetLastError可以獲知這類錯誤的原因。

GetLastError函數是Windows中一個通用的函數,用於獲取當函數調用失敗時的擴展錯誤信息。各函數的說明文檔中都已指明是否可以利用GetLastError獲取這種信息。例如,在Windows 98中調用RegisterClassW時,函數GetLastError的返回值將爲120。通過查看頭文件WINERROR.H可知,值120對應的標識符爲ERROR_CALL_NOT_IMPLEMENTED

/Platform SDK/Windows Base Services/Debugging and Error Handling/Error Codes/System Errors-Numerical Order中,也可查到各種錯誤類型。

一些Windows程序員喜歡檢查每個函數調用的返回值以判斷是否有錯誤發生。這種舉措當然是很有意義的,理由如下:我想你一定十分熟悉分配內存時總需要進行錯誤檢查這種規則。而許多Windows函數都需要分配內存,例如,爲了存儲窗口類信息,RegisterClass需要分配相應的內存。因此無論如何都應對該函數的返回值進行驗證。另一方面,如果由於無法分配到所需的內存而導致RegisterClass調用失敗,則Windows可能已經陷入停止狀態。

創建窗口

由於窗口類只是定義了窗口的一般特徵,因此基於同一窗口類可以創建許多不同的窗口。在調用CreateWindow函數來創建窗口時,可以指定許多與窗口有關的細節信息。

Windows編程的新手有時容易對窗口類和窗口之間的區別以及爲什麼窗口的特徵不能一次性指定完畢而感到疑惑。

實際上,按照這種方式對信息進行劃分會帶來許多便利。例如,所有的下壓按鈕窗口都基於相同的窗口類。與該窗口類關聯的窗口過程位於Windows內部,並負責處理鼠標和鍵盤對按鈕的輸入,以及定義按鈕在屏幕上的視覺外觀。從這個方面看,所有下壓按鈕的工作方式都是一樣的。但所有的下壓按鈕又都是不同的。它們尺寸各異,在屏幕上的位置也不盡相同,而且所帶的文本字符串也有差別。後面的這幾種特徵都是窗口定義的一部分,而非屬於窗口類定義。

與傳給RegisterClass函數的信息是通過一個數據結構指定的不同,傳給CreateWindow的信息則是通過獨立參數的形式指定的。在HELLOWIN.C中,對CreateWindow的調用如下:

     hwnd = CreateWindow (szAppName,                  // window class name
                          TEXT ("The Hello Program"), // window caption
                          WS_OVERLAPPEDWINDOW,        // window style
                          CW_USEDEFAULT,              // initial x position
                          CW_USEDEFAULT,              // initial y position
                          CW_USEDEFAULT,              // initial x size
                          CW_USEDEFAULT,              // initial y size
                          NULL,                       // parent window handle
                          NULL,                       // window menu handle
                          hInstance,                  // program instance handle
                          NULL) ;                     // creation parameters
     

需要說明的是,事實上還有另外兩個用於創建窗口的函數,即CreateWindowA和CreateWindowW,這兩個函數將頭兩個參數分別當作ASCII和UNICODE字符來處理。

註釋爲“窗口類名稱”的參數是szAppName,該參數中包含字符串“HelloWin”,即程序剛剛註冊的窗口類的名稱。我們所要創建的窗口正是通過這種方式與窗口類建立了關聯。

由該程序創建的窗口是一個普通的層疊(overlapped)窗口。該窗口有一個標題欄、一個位於標題欄左邊的系統菜單按鈕、一個窗口尺寸調整邊框以及位於標題欄右方的三個按鈕(分別用於最小化、最大化和關閉窗口)。這是窗口的標準風格(或稱樣式)之一,該風格的名稱爲WS_OVERLAPPEDWINDOW,它是作爲CreateWindow的“窗口風格”參數而出現的。如果查看一下頭文件WINUSER.H,你會發現該風格其實是由幾個位標記通過按位或組合而成:
在這裏插入圖片描述
“窗口標題”是要出現在窗口標題欄中的那行文本。

註釋爲“初始x座標”和“初始y座標”的那兩個參數指定了窗口左上角相對於屏幕左上角的初始位置。在這個程序中,我們將這些參數設爲CW_USEDEFAULT,意爲我們希望Windows將層疊窗口的初始位置取爲默認值。(CW_USEDEFAULT被定義爲0x80000000)。默認情況下,Windows會將連續新建的窗口的左上角位置沿水平方向和垂直方向分別作步長爲1的偏移。類似地,註釋爲“初始x方向尺寸”和“初始y方向尺寸”
的兩個參數分別指定了窗口的初始寬度和高度。標識符CW_USEDEFAULT同樣表明我們希望Windows將窗口的尺寸取爲默認值。

如果新建窗口爲頂級窗口(例如應用程序窗口),註釋爲“父窗口句柄”的參數就應設爲NULL。通常,當兩個窗口之間存在父子關係時,子窗口總是位於父窗口的前方。應用程序窗口總是位於桌面窗口的前方,但不必爲了調用CreateWindow函數而設法獲取桌面窗口的句柄。

在這段程序中,“窗口菜單句柄”也被設爲NULL,這是由於該窗口沒有菜單。“程序實例句柄”則被設爲作爲WinMain函數參數傳入的程序實例句柄。最後,我們將“創建參數”賦爲了NULL。也可將該參數指向某些數據,以便後續在程序中加以引用。

CreateWindow 函數的返回值爲一個指向所創建窗口的句柄。該句柄保存在變量hwnd中,該變量被定義爲HWND(handle to the winndow)類型。Windows系統中,每一個窗口都有一個句柄。在程序中可用句柄來對窗口進行引用。許多Windows函數都以hwnd爲輸入參數,以便Windows獲知該函數是要對哪個窗口進行操作。如果一個程序創建了多個窗口,則每個窗口都具有不同的句柄。在Windows程序所操作的各種類型的句柄中,窗口句柄是最重要的一類。

顯示窗口

當CreateWindow調用返回時,窗口已在Windows內部被創建。這句話的基本意思是,Windows已經分配了一塊內存來保存CreateWindow調用中指定的窗口信息以及一些其他信息。Windows可通過窗口句柄來獲取這些信息。

但是,要將窗口顯示在屏幕上,僅僅這樣是不夠的。我們還需調用另外兩個函數。第一個如下:

showwindow(hwnd,icmdshow);

該函數的第一個參數是指向剛纔由CreateWindow所創建的窗口的句柄。第二個參數是WinMain函數所接收的iCmdShow值。該參數決定着窗口在屏幕中的初始顯示形式,即是正常顯示,還是顯示爲最小化窗口或最大化窗口。將程序添加到“開始”菜單時,用戶可能會選擇一種偏好。如果窗口是正常顯示,則從WinMain傳給ShowWindow的參數值便爲SWSHOWNORMAL;若窗口以最大化顯示,則爲SWSHOWMAXIMIZED;若窗口只是顯示在任務欄,則爲SW_SHOWMINNOACTIVE。

函數ShowWindow用於將窗口顯示在屏幕中。如果該函數的第二個參數是SW_SHOWNORMAL,則該窗口的客戶區將被在窗口類中所指定的背景畫刷擦除。然後下面的調用將使窗口客戶區重繪:

Updatewindow(hwnd);

這是通過向窗口過程(即HELLOWIN.C中的WndProc函數)發送一條WMPAINT消息而完成的。我們隨後將研究WndProc是如何處理該消息的。

進入消息循環隊列

什麼叫消息循環隊列?

在UpdateWindow被調用之後,新建窗口在屏幕中便完全可見了。此時,該程序必須能夠接收來自用戶的鍵盤輸入和鼠標輸入。Windows爲當前在其中運行的每一個Windows程序都維護了一個“消息隊列”。當輸入事件發生後,Windows會自動將這些事件轉換爲“消息”,並將其放置在應用程序的消息隊列中。

應用程序通過執行一段名爲“消息循環”的代碼段來從該消息隊列中獲取消息:

while(GetMessage(&msg,NULL00))
{
	TranslateMessage(&msg);
	DispatchMessage(&msg);
}

其中的msg是一個結構變量,其類型爲MSG。該類型的定義位於頭文件WINUSER.H中,其定義如下:

typedef struct tagMsG 
{
HwND hwnd;
UINT message;
WPARAM wParam;
LPARAM 1Param;
DwORD time;
POINT Dt;
}MSG,*PMSG;

POINT數據類型是另外一種結構,其定義位於頭文件WINDEF.H中:

typedef struct tagPOINT 
{
LONGx;
LONGy;
}POINT,*PPOINT;

開啓消息循環的GetMessage函數用於從消息隊列中對消息進行檢索:
GetMessage(&msg,NULL,0,0)
該調用將一個指向名稱爲msg的MSG結構變量的指針傳給Windows。其第二、第三和第四個參數分別被設爲NULL或0,表明該程序希望獲取由該程序所創建的所有窗口的消息。
Windows用從消息隊列中得到的下一條消息來填充消息結構的各個字段。該結構的各個字段說明如下:

  • hwnd 消息所指向的窗口的句柄。在HELLOWIN程序中,它與從CreateWindow所返回的hwnd相同,因爲這是該程序所擁有的唯一窗口。
  • message消息標識符。這是一個用於標識消息的數字。對於每條消息,在Windows的頭文件(大多數位於WINUSER.H)中都爲其定義了一個以WM(window message)爲前綴的標識符。例如,如果你將鼠標放置在HELLOWIN程序的主窗口的客戶區,並按下鼠標左鍵,則Windows會將一個message字段爲WM LBUTTONDOWN(值爲0x0201)的消息放入消息隊列中。
  • wParam一個32位的“消息參數”,該參數的含義和取值取決於具體的消息。
  • IParam另外一個32位的消息參數,該參數的含義和取值同樣取決於具體消息。
  • time 消息進入消息隊列的時間。
  • pt消息進入消息隊列中時鼠標指針的位置座標。
    如果從消息隊列中檢索到的消息的message字段不等於WM_QUIT(其值爲0x0012),則GetMessage將返回一個非0值,否則返回0。

下面的語句將msg結構返還給Windows以進行某些鍵盤消息的轉換:

TranslateMessage(&msg);

而以下語句則將msg結構再次返回給Windows:

DispatchMessage(&msg);

接下來,Windows會將這條消息發送給合適的窗口過程來處理。也就是說,Windows調用了窗口過程。在HELLOWIN程序中,窗口過程爲WndProc。當WndProc處理完該消息後,將控制權轉回給 Windows,後者還將爲DispatchMessage調用服務。當Windows從DispatchMessage返回HELLOWIN後,消息循環又會進行下一輪的GetMesage調用。

使用窗口過程處理消息

什麼是窗口過程?

到目前爲止,所討論的內容都是一些繁瑣的常規步驟,包括註冊窗口類、創建窗口、在屏幕中顯示窗口、程序進入消息循環、從消息隊列中檢索消息。

真正有意義的事情發生在窗口過程中。正是窗口過程決定了窗口客戶區的顯示內容以及窗口如何對用戶的輸入做出響應。

在HELLOWIN程序中,窗口過程是一個名爲WndProc的函數。窗口過程的名稱可以任意命名(只要不與其他名稱衝突即可)。一個Windows程序可包含多個窗口過程,但一個窗口過程總是與一個通過調用RegisterClass註冊的特定窗口類相關聯。

CreateWindow函數基於特定的窗口類創建窗口,而基於同一個窗口類則可創建多個窗口。

窗口過程總是按照如下方式來定義:

LRESULT CALLBACK WndProc(HwND hwnd,UINT message,WPARAM wParam,LPARAM 1Param)

窗口過程的4個參數與MSG結構的前4個字段是一一對應的。第一個參數是hwnd,表示接收消息的窗口的句柄,它與從CreateWindow函數返回的句柄相同。對於一個像HELLOWIN 這樣只創建了一個窗口的程序,這是程序已知的唯一一個窗口句柄。如果某個應用程序基於相同的窗口類創建了多個窗口(因而這些窗口的窗口過程均相同),則hwnd將標識接收消息的窗口的句柄。

第二個參數與MSG結構的mesage字段對應,是一個標識消息的數字。最後兩個參數是32位的消息參數,用於提供關於該消息的更豐富的信息。這些參數中所包含的內容依賴於具體的消息類型。有時一個消息參數是由兩個16位的值組合而成,有時一個消息參數是一個指向文本字符串或一個數據結構的指針。

應用程序通常並不直接對窗口過程進行調用。窗口過程幾乎總是由Windows自身調用的。應用程序如果希望調用自身的窗口過程,則可通過調用函數SendMessage來實現,該函數我們將在後面幾章進行介紹。

窗口過程所接收的每條消息都由一個數字來標識,即窗口過程的message參數。

Windows頭文件WINUSER.H中爲各種類型的消息定義了以WM爲前綴的標識符。

通常,Windows 程序員會使用switch-case結構來確定窗口過程所收到的消息的類型以及相應的處理方法。當窗口過程對消息進行處理後,應返回0。所有窗口過程不進行處理的消息都必須傳給名稱爲DefWindowProc的Windows 函數。DefWindowProc的返回值必須從窗口過程返回。

現在回到我們的程序中!

在HELLOWIN中,WndProc選擇了只對三個消息進行處理,即 WMCREATE、WM_PAINT和WM_DESTROY。這個窗口過程的結構如下所示:

switch (message)
	{
	case WM_CREATE:
		PlaySound(TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC);
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);

		GetClientRect(hwnd, &rect);

		DrawText(hdc, TEXT("Hello, Windows 98!"), -1, &rect,
			DT_SINGLELINE | DT_CENTER | DT_VCENTER);

		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	//不進行處理的消息都傳給Windows函數DefWindowProc返回
	return DefWindowProc(hwnd, message, wParam, lParam);

用DefWindowProc來對所有窗口過程沒有處理的消息進行默認處理非常重要,否則其他的正常行爲(如結束程序)將無法進行。

HELLOWIN中WndProc選擇處理的第一條消息WM_CREATE

其實這也正是一個窗口過程所接收到的第一條消息。當Windows在WinMain 函數中處理CreateWindow函數調用時,WndProc將接收到該消息。也就是說,當HELLOWIN 調用CreateWindow時,Windows完成其必須的操作,而在此過程中,Windows對WndProc進行調用,並將其第一個參數設爲該窗口的句柄,將第二個參數設爲WM_CREATE(其值爲1)。接着,WndProc對WMCREATE消息進行處理,並將控制權返還給Windows。然後Windows從CreateWindow 調用返回到HELLOWIN中,並繼續執行WinMain中的其他步驟。

通常情況下,窗口過程都會在處理WM_CREATE消息期間對窗口進行一次性的初始化。HELLOWIN通過播放一個名爲HELLOWIN.WAV的波形聲音文件作爲對該消息的處理。完成播放功能是通過調用PlaySound函數而實現的,該函數的描述可在如下路徑中找到:/Platform SDK/Graphics and Multimedia Services/Multimedia Audio/Waveform Audio,其用法說明可在/Platform SDK/Graphics and Multimedia Services/Multimedia Reference/Multimedia Functions中找到。

PlaySound函數的第一個參數是波形文件的名稱。(該參數也可以是控制面板的聲音控制區定義的聲音別名或應用程序資源。)該函數的第二個參數只有當聲音文件是一個資源時
纔有用。該函數的第三個參數指定了一組選項。在本例中,我指定了第一個參數爲一個文件名且該段聲音是以異步方式播放的,即當所指定的聲音文件開始播放時PlaySound函數便立即返回,而無需等待該文件播放結束。按照這種方式,應用程序可以繼續完成其初始化。

當WndProc處理完WM_CREATE消息後,將從窗口過程返回0。

WndProc所處理的第二條消息是WMPAINT。這條消息在Windows編程中極其重要。

當窗口的客戶區的部分或全部“無效”且必須“更新”時,應用程序將得到此通知。這也就意味着窗口必須被“重繪”。

但何種情況下客戶區會變爲無效?當窗口被首次創建時,整個客戶區都是無效的,因爲此時應用程序尚未在該窗口上繪製任何東西。第一條WM_PAINT消息(通常在應用程序調用WinMain中的UpdateWindow 時出現)將指示窗口過程在窗口客戶區進行繪製。

在調整HELLOWIN窗口的尺寸時,客戶區也會變爲無效。你可能還記得在前面的程序中,我們將HELLOWIN的wndclass結構的style字段設爲了標記CS_HREDRAW和CSVREDRAW。這就指示Windows當窗口尺寸發生變化時,整個窗口都應宣佈無效。在此之後,窗口過程將接收到一條WM_PAINT消息。

如果先最小化HELLOWIN,然後再將窗口恢復到原先的尺寸,Windows並不會保存客戶區的內容。在圖形環境中,這種情況下需要保存的數據太多了。對此,Windows採取的策略是宣佈窗口無效。窗口過程接收到WM_PAINT消息後,會自行恢復窗口的內容。在屏幕中拖動窗口導致窗口之間發生重疊時,Windows並不負責保存被另一個窗口覆蓋的區域。當被覆蓋的區域在後來不再被遮擋時,窗口被標記爲無效。窗口過程會收到一條WM_PAINT消息,並對窗口的內容進行重繪。

對WM_PAINT消息的處理幾乎總是從調用BeginPaint函數開始:

hdc=BeginPaint(hwnd,sps);

而以調用EndPaint函數結束:

EndPasnt (hwnd,spa);

在這兩個函數調用中,第一個參數均爲程序的窗口句柄,而第二個參數均爲指向一個類型爲PAINTSTRUCT結構的指針。PAINTSTRUCT結構包含一些窗口過程用來對客戶區進行繪製的信息。在下一章,我將介紹該結構中的各個字段;而現在我們只管在BeginPaint和EndPaint這兩個函數中使用它。

在BeginPaint調用期間,如果客戶區的背景尚未被擦除,則Windows會對其進行擦除。擦除背景時使用的畫刷是在用於註冊窗口類的WNDCLASS結構中的hbrBackground字段中指定的。在HELOWIN這個例子中,所使用的畫刷是一個庫存的白色畫刷,即Windows會將窗口的背景清除爲白色。BeginPaint 調用將使整個客戶區有效,並返回一個“設備環境句柄”。設備環境(device context)是指物理輸出設備(如視頻顯示器)及其設備驅動程序。

我們需要設備環境句柄以在窗口的客戶區顯示文本和圖形。使用由BeginPaint函數返回的設備環境句柄,是無法在客戶區以外的區域進行繪製的。EndPaint函數用於釋放設備環境句柄,以使其無效。

如果一個窗口過程不對WMPAINT消息進行處理(這是極其罕見的),則該類消息必須交給DeWindowProe 來處理。DefWindowProc所做的只是簡單地依次調用BeginPaint 和EndPaint,以使客戶區變爲有效。

當WndProc 調用完BeginPaint之後,它對GetClientRect進行調用。

GetclientRect{hwnd,&rect};

該函數的第一個參數是程序的窗口句柄。第二個參數爲指向類型爲RECT的矩形結構的指針。該結構具有4個類型爲LONG的字段,名稱分別爲lefttop、right 和botom.GetClientRect函數將依據窗口尺寸來對這4個字段進行設置。其中,1eft和ap字段總是會被賦爲0,這樣right和botom字段就分別表示以像素爲單位的客戶區的寬度和高度。

WndProc並未對該RECT結構做任何處理,而僅是將其賦給一個指針而作爲DrawText函數的第四個參數:

Drawrext(hdc,rExT(“Rel1o,windows 98:"),-1,&rect,OT SINGEITINE 1 DT_CENTER I DT_VCENTER):DrawTet

函數所實現的功能與其名稱一致,即繪製文本。由於該函數完成的是繪製功能,因此其第一個參數爲由BeginPaint 函數所返回的設備環境句柄。第二個參數是所要繪製的文本內容。第三個參數被設爲-1,表示該文本字符申以0作爲結尾。

DrawText函數的最後一個參數是一組位標記,這些位標記的定義位於WINUSER.H中。

(儘管由於DrawText完成的是輸出的顯示而看起來像是一個GDI函數,但由於它是一個相當高層的繪製函數,因而實際上屬於用戶模塊。該函數的文檔請參閱/Platfom sDK/Graphics and Mulimedia Services/GDI/Fonts and Text。)這些標記指示所顯示的文本應在由第四個參數所限定的矩形區域內單行顯示,並且水平和垂直居中。按照這種方式對該函數進行調用,就會使字符串“Hello,Windows98!”顯示在窗口客戶區的中央。

無論何時當客戶區變爲無效時(如調整窗口尺寸時),WndProc都將接收到一條新的WM PAINT消息。WndProc 通過調用GetClientRect函數可以獲得更新後的窗口尺寸,並再次將這行文本顯示在改變後的窗口的中央。

第三條消息WM_DESTROY

WM DESTROY是另外一個非常重要的消息。該消息表明Windows正處在依照用戶的命令銷燬窗口的過程中。當用戶單擊【關閉】按鈕或從程序的系統菜單中選擇【關閉】時,該消息將會發出。(稍後,我將更詳細地介紹WM_DESTROY消息的產生機制。)HELLOWIN通過調用函數PostQuitMessage來對WM_DESTROY消息做出響應,這是一種標準的響應方式:
PostOuitMessage(0);該函數的功能是將一個WMQUIT消息插入到程序的消息隊列中。在前面的內容中我曾經提到,對於所有非 WMQUIT消息,GetMessage函數都將返回非零值,而對WM_QUIT消息,GetMessage將返回0。這樣,如果GetMessage獲取到的消息是WM_QUIT,便會退出消息循環。然後,程序會執行下列語句:

return meg,wParam;msg

結構的wParam字段是傳遞給PostQuitMessage函數的值(通常情況下爲0)。該返回語句將從WinMain中退出並將程序結束。

補充

函數調用

HELLOWIN調用了至少18個Windows函數。下面按照出現順序一一列出這些被調用的函數(並附有簡短描述):
Loadlcon 加載圖標,以供程序使用。
·LoadCursor加載鼠標光標,以供程序使用。
GetStockObject 獲取一個圖形對象。在本例中是一個用來對窗口的背景進行重繪的畫刷。
·RegisterClass爲應用程序的窗口註冊一個窗口類。
·MessageBox顯示消息框。
·CreateWindow 基於窗口類創建一個窗口。
ShowWindow在屏幕中顯示窗口。
·UpdateWindow 指示窗口對其自身進行重繪。
·GetMessage從消息隊列獲取消息。
TranslateMessage翻譯一些鍵盤消息。
·DispatchMessage將消息發送給窗口過程。
·PlaySound 播放聲音文件。
BeginPaint 標明窗口繪製開始。
GetClientRect 獲取窗口客戶區的尺寸。
DrawText 顯示一個文本字符串。
·EndPaint 結束窗口繪製。
·PostQuitMessage將“退出”消息插入消息隊列。
DefWindowProc執行默認的消息處理。

大寫標識符

你可能已經注意到了HELLOWIN.C中所使用的大量大寫標識符。這些標識符都是在Windows頭文件中定義的。這些標識符有很多都是以兩個或三個字母作爲前綴,且其後緊跟一個下劃線。

這些標識符其實都是數值常量。前綴表明該常量所屬的一般類別,如下表所示。

在這裏插入圖片描述

Windows編程中的若干難點

即使我已對HELLOWIN程序做了比較細緻的剖析,你可能仍然會覺得這個程序的結構和工作原理難以理解。在一個爲字符模式環境所編寫的簡短C程序中,整個程序可能都僅包含在main函數中。但在HELLOWIN中,WinMain函數只包含一些常規的必要操作,如註冊窗口類、創建窗口、從消息隊列中檢索和分發消息。

真正有價值的行爲發生在窗口過程中。在HELLOWIN這個範例程序中,這種行爲並不是很多——WndProc僅僅播放了一個聲音文件,並在窗口中顯示了一行文本。但在後幾章中,你將瞭解到Windows程序所做的一切幾乎都是在窗口過程中對消息做出響應。在開始編寫 Windows程序之前,這是一個必須逾越的概念難點。

究竟是誰調用誰

程序員們一定早已熟知調用操作系統來完成某種操作的做法。例如,需要打開文件時,C程序員會調用fopen函數,而fopen函數是通過調用操作系統來完成打開文件這個操作的。這一點毋庸置疑。

但Windows系統截然不同。雖然它也提供了成百上千個函數,但它還可以調用用戶的程序,尤其是我們稱爲WndProc的窗口過程。窗口過程總是與程序通過調用RegiserClas註冊的窗口類關聯在一起。基於該窗口類創建的窗口使用該窗口類中指定的窗口過程來處理所有的消息。Windows通過調用窗口過程來向窗口發送消息。

WndProc會在類似場景下被Windows系統調用:新建窗口時;窗口被最終銷燬時;窗口尺寸發生變化或被移動或被最小化時;用戶用鼠標在窗口中執行單擊或雙擊操作時:用戶從鍵盤輸入字符時;用戶從菜單中選擇某個菜單項時:用戶用鼠標操作或單擊滾動條時:窗口的客戶區需要重繪時。

所有這些對WndProc的調用都是以消息的形式出現的。大多數Windows程序的主要任務無一例外都致力於對各種各樣的消息進行處理。這些Windows能夠發送給程序的消息通常都帶有前綴WM,而其定義一般都位於頭文件WINUSER.H中。

實際上,在程序外部對程序內的例程進行調用的這種方式在字符模式的編程中也有一些例子。例如C語言中的signal 函數就可獲取CTlkC中斷或其他來自操作系統的中斷。爲MS-DOS編寫的老式程序通常都會捕獲硬件中斷。

而在Windows中這一概念被擴展到了所有事件上。窗口所發生的一切都通過消息的形式轉給窗口過程。然後窗口過程以某種方式對消息作出反應,或是把消息傳遞給DefWindowProc以進行默認處理。

窗口過程的wParam和IParam參數在HELLOWIN中只是作爲參數傳給了DefWindowProc.這些參數爲窗口過程提供了關於該消息的一些附加信息。這些參數的含義與具體消息有關。

現在我們來看一個例子。無論何時當窗口的客戶區尺寸發生變化時,Windows都會對該窗口的窗口過程進行調用。此時,窗口過程的hwnd參數就是尺寸發生變化的這個窗口的句柄。(請不要忘記,窗口過程可以爲基於同一窗口類創建的多個窗口處理消息。
通過hwnd 參數,窗口過程就可獲知到底是哪一個窗口正在接收消息。)

mesage 參數爲WM SIZE.一條WMSIZE 消息的wParam 參數取值可爲SIZE RESTORED SIZE_MINIMIZED、SIZE_MAXIMIZED、SIZE_MAXSHOW 或 SIZE MAXEHIDE(這些值都定義在WNUSERO中,取值依次爲0到4)。也就是說,wParam參數表明窗口是將被改爲非最小化或非最大化尺寸,還是將被最小化,抑或是將被最大化,或將被隱藏。

1Param參數中包含該窗口的新尺寸。窗口的新寬度(一個16位的值)和新高度(也是一個16位的值)被組合在32位的IParam參數中。頭文件WINDEF日中定義了一些宏便於從IParam中提取這兩個值。第4章將介紹這些宏的用法。

有時,由於對DefWindowProc的調用,一些消息還會產生其他的消息。例如,假定你已運行HELLOWIN,並且用鼠標單擊了【關閉】按鈕,或假定使用鍵盤或鼠標選擇了系統菜單中的【關閉】選項,那麼DefWindowProc會對該鍵盤或鼠標輸入進行處理。當該函數檢測到你已選擇【關閉】選項時,便會向窗口過程發送一條 WM_SYSCOMMAND消息。

WndProc會將該消息傳給DefWindowProc。作爲響應,DefWindowProc又會給窗口過程發送一條WMCLOSE消息。WndProc再次將該消息傳給DefWindowProc。此時DefWindowProc 會通過調用DestroyWindow來對WM_CLOSE做出響應。DestroyWindow則會導致Windows向窗口過程發送一條WM_PESTROY 消息。WndProc最終又會通過調用PostQuitMessage 將一條WM_QUIT消息投遞到消息隊列中來作爲對該消息的響應。這條消息會使WinMain中的消息循環結束,並使程序終止。

隊列消息和非隊列消息

前面提過Windows將消息發送給一個窗口,意思是說Windows調用了該窗口的窗口過程。但是,一個Windows程序同時還具有一個消息循環用於從消息隊列中檢索和分發消息,其中檢索消息是通過調用GetMessage實現的,而分發消息是通過調用DispatchMessage而實現的。

那麼,是Windows程序捕獲到消息(非常類似於字符模式程序對鍵盤輸入的捕獲)然後將這些消息轉送到某個目的地呢?還是直接從程序外部接收消息呢?答案是兩者皆有。

消息既可以是“隊列消息”,也可以是“非隊列消息”。隊列消息是指那些由Windows放入程序的消息隊列中的消息。在程序的消息循環中,消息被檢索,然後被投遞到窗口過程中。非隊列消息則是由Windows對窗口過程的直接調用而產生的。我們一般說隊列消息被“投遞”(post)到消息隊列中,而非隊列消息則是被“發送”(send)到窗口過程。無論在哪種情形下,窗口過程都會爲窗口獲取所有消息一—無論是隊列消息還是非隊列消息。因此,窗口過程實際上是窗口的“消息中心”。

隊列消息主要由用戶的輸入產生,主要形式爲按鍵消息(例如WMKEYDOWN和WMKEYUP消息)、由按鍵產生的字符消息(WM_CHAR)、鼠標移動(WM_MOUSEMOVE)、鼠標單擊(WM_LBUTTONDOWN)等。此外,隊列消息還包括定時器消息(WM_TIMER)、重繪消息(WM_PAINT)和退出消息(WM_QUIT)。

非隊列消息則包括隊列消息以外的其他所有消息。非隊列消息通常由調用特定的Windows函數引起。例如,當WinMain 調用CreateWindow函數時,Windows就會創建窗口,並在創建過程中向窗口過程發送一條WM_CREATE消息。當WinMain 調用Show Window函數時,Windows又會將WMSIZE消息和WM_SHOWWINDOW消息發送給窗口過程。接下來,WinMain又對UpdateWindow 進行了調用,這就促使Windows向窗口過程發送一條WM_PAINT消息。表明鍵盤或鼠標輸入的隊列消息也能夠產生非隊列消息。例如,當用鍵盤或鼠標選擇某個菜單項時,鍵盤或鼠標消息會進入消息隊列,而最終表明有某菜單項被選中的WM_COMMAND消息卻是一個非隊列消息。

這個過程顯然十分複雜。但幸運的是,這些複雜性大部分都由Windows承擔了。從窗口過程的視角看,這些消息是以有序、同步的方式到來的。窗口過程可以選擇對這些消息進行某種處理或乾脆直接忽略掉。

在我剛纔提到消息是以有序、同步的方式到來時,這句話的第一層含義是指消息與硬件中斷不同。在窗口過程處理某一消息的過程中,程序不會被其他消息突然中斷。

雖然Windows程序可有多個執行線程,但每個線程的消息隊列僅爲那些其窗口過程在該線程內執行的窗口進行消息處理。換言之,消息循環和窗口過程不是併發運行的。當一個消息循環從其自身的消息隊列中檢索消息,並調用DispatchMessage 函數將檢索到的消息發送給窗口過程時,只有在窗口過程將控制權返還給Windows 後,DispatchMesage纔會返回。

但是,窗口過程可以調用爲其發送其他消息的函數。這種情形下,在該函數調用返回之前,窗口過程必須將第二個消息處理完畢,此時窗口過程才處理前一條消息。例如,當一個窗口過程調用UpdateWindow時,Windows會以一條WM_PAINT消息來調用窗口過程。當窗口過程處理完WM_PAINT 消息後,UpdateWindow 調用纔將控制權返還給窗口過程。

這就意味着窗口過程必須是可重入的(rentrant)。在大多數情形下,這並不會帶來什麼問題,但是對此必須做到心中有數。例如,假定在窗口過程處理某條消息期間,你對一個靜態變量進行了設置,接着又調用了一個Windows函數。當該函數返回時,你能確保這個變量仍然跟先前一樣嗎?我們的確無法保證這一點,因爲如果你所調用的特定Windows 函數產生了另外一條消息,且窗口過程在處理第二條消息期間對該變量進行了修改,則該變量的狀態一定會發生改變。而這也是我們在編譯Windows程序時需要將某些編譯優化關閉的原因之一

在許多情況下,窗口過程必須保留其從消息中獲取的信息,並在處理其他消息時使用該信息。這種信息必須保存在窗口過程所定義的靜態變量中,或保存在全局變量中。

搶佔式多任務環境

Windows 98 和WindowsNT都是搶佔式多任務環境。這就意味着當一個程序完成一項非常耗時的工作時,Windows允許用戶將控制權切換給其他程序。這是一件非常好的事,而這也體現了當前版本的Windows相對於以前的16位版本的優越性。

但是,由於Windows的構建方式,這種搶佔式多任務模式未必總會按照你期望的方式工作。例如,假定你的程序處理某條特定消息需要花費一至兩分鐘的時間。的確,在此期間,用戶可以切換到其他程序。但是用戶無法對你的程序做任何操作。用戶不能對你的程序窗口進行移動,也不能對窗口採取調整尺寸、最小化或關閉等操作,什麼都不能做。這是因爲你的窗口過程正在執行一項非常耗時的任務。表面上看起來窗口過程並沒有執行移動或尺寸調整的操作,但實際上它確實在這樣做。這部分工作由DefWindowProc函數負責,在定義窗口過程時必須將其考慮在內。

如果程序在處理某個特定消息時需要執行耗時的操作,可採用第20章將介紹的一種更禮貌的方法。即便在一個搶佔式多任務的環境中,讓你的程序一直霸佔着屏幕也不值得提倡,因爲這會給用戶平添無謂的煩惱。實際上,這樣的程序所造成的用戶煩惱與因程序缺陷、行爲異常以及幫助文件不完整而產生的麻煩沒有什麼兩樣。所以,請不要給用戶製造不必要的麻煩,並儘快從消息中返回。

基礎知識和Unicode寬字符

1.WINDEF.H基本數據類型定義。
WINNT.H支持Unicode的類型定義。
WINBASE.H 內核函數。
WINUSER.H 用戶界面函數。
WINGDl.H圖形設備接口函數。
這些頭文件定義了Windows的所有數據類型、函數調用、數據結構以及常量標識符。
它們在Windows文檔中佔有至關重要的地位。你既可以通過Visual C++Developer Studio的Edit菜單中的Find in Files選項來非常方便地搜索這些頭文件,也可以在Developer Studio中打開這些頭文件,直接翻閱它們的內容。
2.絕大部分Windows程序在變量命名上都採用所謂“匈牙利標記法”(Hungarian Notation)系統。在這一系統中,變量名前都有一個短前綴,用以表明該變量的數據類型。
3.

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, 
	int iCmdShow)
{
	MessageBox(NULL, TEXT("Hello, Windows 98!"), TEXT("HelloMsg"), 0);
	return 0;
}

HINSTANCE 句柄,標識某些東西
HINSTANCE 是否有多個實例
LPSTR lpCmdLine 運行程序的命令行
int nshowcmd 指明程序如何顯示
4.MessageBox函數(顯示短信息)
第一個參數是窗口句柄;第二個參數是將要在信息框中出現的文本字符串;第三個參數是要在標題欄上出現的文本字符串;第四個參數是MB_打頭的常量組合。
#define MB_OK0X00000000L
#define MB_OKCANCEL0X00000001L
#define MB_ABORTRETRYIGNORE 0×00000002L
#define MB_YESNOCANCEL0X00000003L
#define MB_YESNO0X00000004L
#define MB_RETRYCANCEL0X00000005L
5.在字符前面加“L”表示爲寬字符
6.寬字符並不一定是Unicode,Unicode只是寬字符編碼的一種實現。
7.TCHAR.H頭文件用來解決編譯ASCII或者Unicode的問題
8.如果包含了TCHAR.H頭文件並且_UNICODE標識符被定義了,那麼_tcslen就被定義爲wcslen;反之_tcslen就被定義爲strlen
9.如果_UNICODE標識符被定義了,TCHAR就是wchar_t:
typedef wchar_t TCHAR;否則的話,TCHAR就是一個簡單的char:
10.CHAR和WCHAR是寫Windows程序時推薦使用的數據類型,它們分別用於定義8位或者16位的字符。WCHAR定義後面的註釋wc,是建議使用匈牙利標記法來說明這是一個基於WCHAR數據類型的變量,即這是一個寬字符。

匈牙利標記法

匈牙利標記法是一種變量前綴的規範,如下表所示:
在這裏插入圖片描述

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