基本的Windows應用程序 窗體創建


基本的Windows應用程序

 

轉載:http://shiba.hpe.sh.cn/jiaoyanzu/WULI/Article1506

 

下面是一個完全可以運行的Windows程序,代碼很簡單,讀者通過代碼中的注釋了解它們的含義。我們將在下一節詳細講解些代碼。做為一個練習,我們建議讀者在你的開發工具中創建一個工程,手工輸入些代碼,然后編譯運行這個程序。注意,如果你使用的是Visual C++,那么在選擇工程類型時必須是“Win32 application project”,而不能是“Win32 console application project”。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// 包含Windows頭文件;這個文件中包含所有Win32 API聲明的
// 結構體、類型和函數,是使用Win32 API必須的基本條件。
#include <windows.h>
// 主窗口句柄;用於標示創建的窗體。
HWND ghMainWnd = 0;
// 封裝了初始化一個Windows應用程序的代碼。如果初始化成功則返回true,
// 否則則返回false。
bool InitWindowsApp( HINSTANCE instanceHandle, int show);
// 封裝了消息循環的代碼
int    Run();
// 消息處理函數用於處理窗口收到的消息。
LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
 
// 在Windows中,WinMain相當於普通C++編程中的main函數
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR pCmdLine, int nShowCmd)
{
     // 首先調用InitWindowsApp函數創建並初始化主應用程序窗口,
     // 該函數的參數是hInstance和nShowCmd。
     if (!InitWindowsApp(hInstance, nShowCmd))
         return 0;
     // 之后進入消息循環,直到接收到WM_QUIT消息后才退出循環。
     return Run();
}
 
bool InitWindowsApp( HINSTANCE instanceHandle, int show)
{
     // 首先填寫一個WNDCLASS結構體,描述窗口的基本屬性。
     WNDCLASS wc;
     wc.style          = CS_HREDRAW | CS_VREDRAW;
     wc.lpfnWndProc    = WndProc;
     wc.cbClsExtra      = 0;
     wc.cbWndExtra      = 0;
     wc.hInstance      = instanceHandle;
     wc.hIcon          = LoadIcon(0, IDI_APPLICATION);
     wc.hCursor        = LoadCursor(0, IDC_ARROW);
     wc.hbrBackground = ( HBRUSH )GetStockObject(WHITE_BRUSH);
     wc.lpszMenuName    = 0;
     wc.lpszClassName = L "BasicWndClass" ;
     // 然后注冊這個WNDCLASS示例,這樣就
     //  可以基於它創建一個窗口。
     if (!RegisterClass(&wc))
     {
         MessageBox(0, L "RegisterClass FAILED" ,0, 0);
         return false ;
     }
     // 注冊了WNDCLASS示例后,我們就可以使用CreateWindow方法創建一個窗口。
     // 這個方法返回一個指向新創建的窗口的句柄(HWND)。如果創建失敗,這個
     // 句柄為零。我們使用這個HWND變量引用由Windows維護的主程序窗口。
     // 我們必須保存這個窗口句柄,因為許多API函數都要求傳入窗口句柄,明確所要操縱的窗口對象。
     ghMainWnd = CreateWindow(
         L "BasicWndClass" ,      // Registered WNDCLASS instance to use.
         L "Win32Basic" ,          // window title
         WS_OVERLAPPEDWINDOW,    // style flags
         CW_USEDEFAULT,          // x-coordinate
         CW_USEDEFAULT,          // y-coordinate
         CW_USEDEFAULT,          // width
         CW_USEDEFAULT,          // height
         0,                      // parent window
         0,                      // menu handle
         instanceHandle,        // app instance
         0);                    // extra creationparameters
     if (ghMainWnd == 0)
     {
         MessageBox(0, L "CreateWindow FAILED" , 0, 0);
         return false ;
     }
     // 最后一步是使用下面的兩個函數顯示並更新新創建的窗口。
     // 將剛剛創建的窗口句柄傳遞給這兩個函數,使Windows知道要顯示和更新哪個窗口。
     ShowWindow(ghMainWnd, show);
     UpdateWindow(ghMainWnd);
     return true ;
}
 
