跟我一起學圖形編程
作者:姚明 聯(lián)系方式:alanvincentmail@gmail.com 2011年1月22日 18:46:14
歡迎使用我的圖形學教程。我是計算機專業(yè)的學生,對圖形圖像技術(shù)有濃厚的興趣,就讀期間廣泛的涉及相關(guān)知識,但始終沒有深入研究。原因很簡單,我認為廣度可以決定深度,大學期間應博學,不宜專于細節(jié)。現(xiàn)在畢業(yè)了,我選擇了圖形學作為自己深入研究的方向。
關(guān)于圖形學,我也算是初學者,也許,與大家不同的是,在深入研究之前,我做了充分的知識準備,我計劃從點的繪制開始,實現(xiàn)一套完整的三維渲染流水線。朋友們,跟我一起學吧,讓我們披荊斬棘,一起攀上這座美麗的高峰!
下面,開始我們的第一步,創(chuàng)建一個windows下的窗口程序。
理論:
Windows 程序中,在寫圖形用戶界面時需要調(diào)用大量的標準 Windows Gui 函數(shù)。其實這對用戶和程序員來說都有好處,對于用戶,面對的是同一套標準的窗口,對這些窗口的操作都是一樣的,所以使用不同的應用程序時無須重新學習操作。對程序員來說,這些 Gui 源代碼都是經(jīng)過了微軟的嚴格測試,隨時拿來就可以用的。當然至于具體地寫程序?qū)τ诔绦騿T來說還是有難度的。為了創(chuàng)建基于窗口的應用程序,必須嚴格遵守規(guī)范。作到這一點并不難,只要用模塊化或面向?qū)ο蟮木幊谭椒纯伞?/span>
下面我就列出在桌面顯示一個窗口的幾個步驟:
- 得到您應用程序的句柄(必需);
- 得到命令行參數(shù)(如果您想從命令行得到參數(shù),可選);
- 注冊窗口類(必需,除非您使用 Windows 預定義的窗口類,如 MessageBox 或 dialog box;
- 產(chǎn)生窗口(必需);
- 在桌面顯示窗口(必需,除非您不想立即顯示它);
- 刷新窗口客戶區(qū);
- 進入無限的獲取窗口消息的循環(huán);
- 如果有消息到達,由負責該窗口的窗口回調(diào)函數(shù)處理;
- 如果用戶關(guān)閉窗口,進行退出處理。
相對于單用戶的 DOS 下的編程來說,Windows 下的程序框架結(jié)構(gòu)是相當復雜的。但是 Windows 和 DOS 在系統(tǒng)架構(gòu)上是截然不同的。Windows 是一個多任務的操作系統(tǒng),故系統(tǒng)中同時有多個應用程序彼此協(xié)同運行。這就要求 Windows 程序員必須嚴格遵守編程規(guī)范,并養(yǎng)成良好的編程風格。
Windows 中的文本是一個GUI(圖形用戶界面)對象。每一個字符實際上是由許多的像素點組成,這些點在有筆畫的地方顯示出來,這樣就會出現(xiàn)字符。這也是為什么我說“繪制”字符,而不是寫字符。通常您都是在您應用程序的客戶區(qū)“繪制”字符串(盡管您也可以在客戶區(qū)外“繪制”)。Windows 下的“繪制”字符串方法和 Dos 下的截然不同,在 Dos 下,您可以把屏幕想象成 85 x 25 的一個平面,而 Windows 下由于屏幕上同時有幾個應用程序的畫面,所以您必須嚴格遵從規(guī)范。Windows 通過把每一個應用程序限制在他的客戶區(qū)來做到這一點。當然客戶區(qū)的大小是可變的,您隨時可以調(diào)整。
在您在客戶區(qū)“繪制”字符串前,您必須從 Windows 那里得到您客戶區(qū)的大小,確實您無法像在 DOS 下那樣隨心所欲地在屏幕上任何地方“繪制”,繪制前您必須得到 Windows 的允許,然后 Windows 會告訴您客戶區(qū)的大小,字體,顏色和其它 GUI 對象的屬性。您可以用這些來在客戶區(qū)“繪制”。
什么是“設備環(huán)境”(DC)呢? 它其實是由 Windows 內(nèi)部維護的一個數(shù)據(jù)結(jié)構(gòu)。一個“設備環(huán)境”和一個特定的設備相連。像打印機和顯示器。對于顯示器來說,“設備環(huán)境”和一個個特定的窗口相連。
“設備環(huán)境”中的有些屬性和繪圖有關(guān),像:顏色,字體等。您可以隨時改動那些缺省值,之所以保存缺省值是為了方便。您可以把“設備環(huán)境”想象成是Windows 為您準備的一個繪圖環(huán)境,而您可以隨時根據(jù)需要改變某些缺省屬性。
當應用程序需要繪制時,您必須得到一個“設備環(huán)境”的句柄。通常有幾種方法。
- 在 WM_PAINT 消息中使用 call BeginPaint
- 在其他消息中使用 call GetDC
- call CreateDC 建立你自己的 DC
您必須牢記的是,在處理單個消息后你必須釋放“設備環(huán)境”句柄。不要在一個消息處理中獲得 “設備環(huán)境”句柄,而在另一個消息處理中在釋放它。
我們在Windows 發(fā)送 WM_PAINT 消息時處理繪制客戶區(qū),Windows 不會保存客戶區(qū)的內(nèi)容,它用的是方法是“重繪”機制(譬如當客戶區(qū)剛被另一個應用程序的客戶區(qū)覆蓋),Windows 會把 WM_PAINT 消息放入該應用程序的消息隊列。重繪窗口的客戶區(qū)是各個窗口自己的責任,您要做的是在窗口過程處理 WM_PAINT 的部分知道繪制什么和何如繪制。
您必須了解的另一個概念是“無效區(qū)域”。Windows 把一個最小的需要重繪的正方形區(qū)域叫做“無效區(qū)域”。當 Windows 發(fā)現(xiàn)了一個”無效區(qū)域“后,它就會向該應用程序發(fā)送一個 WM_PAINT 消息,在 WM_PAINT 的處理過程中,窗口首先得到一個有關(guān)繪圖的結(jié)構(gòu)體,里面包括無效區(qū)的坐標位置等。您可以通過調(diào)用BeginPaint 讓“無效區(qū)”有效,如果您不處理 WM_PAINT 消息,至少要調(diào)用缺省的窗口處理函數(shù) DefWindowProc ,或者調(diào)用 ValidateRect 讓“無效區(qū)”有效。否則您的應用程序?qū)盏綗o窮無盡的 WM_PAINT 消息。
下面是響應該消息的步驟:
- 取得“設備環(huán)境”句柄
- 繪制客戶區(qū)
- 釋放“設備環(huán)境”句柄
注意,您無須顯式地讓“無效區(qū)”有效,這個動作由 BeginPaint 自動完成。您可以在 BeginPaint 和 Endpaint 之間,調(diào)用所有的繪制函數(shù)。幾乎所有的GDI 函數(shù)都需要“設備環(huán)境”的句柄作為參數(shù)。
內(nèi)容:
下面是我們簡單的窗口程序的源代碼。在進入復雜的細節(jié)前,我將提綱挈領地指出幾點要點:
- 您應當把程序中要用到的所有常量和結(jié)構(gòu)體的聲明放到一個頭文件中,并且在源程序的開始處包含這個頭文件。這么做將會節(jié)省您大量的時間,也免得一次又一次的敲鍵盤。您也可以定義您自己的常量和結(jié)構(gòu)體,但最好把它們放到獨立的頭文件中。
- 在其它地方運用頭文件中定義函數(shù)原型,常數(shù)和結(jié)構(gòu)體時,要嚴格保持和頭文件中的定義一致,包括大小寫。在查詢函數(shù)定義時,這將節(jié)約您大量的時間;
- 如果想詳細系統(tǒng)的學習Windows編程,可以參考《windows程序設計》,點擊下載。
- 如果你的系統(tǒng)中沒有安裝VC, 或不會使用VC,我準備了精簡版本,只有10MB左右,無需復雜操作,也能編譯本教程中的代碼,生成程序,點擊下載。
- 如果你對下面的代碼視如天書,那么請先看《譚浩強C語言教程》,務必熟讀,點擊下載。
1
/**//*------------------------------------------------------------------------
2
HELLOWIN.CPP -- Displays "你好, 歡迎使用YM的圖形學教程!" in client area
3
(c) Charles Petzold, 1998
4
-----------------------------------------------------------------------*/
5
#include <windows.h>
6
7
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
8
9
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
10
PSTR szCmdLine, int iCmdShow)
11

