一個簡單的窗口
例子:簡單的窗口
有時人們在IRC提問,”我應該怎樣制作一個窗口”。。。嗯,這恐怕不是完全這么簡單好回答!其實這並不難一旦你明白你在做什么,但在你得到一個可展示的窗口之前還有一些事情需要我們去做,我們只需要簡單地聊聊快速做下筆記,這個問題就能被很簡單的回答。
我很喜歡先動手再學習。。。一下就是一個簡單的窗口的程序,我們將會簡短的對它進行解釋說明。
1 #include <windows.h>
2
3 const char g_szClassName[] = "myWindowClass"; 4
5 // Step 4: the Window Procedure
6 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 7 { 8 switch(msg) 9 { 10 case WM_CLOSE: 11 DestroyWindow(hwnd); 12 break; 13 case WM_DESTROY: 14 PostQuitMessage(0); 15 break; 16 default: 17 return DefWindowProc(hwnd, msg, wParam, lParam); 18 } 19 return 0; 20 } 21
22 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 23 LPSTR lpCmdLine, int nCmdShow) 24 { 25 WNDCLASSEX wc; 26 HWND hwnd; 27 MSG Msg; 28
29 //Step 1: Registering the Window Class
30 wc.cbSize = sizeof(WNDCLASSEX); 31 wc.style = 0; 32 wc.lpfnWndProc = WndProc; 33 wc.cbClsExtra = 0; 34 wc.cbWndExtra = 0; 35 wc.hInstance = hInstance; 36 wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); 37 wc.hCursor = LoadCursor(NULL, IDC_ARROW); 38 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 39 wc.lpszMenuName = NULL; 40 wc.lpszClassName = g_szClassName; 41 wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); 42
43 if(!RegisterClassEx(&wc)) 44 { 45 MessageBox(NULL, "Window Registration Failed!", "Error!", 46 MB_ICONEXCLAMATION | MB_OK); 47 return 0; 48 } 49
50 // Step 2: Creating the Window
51 hwnd = CreateWindowEx( 52 WS_EX_CLIENTEDGE, 53 g_szClassName, 54 "The title of my window", 55 WS_OVERLAPPEDWINDOW, 56 CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, 57 NULL, NULL, hInstance, NULL); 58
59 if(hwnd == NULL) 60 { 61 MessageBox(NULL, "Window Creation Failed!", "Error!", 62 MB_ICONEXCLAMATION | MB_OK); 63 return 0; 64 } 65
66 ShowWindow(hwnd, nCmdShow); 67 UpdateWindow(hwnd); 68
69 // Step 3: The Message Loop
70 while(GetMessage(&Msg, NULL, 0, 0) > 0) 71 { 72 TranslateMessage(&Msg); 73 DispatchMessage(&Msg); 74 } 75 return Msg.wParam; 76 }
你可以利用這個最簡單的窗口程序中的大多數內容,創建一個實際功能窗口,如果你成功編譯了第一個小程序,那么這個樣例程序也應該能夠運行。
步驟1:注冊窗口類
一個窗口類存儲了一種窗口類型的信息,包括控制窗口的窗口程序,窗口的大小圖標以及背景顏色等。通過這種方式,你可以只注冊一次類,然后無需再次指定這些屬性創建多個窗口,但是如果需要的話,你設置的大多數類的屬性具體到某一個窗口上都是可以修改的。
一個窗口類跟一個C++類沒有任何關系。
1 const char g_szClassName[] = "myWindowClass";
上面的變量存儲了一個窗口類的值,我們會馬上使用它來注冊系統的窗口類。
1 WNDCLASSEX wc; 2
3 wc.cbSize = sizeof(WNDCLASSEX); 4 wc.style = 0; 5 wc.lpfnWndProc = WndProc; 6 wc.cbClsExtra = 0; 7 wc.cbWndExtra = 0; 8 wc.hInstance = hInstance; 9 wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); 10 wc.hCursor = LoadCursor(NULL, IDC_ARROW); 11 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 12 wc.lpszMenuName = NULL; 13 wc.lpszClassName = g_szClassName; 14 wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); 15
16 if(!RegisterClassEx(&wc)) 17 { 18 MessageBox(NULL, "Window Registration Failed!", "Error!", 19 MB_ICONEXCLAMATION | MB_OK); 20 return 0; 21 }
這是我們在WinMain()中用來注冊窗口類的代碼,我們會填寫WNDCLASSEX結構的成員,然后調用RegisterClassEx()
影響窗口類的結構成員如下:
cbSize
結構體的大小
style
類的風格,不要跟窗口的風格混淆,通常設置為0
lpfnWndProc
指向窗口類的窗口消息處理程序的指針
cbClsExtra
在內存中為這個類分配的額外的數據的數據量的大小,通常為0
cbWndExtra
在內存中為每一個這個類的窗口分配的額外的數據的數據量的大小,通常為0
hInstance
應用程序實例句柄(我們在. WinMain())的第一個參數。
hIcon
大圖標(通常是32x32),當我們按下Alt+Tab時
hCursor
顯示在窗口中的光標
hbrBackground
設置窗口顏色的背景刷
lpszMenuName
被用在這個類的窗口中的菜單資源的名字
lpszClassName
窗口類的名字
hIconSm
小(通常是16 x16)圖標在任務欄顯示在窗口的左上角。
不要擔心,如果你不是很確切理解這些屬性的意思,關於窗口類的屬性值將在后面提到更多。另外你要記住不要嘗試記住這些屬性,這沒有什么意義,我很少嘗試記住類的屬性,或函數參數,這完全是浪費精力和時間的做法。如果你需要調用某個函數,那么只需要花費幾秒時間就能在幫助文檔中找到它的相關資料,如果你連幫助文檔都沒有,那快去找。你沒有任何損失,最終你會知道你用的最多的函數的參數。
注冊完窗口類后我們調用RegisterClassEx()並檢查是否有出錯,如果注冊失敗會彈出一個消息彈窗提醒我們並且通過返回0終止接下來程序WinMain()的運行。
步驟2:創建窗口
一旦類注冊成功,我們就能用它來創建窗口。你應該理解CreateWindowEx()的參數(當我們使用一個新的API調用時我們經常會這么做),但在這里我會簡要解釋。
1 HWND hwnd; 2
3 hwnd = CreateWindowEx( 4 WS_EX_CLIENTEDGE, 5 g_szClassName, 6 "The title of my window", 7 WS_OVERLAPPEDWINDOW, 8 CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, 9 NULL, NULL, hInstance, NULL);
參數的說明如下:
WS_EX_CLIENTEDGE
擴展的窗口風格,在例子中我已經給創建的窗口設置了一個凹內邊框,如果你想看到設置不設置的區別,你可以設置為0,你可以設置一下其他值,看看有什么區別。
g_szClassName:
設置窗口類名,這個參數告訴系統要創建一個哪種類型的窗口,在這里我們想創建一個剛才注冊的窗口類,所以使用g_szClassName這個類名。
"The title of my window"
很明顯啦這個就是窗口的標題
WS_OVERLAPPEDWINDOW
設置窗口的樣式參數,有很多這種類型的參數,你應該把他們找出來然后測試一下又什么區別,關於這些的講解會在后面被覆蓋。
W_USEDEFAULT, CW_USEDEFAULT, 240, 120,
這四個參數是用來設置窗口的大小和位置的,前面兩個參數是窗口左上角的X和Y坐標,后面兩個參數分別代表寬度和高度。我把窗口的X、Y坐標設置成W_USEDEFAULT, CW_USEDEFAULT,這樣是為了讓窗口在屏幕上自適選擇顯示窗口。記住屏幕的左邊是 一個0的X坐標值,並且向右增加;屏幕的頂端是一個0的Y坐標值,並且向底部增加。單位是像素,這是屏幕可以顯示的最小單位。
NULL, NULL, hInstance, NULL
上面四個分別對應父窗口句柄,菜單處理,應用程序實例句柄,窗口創建數據的指針。在windows中,在你屏幕上的窗口會按父和子窗口的等級安排。當你在一個窗口中看到一個按鈕時,這個按鈕在它被包含的父窗口中,相當於一個孩子,通過這個例子,父句柄被設 置為NULL,因為我們創建的這個窗口沒有父母,這是我們的主或頂層窗口。菜單是空的因為我們還沒有設置菜單;實例句柄被設置成傳遞給WinMain()的第一個參數;創建數據(我幾乎不適用)可用於發送額外的數據給我們要創建的窗口,這里我們也設為NULL。
如果你想知道這個神奇的NULL是什么,它只是簡單定義為0而已。事實上,在C語言中它被定義為((void*)0),因為它是用來使用指針的,因此使用NULL可能會報警,取決於編譯器和報警級別設置,可以忽略警告,或者使用0替代NULL
注意多檢查返回值,這有利於減少出錯,很多人遇到一些莫名其妙的錯誤,這很可能是因為沒有設置好返回值。CreateWindow()在某些時候可能會失敗即使你是一個經驗豐富的程序員,原因很簡單,有很多很容易犯的錯誤。只有你學會如何快速定位找到這些 錯誤,至少給自己找出哪里出錯的機會,並且一直檢查返回值!
1 if(hwnd == NULL) 2 { 3 MessageBox(NULL, "Window Creation Failed!", "Error!", 4 MB_ICONEXCLAMATION | MB_OK); 5 return 0; 6 }
在創建並檢查確保我們有一個有效的可展示的窗口之后,使用WinMain()中的最后一個參數,然后更新它確保在屏幕上它能正確地重繪本身。
1 ShowWindow(hwnd, nCmdShow); 2 UpdateWindow(hwnd);
nCmdShow參數是可選的,你可以用SW_SHOWNORMAL來代替它,並且從頭到尾使用,但是使用傳遞給WinMain()的參數讓任何使用你程序的人可以指定是否設置窗口可見、最大化、最小化等。。。你會在windowsd的快捷方式的屬性中發現這些選項,這個參數是關於如何進行選擇的。
步驟3:消息環
這是整個程序的核心,幾乎所有的程序都是通過這個點來進行控制的。
1 while(GetMessage(&Msg, NULL, 0, 0) > 0) 2 { 3 TranslateMessage(&Msg); 4 DispatchMessage(&Msg); 5 } 6 return Msg.wParam;
GetMessage()從你的應用的消息隊列中接收消息。任何時候,用戶移動鼠標、敲擊鍵盤,點擊窗口上的菜單或者其他動作時,系統會生成消息並插入程序的消息隊列,通過調用GetMessage(),你會請求下一個從隊列中移除可用的消息,並且返回給你。如果沒有消息,GetMessage()會鎖住掛起,如果你不熟悉這個詞,這意味着它一直等待直到有一個消息,然后返回給你。
TranslateMessage()做了一些額外的處理鍵盤事件,將我們敲擊鍵盤轉化給字符;最后DispatchMessage()將消息發送到消息產生的窗口,這可能是我們的主窗口,也可能是其他窗口,或者在某些情況下被屏幕后其他系統或程序創建的窗口。這不需要你關心,我們要關心的是我們得到消息然后把它發送出去,系統負責剩下的把消息發送到合適正確的窗口。
步驟4:窗口程序(消息處理程序)
如果說消息環是程序的核心,那么消息處理程序就是程序的大腦,這里是所有的消息被發送到的窗口中進行處理的地方。
1 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 2 { 3 switch(msg) 4 { 5 case WM_CLOSE: 6 DestroyWindow(hwnd); 7 break; 8 case WM_DESTROY: 9 PostQuitMessage(0); 10 break; 11 default: 12 return DefWindowProc(hwnd, msg, wParam, lParam); 13 } 14 return 0; 15 }
窗口程序(消息處理程序)會被每個消息調用,HWND參數是你消息作用的窗口的句柄。這很重要,因為你可能有同個類的兩個或更多的窗口,這些窗口使用同一個窗口程序(WinProc()),這就需要hwnd參數來區分不同窗口。舉個例子,我們得到一個WM_CLOSE的消息,這個消息會關閉窗口,因為我們使用了窗口句柄參數,這個消息的處理就不會影響到其他窗口,只有這個消息應用的窗口才會被影響和處理。
當我們按下窗口右上方的X按鈕或觸發組合鍵Alt-F4時,WM_CLOSE消息被發送,這會導致窗口被默認摧毀,但是我喜歡顯式地處理它,因為這是進行清理檢查的最佳時機,或者在程序退出之前詢問用戶是否要保存文件等。
當我們調用DestroyWindow()時,系統發送WM_DESTROY消息給要摧毀的窗口,在這種情況下即我們的窗口,然后在最終從系統中移除我們的窗口之前摧毀所有的子窗口,在我們的小程序中這個唯一的一個窗口,所以我們什么都不需要做,只需要調用PostQuitMessage()方法來退出。這個方法會發送WM_QUIT消息給消息環,我們永遠都不會接收到這一消息,因為它會導致GetMesage()返回false,正如你在消息環的代碼里看到的,當這個發生時我們會停止處理消息然后返回最終的結果代碼,WM_QUIT的wParam恰好是我們傳遞給PostQuitMessage()的值。當你的程序被設計用來被另一個程序調用並且你想返回一個指定的值返回值才有意義。
步驟5:沒有步驟5
唷,就是醬紫!如果我還沒有解釋得很清楚,那多看幾遍,在我們進入更有用的程序之前希望你會更加清楚。
PS.由於本人英文水平所限,只能翻譯到這個程度了,有紕漏還望多多指出,附上本篇翻譯的英文原版教程地址:http://www.winprog.org/tutorial/simple_window.html