int Run()
{
     MSG msg = {0};
     // 直到接收到WM_QUIT消息才會退出循環。當收到一個WM_QUIT消息時,
     // GetMessage函數會返回0,並退出循環。當出現錯誤時,
     // GetMessage函數會返回−1。注意,若沒有收到消息,
     // GetMessage方法會讓線程進入休眠狀態。
     BOOL bRet = 1;
     while ( (bRet = GetMessage(&msg, 0, 0, 0)) != 0)
     {
         if (bRet == -1)
         {
             MessageBox(0, L "GetMessage FAILED" ,L "Error" , MB_OK);
             break ;
         }
         else
         {
             TranslateMessage(&msg);
             DispatchMessage(&msg);
         }
     }
     return ( int )msg.wParam;
}
 
LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
     // 處理指定的消息。注意,若自己處理消息,就必須返回0。
     switch ( msg)
     {
         // 點擊鼠標左鍵,顯示一個消息框。
         case WM_LBUTTONDOWN:
             MessageBox(0, L "Hello, World" , L "Hello" , MB_OK);
             return 0;
         // 當按下Escape時,會銷毀主程序窗口
         case WM_KEYDOWN:
             if ( wParam == VK_ESCAPE)
                 DestroyWindow(ghMainWnd);
             return 0;
         // 接收到WM_DESTROY消息后,發送退出信息,終止消息循環。
         case WM_DESTROY:
             PostQuitMessage(0);
             return 0;
     }
     // 將前面未處理的其他信息發送到默認的信息處理程序。
     // 注意,這個程序返回的是DefWindowProc的返回值。
     return DefWindowProc(hWnd, msg, wParam, lParam);
}

圖3圖 A.3:上述程序的屏幕截圖。注意,當鼠標單擊窗口客戶區時會彈出一個消息框。還可以試着按下ESC鍵退出程序。

A.3 程序解析

我們將按照從上到下的順序分析些代碼,依次講解遇到的每個函數。讀者在閱讀以下幾節的過程中,可以時常參照上面列出的代碼。

A.3.1 頭文件、全局變量和函數原型

我們要做的第一件事情是包含windows.h頭文件,獲得windows.h頭文件中聲明的結構體、類型和函數,是使用Win32 API必須的基本條件。

?
1
#include <windows.h>

第二條語句定義了一個HWND類型的全局變量,該類型用於表示“窗口句柄”。在Windows編程中,我們經常使用句柄來引用那些由Windows內部維護的對象。在本例中,我們使用這個HWND變量引用由Windows維護的主程序窗口。我們必須保存這個窗口句柄,因為許多API函數都要求傳入窗口句柄,明確所要操縱的窗口對象。例如,在調用UpdateWindow函數時需要傳入一個HWND參數,指定所要刷新的窗口。如果我們不傳入窗口句柄,那該函數就無法確定要刷新哪個窗口。

?
1
HWND ghMainWnd = 0;

下面的3行是函數聲明。簡單地說,InitWindowsApp負責創建和初始化主程序窗口, Run封裝了程序的消息循環,WndProc是主窗口的消息處理函數。在隨后的幾個小節中,當我們調用些函數時,會對它們進行更為詳細的分析和說明。

?
1
2
3
bool InitWindowsApp( HINSTANCE instanceHandle, int show);
int    Run();
LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

A.3.2 WinMain

Windows中的WinMain函數與普通C++編程中的main函數的作用相同。WinMain函數的原型如下:

?
1
2
3
int WINAPI
WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR pCmdLine, int nShowCmd)

1.hInstance:當前應用程序的實例句柄。它是標識和引用當前應用程序的一種方式。記住,同一個Windows應用程序可能會並行運行多個實例(比如同時運行多個Microsoft Word實例),所以在引用某個特定實例時它非常有用。

2.hPrevInstance:該參數的值為0。它是16位Windows遺留下來的一個參數,Win32編程已不再使用。

3.pCmdLine:程序啟動時傳入的命令行參數字符串。

4.nShowCmd:指定應用程序窗口的顯示方式。一些常用的值有:SW_SHOW(以當前的尺寸和位置顯示窗口)、SW_SHOWMAXIMIZED(最大化)和SW_SHOWMINIMIZED(最小化)。要了解該參數的所有選項值,請參閱MSDN。如果WinMain函數調用成功,那么它將返回WM_QUIT消息的wParam成員。如果函數在未進入消息循環就已退出,那么它將返回0。標識符WINAPI的定義如下:

?
1
#define WINAPI __stdcall

它指定了函數的調用方式,即以何種方式處理堆棧上的函數參數。

A.3.3 WNDCLASS與注冊