{
12
static TCHAR szAppName[] = TEXT ("HelloWin") ;
13
HWND hwnd ;
14
MSG msg ;
15
WNDCLASS wndclass ;
16
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
17
wndclass.lpfnWndProc = WndProc ;
18
wndclass.cbClsExtra = 0 ;
19
wndclass.cbWndExtra = 0 ;
20
wndclass.hInstance = hInstance ;
21
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
22
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
23
wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
24
wndclass.lpszMenuName = NULL ;
25
wndclass.lpszClassName= szAppName ;
26
RegisterClass (&wndclass);
27
hwnd = CreateWindow( szAppName, // window class name
28
TEXT ("The Hello Program"), // window caption
29
WS_OVERLAPPEDWINDOW, // window style
30
CW_USEDEFAULT,// initial x position
31
CW_USEDEFAULT,// initial y position
32
CW_USEDEFAULT,// initial x size
33
CW_USEDEFAULT,// initial y size
34
NULL, // parent window handle
35
NULL, // window menu handle
36
hInstance, // program instance handle
37
NULL) ; // creation parameters
38
ShowWindow (hwnd, iCmdShow) ;
39
UpdateWindow (hwnd) ;
40
while (GetMessage (&msg, NULL, 0, 0))
41
{
42
TranslateMessage (&msg) ;
43
DispatchMessage (&msg) ;
44
}
45
return msg.wParam ;
46
}
47
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
48

