OpenGL 程式設計/GLStart/Tut1
本章將介紹使用 Win32 API 進行程式設計。
首先開啟 Dev – C++。開啟後,點選頂部選單欄上的檔案,新建,專案。確保已選中基本選項卡,然後點選 Windows 應用程式。然後給它起一個名字,然後點選確定。在下一個對話方塊中,選擇您計算機上的儲存位置,然後點選儲存。
完成此操作後,您的專案將載入,您將獲得一個帶有預設原始檔的預設原始檔,其中包含 Windows 程式碼。右鍵單擊原始檔上方的 main.cpp 選項卡,然後點選關閉。當它詢問您是否要儲存更改時,點選否。
要向專案新增新的原始檔,請轉到頂部選單欄,然後點選檔案,新建,原始檔。它會詢問您是否要將原始檔新增到專案中。點選是。現在您將有一個空白的原始檔可供編輯。在我們開始編碼之前,點選檔案,儲存。它會詢問您要將原始檔儲存到哪裡。確保您將其儲存到您放置 Dev – C++ 專案的相同位置。將其命名為類似“winmain”的內容,然後點選儲存。現在我們已經準備好了一個空白的原始檔,我們可以開始為我們的視窗編碼了。
原始檔的第一行應該包含 Windows 標頭“windows.h”。
#include <windows.h>
此標頭包含到目前為止我們在 Windows 中程式設計所需的所有函式和結構。
在我們繼續之前,我們需要討論一些關於 Windows 事件的事情。
與標準的命令列程式不同,Windows 程式同時發生許多事件。一個例子是視窗必須不斷地重新繪製自身的背景。我們現在不用擔心這個問題。但是,還有一些其他事件,例如使用者按下視窗上的按鈕,使用者用滑鼠右鍵單擊等。我們將在本課中關注的事件是使用者點選視窗右上角的 X 按鈕以退出程式。我們必須告訴作業系統(Windows)在使用者執行此操作時,程式應該退出。處理這些型別事件的 Windows 中的函式稱為視窗過程。我們接下來將定義此函式。還要注意,Windows 不會將這些操作稱為“事件”,而是將它們稱為訊息。訊息是事件。我們將在課程中使用“訊息”一詞。現在讓我們開始為視窗過程編碼。
視窗過程的返回值是訊息處理的結果,該結果將返回給 WinMain() 函式(稍後將詳細介紹)。
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle of window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
我將在稍後解釋這些引數。但這是您宣告視窗過程的方式。因此,在包含 Windows 標頭的下方,請將其放入其中
LRESULT CALLBACK WinProc(HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
請注意,您可以根據需要命名函式。在這裡,我們將其命名為“WinProc”。容易記住,對吧?在建立實際視窗時,我們需要這個名稱。現在讓我解釋一下不同的引數
HWND hWnd 是“視窗控制代碼”的縮寫。基本上,這是實際的視窗物件本身。此控制代碼在其內部儲存視窗。
UINT msg 是我們將要處理的實際訊息。我們可以處理的訊息成千上萬,但我們將在本課中只關注一個。
WPARAM wParam 和 LPARAM lParam 是不同型別的訊息資訊。在本課中,我們不會使用這些變數。我會在其他時間解釋它們。
現在在訊息過程中,我們需要一種方法來定義如何處理特定訊息。我們將使用 switch 命令對這些訊息進行分類
switch(msg)
{
現在我們將為我們要處理的唯一訊息插入一個 case 語句,即使用者退出程式時。此訊息的 ID 值為 WM_DESTROY。當我們在其 case 語句中時,我們放入一個函式來退出程式。我們將使用 PostQuitMessage() 函式,該函式接受一個退出程式碼作為其單個引數,該程式碼為 0
case WM_DESTROY:
PostQuitMessage(0);
break;
default: break;
}
在上面,我們放置了退出訊息,然後結束了 switch 語句。現在我們已經處理了該訊息,我們完成了視窗過程。我們還需要做的一件事是將訊息處理的結果返回給 WinMain 函式。為此,我們返回 DefWindowProc() 函式
LRESULT DefWindowProc(
HWND hWnd, // handle to window
UINT Msg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
如您所見,此函式接受與視窗過程本身相同的引數。因此,您只需用與視窗過程宣告中相同的引數填寫它即可
return DefWindowProc(hWnd,msg,wParam,lParam); }
現在這最終完成了我們的視窗過程。現在讓我們繼續進行 WinMain() 函式。
WinMain
WinMain() 函式有點類似於常規 C 或 C++ 程式中的 main() 函式。它是所有 Windows 程式的入口點。但設定起來要複雜一些。以下是它的原型
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // pointer to command line
int nCmdShow // show state of window
);
讓我解釋一下引數
HINSTANCE hInstance 用於跟蹤視窗。讓我更好地解釋一下。它跟蹤視窗的例項或外觀。這是一個很難解釋的概念。我們稍後將進一步討論。
HINSTANCE hPrevInstance 實際上沒有使用。
LPSTR lpCmdLine 指定程式的命令列引數。我們不用擔心這個。
int nCmdShow 是視窗顯示的方式。我們現在還沒有搞亂它。
在訊息過程型別之後,鍵入 WinMain() 函式的初始化
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd)
{
現在在 WinMain() 函式中,我們必須宣告三個非常重要的變數。將其放在 WinMain() 初始化之後
HWND hWnd;
WNDCLASSEX wcex;
MSG msg;
讓我解釋一下這些變數
HWND hWnd 我們已經在訊息過程中解釋過了。它是儲存視窗本身的控制代碼。
WNDCLASSEX wcex 是實際的視窗結構。您定義的此結構設定視窗的某些屬性。我們將在一分鐘內填充此結構。
MSG msg 是與訊息過程中的訊息結構類似的結構。此特定結構包含諸如實際訊息本身之類的欄位。我們稍後將在 WinMain() 函式中使用它。
現在我們有了這些變數,讓我們定義 WNDCLASSEX 結構。以下是 WNDCLASSEX 結構的原型
typedef struct _WNDCLASSEX { // wc
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX;
讓我向您展示如何填寫這些屬性
- UINT cbSize 是結構本身的大小。- UINT style 是視窗樣式。為此有很多選擇。我們將使用 CS_HREDRAW 和 CS_VREDRAW 來允許視窗在水平和垂直方向上調整大小。- WNDPROC lpfnWndProc 是您要連結的訊息過程的名稱。我們已經在開頭建立了它,所以在此處放置它的名稱(WinProc)。- int cbClsExtra 和 int cbWndExtra 將不會使用,因此它們都將設定為 0。- HANDLE hInstance 是我們在宣告 WinMain 時建立的例項控制代碼。只需在此屬性中放入我們為其指定的名稱(hInstance)即可。- HICON hIcon 是我們要用於程式的大圖示。為此,我們將使用 LoadIcon() 函式載入一個標準圖示,該函式接受以下引數:圖示的 HINSTANCE(我們將其設定為 NULL)和圖示名稱(設定為 IDI_APPLICATION 以獲得標準 Windows 圖示)- HCURSOR hCursor 是我們要使用的滑鼠游標。為此,我們使用 LoadCursor() 函式,該函式接受以下引數:游標的 HINSTANCE(設定為 NULL)和游標名稱(IDC_ARROW 用於標準 Windows 箭頭)- HBRUSH hbrBackground 是我們要用於背景的顏色。我們透過使用 GetStockObject() 函式來獲取此顏色,該函式接受刷子名稱作為引數(我們將其設定為 GRAY_BRUSH),並將其型別轉換為 HBRUSH 型別。- LPCTSTR lpszMenuName 標識我們要在程式中使用的選單。由於我們沒有選單,因此將其設定為 NULL。- LPCTSTR lpszClassName 是一個字串,用於標識視窗的 Windows 類名稱。我們稍後在建立視窗時將需要這個名稱。將其設定為“WinClass”。- HICON hIconSm 是用於程式的小圖示。將其設定為 NULL 以獲得預設圖示。
因此,以下是定義好的整個結構,準備鍵入
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WinProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL,IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL,IDC_ARROW);
wcex.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "WinClass";
wcex.hIconSm = NULL;
在定義 WNDCLASSEX 結構之後,我們必須使用 RegisterClassEx() 函式將其註冊,該函式接受指向我們要註冊的 WNDCLASSEX 結構的指標作為其單個引數
RegisterClassEx(&wcex);
現在我們繼續建立視窗。還記得我們在 WinMain() 函式開頭建立的 HWND(視窗控制代碼)嗎?好吧,我們將使用 CreateWindow() 函式建立的視窗將其設定為相等
HWND CreateWindow(
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu or child-window identifier
HANDLE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);
讓我解釋一下這些引數
- LPCTSTR lpClassName 是我們在定義 WNDCLASSEX 結構(“WinClass”)時定義的視窗類的名稱 - LPCTSTR lpWindowName 是顯示在視窗標題欄上的字串。現在,將其設定為“視窗” - DWORD dwStyle 指定某些視窗樣式。有很多,但現在我們將將其設定為 WS_OVERLAPPEDWINDOW,以便視窗顯示帶有邊框和最小化、最大化和關閉按鈕,就像標準的 Windows 應用程式一樣。 - int x 和 int y 是從螢幕左上角開始的視窗的 X 和 Y 位置。現在我們將其設定為 0 和 0。 - int nWidth 和 int nHeight 是視窗的畫素高度和寬度。現在將其設定為 400 和 400。 - HWND hWndParent 是指向父視窗的視窗控制代碼。由於我們只建立一個視窗,因此將其設定為 NULL 以指示沒有父視窗。 - HMENU hMenu 用於標識要使用的選單。由於我們沒有,因此將其設定為 NULL。 - HANDLE hInstance 是我們設定為 WNDCLASSEX 結構(hInstance)的例項控制代碼。 - LPVOID lpParam 不需要,因此將其設定為 NULL。
這是傳遞了正確引數的整個函式
hWnd = CreateWindow("WinClass","My Window",
WS_OVERLAPPEDWINDOW,0,0,400,400,NULL,NULL,
hInstance,NULL);
為了安全起見,讓我們確保視窗已成功建立。如果 CreateWindow() 函式成功,視窗控制代碼 (hWnd) 將具有與之關聯的視窗。如果 CreateWindow() 不成功,則視窗控制代碼將具有 NULL 值。因此,首先使用 IF 語句檢查視窗控制代碼中是否為 NULL 值
if(hWnd == NULL)
{
現在,如果視窗控制代碼為 NULL,我們需要告訴使用者錯誤並關閉程式。首先,為了通知使用者問題,我們可以使用彈出訊息框。為此,我們使用 MessageBox() 函式
int MessageBox(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
);
第一個引數是訊息框來自的視窗的視窗控制代碼。由於如果視窗控制代碼為 NULL,我們正在檢查它,因此在此處輸入 NULL。第二個引數 lpText 是訊息框對話方塊中的主要文字。為此,輸入“錯誤:無法建立視窗”。第三個引數 lpCaption 是訊息框頂部的標題標題。將其設定為“錯誤”。最後一個引數 uType 是訊息框樣式。在這裡,您可以識別使用者可以點選訊息框上的按鈕型別以及出現在訊息框上的圖示。現在,將其設定為 MB_OK,以使其只顯示一個確定按鈕。建立訊息框後,我們必須退出程式。就像標準的 C++ main() 函式一樣,當發生錯誤時,我們輸入“return -1”而不是“return 0”以告訴作業系統我們正在使用錯誤退出程式。因此,在訊息框後輸入“return 0”
MessageBox(NULL,"Error: Unable to create Window","ERROR",MB_OK);
return -1;
}
現在,假設視窗已成功建立,我們需要實際顯示它。我們使用 ShowWindow() 函式來實現這一點
BOOL ShowWindow(
HWND hWnd, // handle of window
int nCmdShow // show state of window
);
第一個引數是我們想要顯示的視窗的控制代碼 (hWnd)。第二個引數是我們 在 WinMain() 函式 (nShowCmd) 初始化時建立的變數
ShowWindow(hWnd,nShowCmd);
緊隨其後,我們應該使用 UpdateWindow() 函式處理對視窗的任何更新,該函式將要更新的視窗的控制代碼 (hWnd) 作為單個引數
UpdateWindow(hWnd);
現在我們的視窗已顯示,我們需要防止程式退出,這樣我們的視窗會一直停留在螢幕上。為此,我們建立一個主迴圈。我將執行迴圈的方式是建立一個條件為 1 的 while 迴圈,以便迴圈無限
while(1)
{
現在,當迴圈正在進行時,我們需要某種方法來檢查使用者是否退出了程式。為此,我們必須檢查傳送的訊息,看看其中是否有一個退出訊息。為此,我們使用 PeekMessage() 函式
BOOL PeekMessage(
LPMSG lpMsg, // pointer to structure for message
HWND hWnd, // handle to window
UINT wMsgFilterMin, // first message
UINT wMsgFilterMax, // last message
UINT wRemoveMsg // removal flags
);
- LPMSG lpMsg 是指向我們在 WinMain() 函式 (msg) 開始時建立的訊息結構的指標 - HWND hWnd 是我們想要處理其訊息的視窗。您可以在此處輸入 NULL 以選擇預設視窗 - UINT wMsgFilterMin 和 UINT wMsgFilterMax 指定要處理的訊息的最小和最大範圍。由於我們想要處理所有訊息,因此在這兩個引數中都輸入 0 - UINT wRemoveMsg 確定訊息在處理後如何處理。我們將使用值 PM_REMOVE 告訴它在處理訊息後將其刪除。
由於 PeekMessage() 函式返回一個布林值以確定其是否成功,因此讓我們將其放入 IF 語句中以確保其成功執行
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
如果處理了一條訊息,我們只需要檢查該訊息是否為退出訊息。我們可以透過檢查我們之前建立的 MSG 結構 (msg) 的訊息屬性來做到這一點。我們要檢查的訊息是 WM_QUIT 訊息。如果是,那麼我們必須退出程式當前所在的迴圈
if(msg.message == WM_QUIT) break;
如果未處理退出訊息,我們仍然必須處理其他訊息。為此,我們使用 TranslateMessage() 函式來解釋訊息。然後,在那之後,我們使用 DispatchMessage() 函式來執行訊息。這兩個函式都將指向 MSG 結構的指標作為單個引數
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
這兩個花括號結束了我們的主迴圈。在完成 WinMain() 函式之前,我們所做的最後一件事是向作業系統返回一個值。我們返回 0 作為值,因為到目前為止,程式已成功執行
return 0;
}
現在編譯並執行程式,您應該得到這個不錯的結果