在WinMain中我們調用了函數InitWindowsApp。也許你能猜到,該函數用於完成程序的初始化工作。下面讓我們進一步分析一下該函數的實現過程。InitWindowsApp返回一個bool值——當初始化成功時返回true,否則返回false。在WinMain函數中,我們為InitWindowsApp傳入了應用程序的實例句柄hInstance和窗口的顯示方式nShowCmd,這兩個參數可以從WinMain的參數列表中得到。

?
1
if (!InitWindowsApp(hInstance, nShowCmd))

要初始化一個窗口,第一步是要填寫一個WNDCLASS(window class,窗口類)結構體,描述窗口的基本屬性。該結構體的定義如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _WNDCLASS{
     UINT      style;
     WNDPROC lpfnWndProc;
     int      cbClsExtra;
     int      cbWndExtra;
     HANDLE    hInstance;
     HICON    hIcon;
     HCURSOR hCursor;
     HBRUSH    hbrBackground;
     LPCTSTR lpszMenuName;
     LPCTSTR lpszClassName;
} WNDCLASS;

1.style:指定窗口類的樣式。在本例中,我們使用了CS_HREDRAW和CS_VREDRAW的組合。這兩個二進制位(bit)標志值表明當窗口的寬度或高度發生變化時,窗口將被重繪。要了解該參數的所有選項值,請參閱MSDN。

?
1
wc.style = CS_HREDRAW | CS_VREDRAW;

2.lpfnWndProc:與WNDCLASS關聯的消息處理函數指針。窗口使用的消息處理函數完全由WNDCLASS中的這個參數決定。也就是說,如果我們使用同一個WNDCLASS來創建兩個窗口,那么這兩個窗口將使用同一個消息處理函數。反之,如果我們希望兩個窗口使用不同的消息處理函數,那我們就必須定義兩個不同的WNDCLASS實例,分別指定不同的消息處理函數。有關消息處理函數的內容請參見A.3.6 節。

?
1
wc.lpfnWndProc = WndProc;

3.cbClsExtra與cbWndExtra:用於存儲附加數據。這兩個參數通常不會用到,所以都設為0。

?
1
2
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;

4.hInstance:應用程序實例句柄。回顧前文,應用程序實例句柄是操作系統傳給WinMain函數的第1個參數。

?
1
wc.hInstance = instanceHandle;

5.hIcon:用於指定顯示在窗口標題欄上的圖標。你可以自己設計圖標,也可以使用Windows內置的幾個圖標;具體內容請參見MSDN。這里使用默認的應用程序圖標:

?
1
wc.hIcon = LoadIcon(0, IDI_APPLICATION);

6.hCursor:用於指定鼠標位於窗口客戶區時顯示的光標。與hIcon類似,你可以自己設計光標,也可以使用Windows 內置的幾個光標;具體內容請參見MSDN。這里使用標准的“箭頭”光標:

?
1
wc.hCursor = LoadCursor(0, IDC_ARROW);

7.hbrBackground:用於指定窗口客戶區的背景色。在本例中,我們調用Win32函數GetStockObject返回一個內置的白色畫刷句柄;如果你想了解其他的內置畫刷,請參見 MSDN。

?
1
wc.hbrBackground = ( HBRUSH )GetStockObject(WHITE_BRUSH);

8.lpszMenuName:指定窗口菜單。由於我們的應用程序沒有菜單, 所以將該參數設為 0。

?
1
wc.lpszMenuName = 0;

9.lpszClassName:指定窗口類的名稱(也就是,為WNDCLASS實例起一個名字),任何有效的字符串都可以。在本例中,我們將它命名為“BasicWndClass”。在隨后的代碼中,我們將通過個名稱來引用相應的WNDCLASS實例。

?
1
wc.lpszClassName = L "BasicWndClass" ;

隨后,我們要調用RegisterClass函數把填好的WNDCLASS實例注冊到Windows中。該函數接收一個指向WNDCLASS結構體的指針。在調用失敗時返回0。

?
1
2
3
4
5
if (!RegisterClass(&wc))
{
     MessageBox(0, L "RegisterClass FAILED" , 0, 0);
     return false ;
}

A.3.4 創建和顯示窗口

當我們把WNDCLASS實例注冊到Windows系統之后,就可以依據該窗口類的描述來創建實際的窗口對象了。另外,我們可以通過窗口類的名字(lpszClassName)來引用注冊后的WNDCLASS 實例。用於創建窗口對象的函數為CreateWindow,它的函數原型如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
HWND CreateWindow(
     LPCTSTR lpClassName,
     LPCTSTR lpWindowName,
     DWORD dwStyle,
     int x,
     int y,
     int nWidth,
     int nHeight,
     HWND hWndParent,
     HMENU hMenu,
     HANDLE hInstance,
     LPVOID lpParam
);