{
49
HDC hdc ;
50
PAINTSTRUCT ps ;
51
RECT rect ;
52
switch (message)
53
{
54
case WM_PAINT:
55
hdc = BeginPaint (hwnd, &ps) ;
56
GetClientRect (hwnd, &rect) ;
57
DrawText (hdc, TEXT ("你好, 歡迎使用YM的圖形學教程! "), -1, &rect,
58
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
59
EndPaint (hwnd, &ps) ;
60
return 0 ;
61
case WM_DESTROY:
62
PostQuitMessage (0) ;
63
return 0 ;
64
}
65
return DefWindowProc (hwnd, message, wParam, lParam) ;
66
}
67
分析:
看到一個簡單的 Windows 程序有這么多行,您是不是有點想撤? 但是您必須要知道的是上面的大多數(shù)代碼都是模板而已,模板的意思即是指這些代碼對差不多所有標準 Windows 程序來說都是相同的。在寫 Windows 程序時您可以把這些代碼拷來拷去,當然把這些重復的代碼寫到一個庫中也挺好。其實真正要寫的代碼集中在 WinMain 中。這和一些 C 編譯器一樣,無須要關(guān)心其它雜務,集中精力于 WinMain 函數(shù)。唯一不同的是 C 編譯器要求您的源代碼有必須有一個函數(shù)叫 WinMain。否則 C 無法知道將哪個函數(shù)和有關(guān)的前后代碼鏈接。相對C,匯編語言提供了較大的靈活性,它不強行要求一個叫 WinMain 的函數(shù)。
下面我們開始分析,您可得做好思想準備,這可不是一件太輕松的活。
HELLOWIN至少呼叫了18個Windows函數(shù)。下面以它們在HELLOWIN中出現(xiàn)的次序列出這些函數(shù)以及各自的簡明描述:
- LoadIcon 加載圖標供程序使用。
- LoadCursor 加載鼠標光標供程序使用。
- GetStockObject 取得一個圖形對象(在這個例子中,是取得繪制窗口背景的畫刷對象)。
- RegisterClass 為程序窗口注冊窗口類別。
- MessageBox 顯示消息框。
- CreateWindow 根據(jù)窗口類別建立一個窗口。
- ShowWindow 在屏幕上顯示窗口。
- UpdateWindow 指示窗口自我更新。
- GetMessage 從消息隊列中取得消息。
- TranslateMessage 轉(zhuǎn)譯某些鍵盤消息。
- DispatchMessage 將消息發(fā)送給窗口消息處理程序。
- PlaySound 播放一個聲音文件。
- BeginPaint 開始繪制窗口。
- GetClientRect 取得窗口顯示區(qū)域的大小。
- DrawText 顯示字符串。
- EndPaint 結(jié)束繪制窗口。
- PostQuitMessage 在消息隊列中插入一個「退出程序」消息。
- DefWindowProc 執(zhí)行內(nèi)定的消息處理。
這些函數(shù)均在Platform SDK文件中說明,并在不同的表頭文件中聲明,其中絕大多數(shù)聲明在WINUSER.H中。
WinMain函數(shù)共有4個參數(shù):應用程序的實例句柄,該應用程序的前一實例句柄,命令行參數(shù)串指針和窗口如何顯示。Win32 沒有前一實例句柄的概念,所以第二個參數(shù)總為0。之所以保留它是為了和 Win16 兼容的考慮,在 Win16下,如果 hPrevInst 是 NULL,則該函數(shù)是第一次運行。特別注意:您不用必須申明一個名為 WinMain 函數(shù),事實上在這方面您可以完全作主,您甚至無須有一個和 WinMain 等同的函數(shù)。您只要把 WinMain 中的代碼拷到GetCommandLine 之后,其所實現(xiàn)的功能完全相同。在 WinMain 返回時,產(chǎn)生一個返回值。然后在應用程序結(jié)束時通過 ExitProcess 函數(shù)把該返回碼傳遞給 Windows 。
1
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
2
wndclass.lpfnWndProc = WndProc ;
3
wndclass.cbClsExtra = 0 ;
4
wndclass.cbWndExtra = 0 ;
5
wndclass.hInstance = hInstance ;
6
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
7
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
8
wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
9
wndclass.lpszMenuName = NULL ;
10
wndclass.lpszClassName= szAppName ;
11
RegisterClass (&wndclass);
12
上面幾行從概念上說確實是非常地簡單。只要幾行指令就可以實現(xiàn)。其中的主要概念就是窗口類(window class),一個窗口類就是一個有關(guān)窗口的規(guī)范,這個規(guī)范定義了幾個主要的窗口的元素,如:圖標、光標、背景色、和負責處理該窗口的函數(shù)。您產(chǎn)生一個窗口時就必須要有這樣的一個窗口類。如果您要產(chǎn)生不止一個同種類型的窗口時,最好的方法就是把這個窗口類存儲起來,這種方法可以節(jié)約許多的內(nèi)存空間。也許今天您不會太感覺到,可是想想以前 PC 大多數(shù)只有 1M 內(nèi)存時,這么做是非常有必要的。如果您要定義自己的創(chuàng)建窗口類就必須:在一個 WINDCLASS 或 WINDOWCLASSEX 結(jié)構(gòu)體中指明您窗口的組成元素,然后調(diào)用 RegisterClass 或 RegisterClass ,再根據(jù)該窗口類產(chǎn)生窗口。對不同特色的窗口必須定義不同的窗口類。 WINDOWS有幾個預定義的窗口類,譬如:按鈕、編輯框等。要產(chǎn)生該種風格的窗口無須預先再定義窗口類了,只要包預定義類的類名作為參數(shù)調(diào)用 CreateWindow 即可。
WNDCLASS 中最重要的成員莫過于lpfnWndProc了。前綴 lpfn 表示該成員是一個指向函數(shù)的長指針。在 Win32中由于內(nèi)存模式是 FLAT 型,所以沒有 near 或 far 的區(qū)別。每一個窗口類必須有一個窗口過程,當 Windows 把屬于特定窗口的消息發(fā)送給該窗口時,該窗口的窗口類負責處理所有的消息,如鍵盤消息或鼠標消息。由于窗口過程差不多智能地處理了所有的窗口消息循環(huán),所以您只要在其中加入消息處理過程即可。下面我將要講解 WNDCLASSEX 的每一個成員
1
typedef struct tagWNDCLASS
{
2
UINT style;
3
WNDPROC lpfnWndProc;
4
int cbClsExtra;
5
int cbWndExtra;
6
HINSTANCE hInstance;
7
HICON hIcon;
8
HCURSOR hCursor;
9
HBRUSH hbrBackground;
10
LPCTSTR lpszMenuName;
11
LPCTSTR lpszClassName;
12
} WNDCLASS, *PWNDCLASS;
13
14
CreateWindow( szAppName, // window class name
15
TEXT ("The Hello Program"), // window caption
16
WS_OVERLAPPEDWINDOW, // window style
17
CW_USEDEFAULT,// initial x position
18
CW_USEDEFAULT,// initial y position
19
CW_USEDEFAULT,// initial x size
20
CW_USEDEFAULT,// initial y size
21
NULL, // parent window handle
22
NULL, // window menu handle
23
hInstance, // program instance handle
24
NULL) ; // creation parameters
25
注冊窗口類后,我們將調(diào)用CreateWindow來產(chǎn)生實際的窗口。請注意該函數(shù)有11個參數(shù)。
1
HWND WINAPI CreateWindow(
2
__in_opt LPCTSTR lpClassName,
3
__in_opt LPCTSTR lpWindowName,
4
__in DWORD dwStyle,
5
__in int x,
6
__in int y,
7
__in int nWidth,
8
__in int nHeight,
9
__in_opt HWND hWndParent,
10
__in_opt HMENU hMenu,
11
__in_opt HINSTANCE hInstance,
12
__in_opt LPVOID lpParam
13
);
14
我們來仔細看一看這些的參數(shù):
- lpClassName:(必須)。ASCIIZ形式的窗口類名稱的地址。可以是您自定義的類,也可以是預定義的類名。像上面所說,每一個應用程序必須有一個窗口類。
- lpWindowName:ASCIIZ形式的窗口名稱的地址。該名稱會顯示在標題條上。如果該參數(shù)空白,則標題條上什么都沒有。
- dwStyle:窗口的風格。在此您可以指定窗口的外觀。可以指定該參數(shù)為零,但那樣該窗口就沒有系統(tǒng)菜單,也沒有最大化和最小化按鈕,也沒有關(guān)閉按鈕,那樣您不得不按Alt+F4 來關(guān)閉它。最為普遍的窗口類風格是 WS_OVERLAPPEDWINDOW。 一種窗口風格是一種按位的掩碼,這樣您可以用“or”把您希望的窗口風格或起來。像 WS_OVERLAPPEDWINDOW 就是由幾種最為不便普遍的風格或起來的。
- X,Y: 指定窗口左上角的以像素為單位的屏幕坐標位置。缺省地可指定為 CW_USEDEFAULT,這樣 Windows 會自動為窗口指定最合適的位置。
- nWidth, nHeight: 以像素為單位的窗口大小。缺省地可指定為 CW_USEDEFAULT,這樣 Windows 會自動為窗口指定最合適的大小。
- hWndParent: 父窗口的句柄(如果有的話)。這個參數(shù)告訴 Windows 這是一個子窗口和他的父窗口是誰。這和 MDI(多文檔結(jié)構(gòu))不同,此處的子窗口并不會局限在父窗口的客戶區(qū)內(nèi)。他只是用來告訴 Windows 各個窗口之間的父子關(guān)系,以便在父窗口銷毀是一同把其子窗口銷毀。在我們的例子程序中因為只有一個窗口,故把該參數(shù)設為 NULL。
- hMenu: WINDOWS菜單的句柄。如果只用系統(tǒng)菜單則指定該參數(shù)為NULL。回頭看一看WNDCLASSEX 結(jié)構(gòu)中的 lpszMenuName 參數(shù),它也指定一個菜單,這是一個缺省菜單,任何從該窗口類派生的窗口若想用其他的菜單需在該參數(shù)中重新指定。其實該參數(shù)有雙重意義:一方面若這是一個自定義窗口時該參數(shù)代表菜單句柄,另一方面,若這是一個預定義窗口時,該參數(shù)代表是該窗口的 ID 號。Windows 是根據(jù)lpClassName 參數(shù)來區(qū)分是自定義窗口還是預定義窗口的。
- hInstance: 產(chǎn)生該窗口的應用程序的實例句柄。
- lpParam: (可選)指向欲傳給窗口的結(jié)構(gòu)體數(shù)據(jù)類型參數(shù)的指針。如在MDI中在產(chǎn)生窗口時傳遞 CLIENTCREATESTRUCT 結(jié)構(gòu)的參數(shù)。一般情況下,該值總為零,這表示沒有參數(shù)傳遞給窗口。可以通過GetWindowLong 函數(shù)檢索該值。
1
ShowWindow (hwnd, iCmdShow) ;
2
UpdateWindow (hwnd) ;
調(diào)用CreateWindow成功后,會返回窗口句柄。我們必須保存該值以備后用。我們剛剛產(chǎn)生的窗口不會自動顯示,所以必須調(diào)用 ShowWindow 來按照我們希望的方式來顯示該窗口。接下來調(diào)用 UpdateWindow 來更新客戶區(qū)。
1
while (GetMessage (&msg, NULL, 0, 0))
2

{
3
TranslateMessage (&msg) ;
4
DispatchMessage (&msg) ;
5
}
這時候我們的窗口已顯示在屏幕上了。但是它還不能從外界接收消息。所以我們必須給它提供相關(guān)的消息。我們是通過一個消息循環(huán)來完成該項工作的。每一個模塊僅有一個消息循環(huán),我們不斷地調(diào)用 GetMessage 從 Windows 中獲得消息。GetMessage 傳遞一個 MSG 結(jié)構(gòu)體給 Windows ,然后 Windows 在該函數(shù)中填充有關(guān)的消息,一直到 Windows 找到并填充好消息后 GetMessage 才會返回。在這段時間內(nèi)系統(tǒng)控制權(quán)可能會轉(zhuǎn)移給其他的應用程序。這樣就構(gòu)成了Win16 下的多任務結(jié)構(gòu)。如果 GetMessage 接收到 WM_QUIT 消息后就會返回 FALSE,使循環(huán)結(jié)束并退出應用程序。TranslateMessage 函數(shù)是一個是實用函數(shù),它從鍵盤接受原始按鍵消息,然后解釋成 WM_CHAR,在把 WM_CHAR 放入消息隊列,由于經(jīng)過解釋后的消息中含有按鍵的 ASCII 碼,這比原始的掃描碼好理解得多。如果您的應用程序不處理按鍵消息的話,可以不調(diào)用該函數(shù)。DispatchMessage 會把消息發(fā)送給負責該窗口過程的函數(shù)。
1
return msg.wParam
如果消息循環(huán)結(jié)束了,退出碼存放在 MSG 中的 wParam中,您可以通過把它放到 eax 寄存器中傳給 Windows目前 Windows 沒有利用到這個結(jié)束碼,但我們最好還是遵從 Windows 規(guī)范已防意外。
1
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
是我們的窗口處理函數(shù)。您可以隨便給該函數(shù)命名。其中第一個參數(shù) hWnd 是接收消息的窗口的句柄。message是接收的消息。注意message不是一個 MSG 結(jié)構(gòu),其實上只是一個 DWORD 類型數(shù)。Windows 定義了成百上千個消息,大多數(shù)您的應用程序不會處理到。當有該窗口的消息發(fā)生時,Windows 會發(fā)送一個相關(guān)消息給該窗口。其窗口過程處理函數(shù)會智能的處理這些消息。wParam 和 lParam 只是附加參數(shù),以方便傳遞更多的和該消息有關(guān)的數(shù)據(jù)。
1
switch (message)
2

{
3
case WM_PAINT:
4
hdc = BeginPaint (hwnd, &ps) ;
5
GetClientRect (hwnd, &rect) ;
6
DrawText (hdc, TEXT ("你好, 歡迎使用YM的圖形學教程!"), -1, &rect,
7
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
8
EndPaint (hwnd, &ps) ;
9
return 0 ;
10
case WM_DESTROY:
11
PostQuitMessage (0) ;
12
return 0 ;
13
}
14
return DefWindowProc (hwnd, message, wParam, lParam) ;
15
上面可以說是關(guān)鍵部分。這也是我們寫 Windows 程序時需要改寫的主要部分。此處您的程序檢查 Windows 傳遞過來的消息,如果是我們感興趣的消息則加以處理,處理完后,在 eax 寄存器中傳遞 0,否則必須調(diào)用 DefWindowProc,把該窗口過程接收到的參數(shù)傳遞給缺省的窗口處理函數(shù)。所有消息中您必須處理的是 WM_DESTROY,當您的應用程序結(jié)束時 Windows 把這個消息傳遞進來,當您的應用程序解說到該消息時它已經(jīng)在屏幕上消失了,這僅是通知您的應用程序窗口已銷毀,您必須自己準備返回 Windows 。在此消息中您可以做一些清理工作,但無法阻止退出應用程序。如果您要那樣做的話,可以處理 WM_CLOSE 消息。在處理完清理工作后,您必須調(diào)用 PostQuitMessage,該函數(shù)會把 WM_QUIT 消息傳回您的應用程序,而該消息會使得 GetMessage 返回,并在將返回值設置0,然后會結(jié)束消息循環(huán)并退回 WINDOWS。您可以在您的程序中調(diào)用 DestroyWindow 函數(shù),它會發(fā)送一個 WM_DESTROY 消息給您自己的應用程序,從而迫使它退出。
特別注意:下面討論的是我們以后的課程的核心部分
1
HDC hdc ;
2
PAINTSTRUCT ps ;
3
RECT rect ;
4
這些局部變量由處理 WM_PAINT 消息中的 GDI 函數(shù)調(diào)用。hdc 用來存放調(diào)用 BeginPaint 返回的“設備環(huán)境”句柄。ps 是一個 PAINTSTRUCT 數(shù)據(jù)類型的變量。通常您不會用到其中的許多值,它由 Windows 傳遞給 BeginPaint,在結(jié)束繪制后再原封不動的傳遞給 EndPaint。rect 是一個 RECT 結(jié)構(gòu)體類型參數(shù),它的定義如下:
1
typedef struct _RECT
{
2
LONG left;
3
LONG top;
4
LONG right;
5
LONG bottom;
6
} RECT, *PRECT;
7
left 和 top 是正方形左上角的坐標。right 和 bottom 是正方形右下角的坐標。客戶區(qū)的左上角的坐標是 x=0,y=0,這樣對于 x=0,y=10 的坐標點就在它的下面。
1
hdc = BeginPaint (hwnd, &ps) ;
2
GetClientRect (hwnd, &rect) ;
3
DrawText (hdc, TEXT ("你好, 歡迎使用YM的圖形學教程!"), -1, &rect,
4
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
5
EndPaint (hwnd, &ps) ;
6
在處理 WM_PAINT 消息時,您調(diào)用BeginPaint函數(shù),傳給它一個窗口句柄和未初始化的 PAINTSTRUCT 型參數(shù)。調(diào)用成功后在 eax 中返回“設備環(huán)境”的句柄。下一次,調(diào)用 GetClientRect 以得到客戶區(qū)的大小,大小放在 rect 中,然后把它傳給 DrawText。DrawText 的語法如下:
1
int DrawText(
2
HDC hDC,
3
LPCTSTR lpString,
4
int nCount,
5
LPRECT lpRect,
6
UNIT uFormat);
7
DrawText是一個高層的調(diào)用函數(shù)。它能自動處理像換行、把文本放到客戶區(qū)中間等這些雜事。所以您只管集中精力“繪制”字符串就可以了。我們會在下一課中講解低一層的函數(shù) TextOut,該函數(shù)在一個正方形區(qū)域中格式化一個文本串。它用當前選擇的字體、顏色和背景色。它處理換行以適應正方形區(qū)域。它會返回以設備邏輯單位度量的文本的高度,我們這里的度量單位是像素點。讓我們來看一看該函數(shù)的參數(shù):
- hdc: “設備環(huán)境”的句柄。
- lpString:要顯示的文本串,該文本串要么以NULL結(jié)尾,要么在nCount中指出它的長短。
- nCount:要輸出的文本的長度。若以NULL結(jié)尾,該參數(shù)必須是-1。
- lpRect: 指向要輸出文本串的正方形區(qū)域的指針,該方形必須是一個裁剪區(qū),也就是說超過該區(qū)域的字符將不能顯示。
- uFormat:指定如何顯示。我們可以用 or 把以下標志或到一塊:
- DT_SINGLELINE:是否單行顯示。
- DT_CENTER:是否水平居中。
- DT_VCENTER :是否垂直居中。
結(jié)束繪制后,必須調(diào)用 EndPaint 釋放“設備環(huán)境”的句柄。好了,現(xiàn)在我們把“繪制”文本串的要點總結(jié)如下:
- 必須在開始和結(jié)束處分別調(diào)用 BeginPaint 和 EndPaint;
- 在 BeginPaint 和 EndPaint 之間調(diào)用所有的繪制函數(shù);
- 如果在其它的消息處理中重新繪制客戶區(qū),您可以有兩種選擇:
(1)用GetDC和ReleaseDC代替BeginPaint和EndPaint;
(2)調(diào)用InvalidateRect或UpdateWindow讓客戶區(qū)無效,這將迫使WINDOWS把WM_PAINT放入應用程序消息隊列,從而使得客戶區(qū)重繪。
posted on 2011-01-22 14:49
姚明 閱讀(1214)
評論(0) 編輯 收藏 引用 所屬分類:
原創(chuàng)教程