1.lpClassName:已注冊的WNDCLASS實例的名字,該實例描述了所要創建的窗口對象的部分特征。

2.lpWindowName:窗口的名字;即顯示在窗口標題欄上的字符串名稱。

3.dwStyle:定義窗口的樣式。WS_OVERLAPPEDWINDOW是以下幾個標志值的組合:WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_THICKFRAME、WS_MINIMIZEBOX和WS_MAXIMIZEBOX。這些標志值描述了所要創建的窗口的特征。 詳情請參見MSDN。

4.x:窗口左上角在屏幕上的x坐標。可以將該參數設為CW_USEDEFAULT,Windows會為窗口選擇一個適當的默認坐標值。

5.y:窗口左上角在屏幕上的y坐標。可以將該參數設為CW_USEDEFAULT,Windows會為窗口選擇一個適當的默認坐標值。

6.nWidth:窗口寬度,單位為像素。可以將該參數設為CW_USEDEFAULT,Windows會為窗口選擇一個適當的默認寬度。

7.nHeight:窗口高度,單位為像素。可以將該參數設為CW_USEDEFAULT,Windows會為窗口選擇一個適當的默認高度。

8.hWndParent: 當前窗口的父窗口句柄。由於本例中的窗口沒有任何父屬窗口,所以該參數設為0。

9.hMenu:菜單句柄。由於本例中的窗口沒有菜單,所以該參數設為0。

10.hInstance:與窗口關聯的應用程序實例句柄。

11.lpParam:傳遞給WM_CREATE消息處理函數的用戶自定義數據的指針。當創建窗口時,操作系統會向窗口發送一個WM_CREATE消息。該消息會在CreateWindow方法返回前發出。如果希望在創建窗口執行某些操作(比如,完成某些對象的初始化工作),那么就需要處理窗口的WM_CREATE消息。

注意:當我們為窗口指定x、y坐標時,這些坐標位置是相對於屏幕左上角的。而且,x軸的正方向水平向右,y軸的正方向垂直向下。

圖A.4展示的坐標系稱為屏幕坐標系(screen coordinate system),或屏幕空間(screen s圖A.4展示的坐標系稱為屏幕坐標系(screen coordinate system),或屏幕空間(screen space)。

圖4圖 A.4:屏幕空間。

CreateWindow函數將返回創建后的窗口句柄(一個HWND變量)。如果創建失敗,則該句柄的值為0(空句柄)。記住,句柄是引用窗口的一種方式,而窗口由Windows來管理。許多API函數都要求傳入窗口句柄,以明確所要操縱的窗口對象。

?
1
2
3
4
5
6
7
8
9
10
ghMainWnd = CreateWindow(L "BasicWndClass" , L "Win32Basic" ,
     WS_OVERLAPPEDWINDOW,
     CW_USEDEFAULT, CW_USEDEFAULT,
     CW_USEDEFAULT, CW_USEDEFAULT,
     0, 0, instanceHandle, 0);
if (ghMainWnd == 0)
{
     MessageBox(0, L "CreateWindow FAILED" , 0, 0);
     return false ;
}

在InitWindowsApp中調用的最后兩個函數用於控制窗口的顯示。首先我們調用ShowWindow,並且將剛剛創建的窗口句柄傳遞給它,使Windows知道要顯示哪個窗口。我們傳入的另一個整數值表示窗口的初始顯示狀態(例如,最小化、最大化等等)。該值應設為nShowCmd(它是WinMain函數的第4個參數)。當初次顯示窗口時,必須調用UpdateWindow方法,對窗口進行一次手動刷新。該函數只接收一個參數,指定所要刷新的窗口的句柄。

?
1
2
ShowWindow(ghMainWnd, show);
UpdateWindow(ghMainWnd);

到此為止,我們在InitWindowsApp函數中的初始化工作就已經完成了;如果一切順利的話,該函數將返回true。

A.3.5 消息循環

在成功完成初始化工作之后,我們開始編寫程序的核心部分——消息循環。在本例中, 我們將消息循環封裝在了Run函數中。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int Run()
{
     MSG msg = {0};
     BOOL bRet = 1;
     while ( (bRet = GetMessage(&msg, 0, 0, 0)) != 0)
     {
         if (bRet == -1)
         {
             MessageBox(0, L "GetMessage FAILED" ,L "Error" , MB_OK);
             break ;
         }
         else
         {
             TranslateMessage(&msg);
             DispatchMessage(&msg);
         }
     }
     return ( int )msg.wParam;
}

在Run函數中做的第一件事情是聲明一個MSG類型的變量msg。MSG是一個用於表示Windows 消息的結構體,它的原型如下:

?
1
2
3
4
5
6
7
8
typedef struct tagMSG {
     HWND hwnd;
     UINT message;
     WPARAM wParam;
     LPARAM lParam;
     DWORD time ;
     POINT pt;
} MSG;

1.hwnd:接收消息的窗口的句柄。

2.message:用於標識特定消息的預定義常量值(例如,WM_QUIT)。

3.wParam:與消息相關的附加信息,參數的取值依具體消息而定。

4.lParam:與消息相關的附加信息,參數的取值依具體消息而定。

5.time:消息被送入消息隊列時的時間。

6.pt:當消息被送入消息隊列時,鼠標在屏幕空間中的x、y坐標。

隨后,我們進入消息循環。GetMessage函數從消息隊列中檢索消息,使用消息的具體內容來填充msg參數。GetMessage函數的第2、3、4個參數均設為0。當出現錯誤時,GetMessage函數會返回−1。當收到一個WM_QUIT消息時,GetMessage函數會返回0,此時可以終止消息循環的運行。當GetMessage返回其他值時,需要調用另外兩個函數:TranslateMessage和DispatchMessage。TranslateMessage用於進行某些鍵盤消息的轉換;確切地說是將虛擬鍵盤碼轉換為字符信息。最后,DispatchMessage將消息分發給特定的消息處理函數。如果應用程序以WM_QUIT消息正常退出,那么WinMain函數應以WM_QUIT消息的wParam參數作為最終的返回值(退出碼)。

A.3.6 消息處理函數

我們前面提到,消息處理函數(window procedure,直譯為窗口過程)用於對窗口收到的消息做出響應,執行與當前消息對應的程序指令。在本例中,我們的消息處理函數為WndProc,其原型如下:

?
1
2
LRESULT CALLBACK
WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

該函數返回一個LRESULT(實際上是一個整數)值,表明函數的調用結果是成功還是失敗。CALLBACK標識符說明該函數是一個回調函數(callback function),Windows將在應用程序的代碼空間之外調用個函數。你可以從本例的源代碼中看到,我們從未直接調用過個消息處理函數——當窗口需要處理一個消息時,Windows會替我們調用個函數。在消息處理函數的簽名(signature)中包含4個參數:

1.hWnd:接收消息的窗口的句柄。

2.msg:用於標識特定消息的預定義常量值。例如,退出消息為WM_QUIT。前綴 WM 表示“window message (窗口消息)”。預定義的窗口消息有一百多個,具體請參見MSDN。

3.wParam:與消息相關的附加信息,參數的取值依具體消息而定。

4.lParam:與消息相關的附加信息,參數的取值依具體消息而定。

我們的消息處理函數處理了3個消息:WM_LBUTTONDOWN、WM_KEYDOWN和WM_DESTROY。WM_LBUTTONDOWN是用戶在窗口客戶區單擊鼠標左鍵時發出的消息。WM_KEYDOWN是用戶按下某個按鍵時發出的消息。WM_DESTROY是窗口被銷毀時發出的消息。我們的代碼相當簡單;當窗口收到WM_LBUTTONDOWN消息時會彈出一個消息框,顯示“Hello, World”字符串:

?
1
2
3
case WM_LBUTTONDOWN:
     MessageBox(0, L "Hello, World" , L "Hello" , MB_OK);
     return 0;

當窗口收到WM_KEYDOWN消息時,測試用戶按下的是否為ESC鍵。如果是ESC鍵,我們就調用DestroyWindow函數銷毀主程序窗口。此時傳給消息處理函數的wParam參數包含了用戶所按按鍵的虛擬鍵盤碼。每個物理按鍵都有一個或多個與之對應的虛擬鍵盤碼,它可以被視為物理按鍵在C++程序中的標識符。windows.h頭文件包含了所有的虛擬鍵盤碼常量,我們可以使用些常量來測試某一按鍵是否被按下;例如,我們可以使用虛擬鍵盤碼常量VK_ESCAPE來測試ESC鍵是否被按下:

?
1
2
3
4
case WM_KEYDOWN:
     if ( wParam == VK_ESCAPE)
         DestroyWindow(ghMainWnd);
     return 0;

記住,wParam和lParam參數用於指定消息的附加信息。對於WM_KEYDOWN消息來說,wParam參數包含了與按鍵對應的虛擬鍵盤碼。MSDN詳述了每個Windows消息的wParam和lParam參數含義。當窗口被銷毀時,我們需要使用PostQuitMessage函數發送一個WM_QUIT消息(它將終止消息循環的運行):

?
1
2
3
case WM_DESTROY:
     PostQuitMessage(0);
     return 0;

在我們的消息處理函數的最后,調用了DefWindowProc函數。該函數是默認的消息處理函數。在本例中,我們只處理了3個消息;其他所有的消息都按DefWindowProc函數中定義的默認行為來處理,我們不必親自處理所有的消息。例如,本例中的最小化、最大化、窗口縮放和關閉功能都是由默認的消息處理函數來完成的,我們沒有親自處理些消息。

A.3.7 MessageBox函數

我們還有最后一個API函數沒有講解,也就是MessageBox函數。該函數可以非常方便地向用戶顯示提示信息,並獲得一些簡單的輸入。MessageBox函數的原型如下:

?
1
2
3
4
5
6
int MessageBox(
     HWND hWnd,          // Handle of owner window, may specify null.
     LPCTSTR lpText,      // Text to put in the message box.
     LPCTSTR lpCaption, // Text for the title of the message box.
     UINT uType          // Style of the message box.
);

MessageBox函數的返回值取決於消息框的類型。要了解所有的返回值和消息框類型,請參見MSDN;圖A.5展示了一種帶有“Yes/No函數的返回值取決於消息框的類型。要了解所有的返回值和消息框類型,請參見MSDN;圖A.5展示了一種帶有“Yes/No”按鈕的消息框。

圖5圖A.5:帶有“Yes/No”按鈕的消息框。

A.4 改進后的消息循環

游戲與傳統的Windows應用程序之間有很大的區別。通常,游戲會主動重繪界面,不停地更新窗口,而不是坐等消息的到來。傳統的消息循環會造成一個問題,當消息隊列沒有消息時,GetMessage函數會讓線程進入休眠狀態,等待消息到來。而在游戲中,我們不希望這種情況發生;當沒有Windows 消息需要處理時,我們希望游戲運行自身的代碼(比如,渲染3D場景、處理AI等等)。所以,我們要對消息循環做一些修改,用PeekMessage函數代替原先的GetMessage函數。PeekMessage函數會在沒有消息時直接返回,不阻塞線程。我們改進后的消息循環如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int Run()
{
     MSG msg = {0};
     
     while (msg.message != WM_QUIT)
     {
         // If there are Window messages then process them.
         if (PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
         {
             TranslateMessage( &msg );
             DispatchMessage( &msg );
         }
         // Otherwise, do animation/game stuff.
         else
         {
         }
     }
     return ( int )msg.wParam;
}

在聲明msg變量之后,我們會進入了一個無限循環。我們首先調用API函數PeekMessage檢查消息隊列中是否存在消息。有關該函數的參數描述請參見 MSDN。如果消息隊列中存在消息,則PeekMessage返回true,我們執行與先前同樣的消息處理;反之,如果沒有消息,則PeekMessage返回false,我們運行游戲自身的代碼。

A.5 小結

1.在使用 Direct3D時,我們必須創建一個帶有主窗口的Windows應用程序,在主窗口上渲染3D場景。而且,要為游戲創建一個特殊的消息循環。在有消息處理消息;在沒有消息執行游戲自身的代碼邏輯。

2.多個Windows應用程序可以並行運行,所以Windows必須管理那些由多個應用程序共享的資源,對應用程序產生的消息進行疏導。當一個事件(按鍵、鼠標單擊、計時器等等)產生時,消息即被發送到應用程序的消息隊列中。

3.每個Windows應用程序都有一個消息隊列,用來存儲應用程序收到的消息。應用程序的消息循環會不斷地檢查消息隊列,當有消息出現時,它會把消息分發給相應的消息處理函數。注意,一個應用程序可以包含多個窗口。

4.消息處理函數是一個由我們自己編寫的特殊的回調函數,當窗口收到消息時,Windows會自動調用與窗口對應的消息處理函數。在消息處理函數中,我們為特定的消息編寫代碼,使窗口在收到特定的消息執行相應的程序邏輯。對於那些未在消息處理函數中處理的消息,應轉交給默認的消息處理函數來執行默認的行為。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM