windows 視窗程式設計 1 - cs.pu.edu.twtsay/course/gameprog/slides/gp2.pdf · 2 windows...
Post on 02-Sep-2019
15 Views
Preview:
TRANSCRIPT
-
1
Windows 視窗程式設計(1)
靜宜大學資訊管理學系蔡奇偉 副教授
大綱
Windows 視窗系統的特性Windows APIMSDN 線上說明文件匈牙利(Hungarian)命名法一個最少行的 Windows 視窗程式Windows 程式的事件處理模型視窗程式的骨架
-
2
Windows 視窗系統的特性
圖形化的人機介面
圖形顯示器
視窗
滑鼠 + 鍵盤Multiprocessing & Multithreading
可同時處理多個程式
一個程式可有多個執行緒(thread)事件驅動模式
程式的執行是依據事件而定
Windows API
Windows API (Application Program Interface) 是一套微軟
公司所提供的函式庫,專門用來撰寫 Windows 的應用程
式。裏面有上千個函式。它們的功能包括:繪圖、列
印、記憶體管理、網路連接、輸出/入裝置的讀寫、檔案
的讀寫、建立選單、程式資源管理、 …、等等,應有盡
有。要完全熟悉它們,可得花上一段長久的歲月
幸好,寫 Windows 的遊戲程式時,我們只要知道少部分
的 Windows API 即可。
-
3
MSDN 線上說明文件
MSDN (Microsoft Developer Network) 是微軟公司提供給程式
發展者的一套鉅大資料庫,裏面涵括所有微軟産品的技術文
件、說明手冊、API 定義、一些微軟的書籍、以及發展中的
程式庫。你可以到 MSDN 網站 (http://msdn.microsoft.com) 直
接閱覽或下載這些資料,也可以安裝 MSDN 光碟片,在你的
電腦上更快速地瀏覽。
任何一位 Windows 視窗程式的設計者,都應該善用 MSDN 裏
豐富的資訊,一方面增強自已的實力,一方面也可避免無謂
的錯誤。
-
4
匈牙利命名法
匈牙利命名法(Hungarian Notations)是微軟公司的
Charles Simonyi 先生所提出的一套變數命名的法則:把
型態的簡稱加在變數名稱之前。比方說:假定 value 是一
個整數變數,則取名成 iValue、 name 是一個字串變數,
則取名成 strName、…、等等。微軟公司認為這套準則有
益於大型軟體計畫的維護,所以在其 Windows API 中都
採用這套命名法。學習 Windows 程式設計的你,應該早
一點熟悉它。下一頁我們列出常用的型態字首。
字首 代表的型態 範例
c char cCode
by BYTE (unsigned char) byHeader
n int nCount
i int iNumber
x, y short (存座標值) xCoord
cx, cy short(存座標計數值) cxOffset
b BOOL(int 代表布林值) bFlag
w UINT(unsigned int) wPara
l LONG(long) lAmount
dw DWORD(unsigned long) dwFlags
-
5
字首 代表的型態 範例
fn function fnSort
s string sName
sz, str C 字串(最後字元為 ‘\0’) szName
lp 32-bit long pointer lpProc
h handle hInstance
msg message msgInfo
註:上述非 C 的標準資料型態者(如 BOOL ),是微軟公司所自定的。舉例來說,BOOL 在 windows.h 中被定義成:
typedef int BOOL;
一個最少行的 Windows 視窗程式
#define WIN32_LEAN_AND_MEAN#include #include
// main entry point for all windows programint WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hprevinstance,
LPSTR lpcmdline, int ncmdshow){
// call message box APIMessageBox(NULL, "What's up, world!",
"My first Windows Program", MB_OK);
// exit programreturn 0;
}
底下是一個號稱最少行的 Windows 視窗程式:
-
6
執行前一頁的程式,我們可以看到下面的對話視窗:
你可以在螢幕上任意移動它的位置。按下「確定」按鈕
後,即可關閉視窗並結束程式的執行。
底下我們逐行地剖析前述的程式。不過,我們先從第 2 行說起。
#include
所有的 Windows 視窗程式都必須加入 windows.h 這個系統標頭檔,因為其中宣告了使用 Winsows API 所需的常數、資料型態、函式原型等等。
回到第一行:
#define WIN32_LEAN_AND_MEAN
加入這巨集定義的目的是為了把 windows.h 瘦身,排除掉一些不常用的宣告,如此一來,可以加速編譯的過程,節省一些程
式發展的時間。
-
7
第三行:
#include
windowsx.h 這個系統標頭檔定義了一些好用的巨集,可以簡化程式的撰寫,所以我們把它加進來。
第四個指令:
int WINAPI WinMain (HINSTANCE hinstance,HINSTANCE hprevinstance,LPSTR lpcmdline,int ncmdshow)
所有的 Windows 視窗程式的執行進入點是 WinMain 這個函式(非 Windows 視窗的程式則是以傳統的 main 函式為執行進入點)。
由於早期的 Windows API 是用 Pascal 程式語言來撰寫,而Pascal 的參數傳遞順序(calling sequence)與 C 恰恰相反。因此,在 C 程式中使用這些 API 函式時,需要加上 WINAPI 這個宣告,告訢編譯程式採用 Pascal 的參數傳遞順序。若是自已寫的 C 函式,就不用加了。
Windows 作業系統透過 WinMain 的四個參數,傳遞一些資訊
給你的程式,這些參數的意義如下:
HINSTANCE hinstance
資料型態 HINSTANCE 是一個 32-bit 的 unsigned int 。參數 hinstance 從作業系統接收一個代碼(handle),此代碼在作業系統代表這個目前正執行的程式。
-
8
HINSTANCE hprevinstance
hprevinstance 是此程式前一個開啟且正執行的代
碼。Win32 系統已經不用這一個參數,而且把它永遠設成 NULL (0) 。
LPSTR lpcmdline
LPSTR 是一個指標型態。參數 lpcmdline 是一個指到
C字串的指標,用來接收從作業系統傳來的指令行參
數。比方說,若下達的指令行為:
prog –a –n data.txt
則 lpcmdline 指到的字串值是 “–a –n data.txt”,注意:程式名稱 prog 並不在這字串中。
int ncmdshow
ncmdshow 是此程式開啟時的視窗顯示方式。常見值如
下:
SW_HIDE 隱藏視窗
SW_MAXIMIZE 視窗放大至整個螢幕
SW_MINIMIZE 視窗縮至最小
SW_SHOW 顯示並啟動視窗
SW_SHOWNORMAL 顯示正常大小的視窗並啟動
其他的可能值請參閱 MSDN 線上手冊。
-
9
第五個指令:
MessageBox(NULL, "What's up, world!","My first Windows Program",MB_OK);
MessageBox 這個 Windows API 在螢幕上開啟一個訊息對
話視窗。其宣告如下:
int MessageBox (HWND hWnd, // handle to owner windowLPCTSTR lpText, // text in message boxLPCTSTR lpCaption, // message box titleUINT uType // message box style
);
由於我們沒有建立視窗,這個 message box 並無父視窗,所以參數 hWnd 設定成 NULL。
message box 的顯示樣式和操作模式可以用參數 uType 來設定。比方說,如果想再加上 Cancel 按鈕和顯示一個警告的圖像(icon),你可以改成以下呼叫方式:
MessageBox(NULL, "What's up, world!",
"My first Windows Program",
MB_OKCANCEL | MB_ICONWARNING);
uType 詳細的說明,請參閱 MSDN 線上的手冊。
-
10
用 MessageBox 來檢視變數
偵錯 Windows 程式時,我們可以用 MessageBox 函式來檢
視變數的值。比方說,我們先寫下面的函式:
int showValue (hWnd hwin, char *name, int ivalue){
char szValue[20];
sprintf(szValue, “%s = %d”, name, ivalue);MessageBox(hwin, szValue, “Debug”, MB_OK);
}
寫好之後,我們就可以在程式中加入 showValue 函式來顯示
整數變數的值,譬如:
int seeme = 1234;
// 假定 main_window 是目前開啟的視窗
showValue(main_window, “seeme”, seeme);
則會在螢幕上顯示出下面的視窗:
-
11
視窗程式的事件驅動模型
視窗程式是採用事件驅動的模型,換句話說,程式的執行
流程是根據事件發生的時間與種類而定。事件可因為使用
者的操作而產生,如滑鼠移動、按下滑鼠鍵、按下鍵盤、
選擇功能表的項目、隱藏的視窗被提到幕前、…、等,也
可由硬體産生,如主機板上的時脈產生器(clock),或由
軟體(作業系統或程式本身)產生。作業系統中,有一個
稱之為訊息佇列(message queue)的資料結構,用來儲存
事件的訊息。作業系統會負責把這些訊息送到適當的視窗
應用程式來處理,如下一頁的圖片所示。
msg 1msg 2msg 3
msg n
MessageQueue
WinMain (){
…}
視窗應用程式
WinMain (){
…}
視窗應用程式
WinMain (){
…}
視窗應用程式
WinMain (){
…}
視窗應用程式
使用者輸
入
-
12
每個視窗應用程式也內建一個 local message queue 來儲存
所接收到的事件訊息。不過做為一個視窗應用程式設計師
的我們,並不需要了解這些內部結構,我們只要知道如何
擷取與處理這些訊息。這些工作可透過 Windows API 的函
式呼叫即可。所有視窗程式的基本架構都大同小異,長像
都近似下一頁所描繪的樣子。
WinMain (){
// code to setup windows
// enter the event loopwhile (GetMessage) {
TranslateMessageDispatchMessage
}}
msg 1msg 1
msg n
WndProc (msg){
switch (msg) {case MOUSE_DOWN:case KEY_PRESS:case WM_PAINT:…
}
-
13
在 WinMain 主函式中,我們必須先設定好視窗物件,讓視窗開始接收事件訊息,然後進入所謂的事件迴圈(event loop)。在事件迴圈中,我們首先呼叫 GetMessage 來取得下一筆事件的訊息。然後呼叫 TranslateMessage 把訊息轉化成可以進一步處理的格式。最後呼叫 DispatchMessage 把訊息傳到我們寫好的 WndProc 函式來判讀與處理。
WndProc 通常只是一個很大的 switch 敘述,以訊息的種類來分派至適當的程式碼去執行。WndProc 稱為視窗的訊息處理函式。
視窗程式的骨架
底下我們來探討視窗程式的基本架構。首先,視窗的設定包
括下面的步驟:
1. 選擇適當的視窗類別
2. 註冊視窗類別
3. 建立視窗物件
4. 顯示視窗
接著我們說明事件迴圈中的 GetMessage、TranslateMesssage、DispatchMessage 三個 API 以及常
見的事件。
-
14
選擇適當的視窗類別
在 WinMain 中,你要宣告一個如下的變數:
WNDCLASSEX wndclass
然後把適當的值填入 wndclass 這個結構變數,來選
取視窗的類別。
附註說明:另有一個叫 WNDCLASS 的資料型態,它已被上述的WNDCLASSEX 所取代。凡是新的 Windows API,都用 EX 當作字尾。微軟公司建議新的視窗應用程式應該都採用新版的 Windows API。
typedef struct _WNDCLASSEX {UINT cbSize;UINT style;WNDPROC lpfnWndProc;int cbClsExtra;int cbWndExtra;HINSTANCE hInstance;HICON hIcon;HCURSOR hCursor;HBRUSH hbrBackground;LPCTSTR lpszMenuName;LPCTSTR lpszClassName;HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;
WNDCLASSEX 結構
的定義
-
15
WNDCLASSEX 的欄位說明
UINT cbSize
這欄必須設成 WNDCLASSEX 結構的大小,即等於sizeof(WNDCLASSEX)。
UINT style這欄設定視窗類別的樣式。多重的樣式可用運算子 | 結合起來。常用的樣式為:CS_HREDRAW | CS_VREDRAW 使得視窗改變水平和垂直大小時,會重畫視窗的內容。若你
希望視窗也處理 double click 事件的話,可加上CS_DBLCLKS 這項樣式。(CS: Class Style)
WNDPROC lpfnWndProc
這欄是個函式指標(function pointer),必須設成前述之訊息處理的函式 。
int cbClsExtra
指定在視窗類別結構後附加的記憶體大小,這塊記憶體可
用來儲存一些視窗類別額外的資訊。通常此欄是設成 0。(cb: count byte)
int cbWndExtra
指定在視窗物件結構後附加的記憶體大小,這塊記憶體可
用來儲存視窗物件額外的資訊。通常此欄是設成 0。
-
16
HINSTANCE hInstance
這欄設成視窗物件的 handle,且此視窗必須包含之前lpfnWndProc 所指定訊息處理函式。
HICON hIcon
指定視窗類別的 icon handle。此欄必須設成 icon 的resource。若設成 NULL 的話,系統會自動選用一個預設的 icon。
HCURSOR hCursor
指定視窗類別的 cursor handle。此欄必須設成 cursor 的resource。若設成 NULL 的話,當游標移入視窗時,你的程式必須自行設定游標的形狀。
HBRUSH hbrBackground;
指定背景筆刷(background brush)的 handle。此筆刷用來塗抹視窗的背景圖案或顏色。
LPCTSTR lpszMenuName
設為一個 C 型態的字串,此字串是視窗類別的 menu resource 名稱。若視窗類別沒用到 menu 的話,可以把此欄設成 NULL。
LPCTSTR lpszClassName
設為一個 C 型態的字串,此字串指定視窗類別的名稱。
-
17
HICON hIconSm
指定 small icon 的 handle。若設成 NULL 的話,系統會用之前 hIcon 指定的 icon 來產生 small icon。
以下是 wndclass 變數的一個設定範例:
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)
{char szAppName[] = "HelloWin";WNDCLASSEX wndclass;
wndclass.cbsize = sizeof(WNDCLASSEX);wndclass.style = CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc = WndProc;wndclass.cbClsExtra = 0;wndclass.cbWndExtra = 0;wndclass.hInstance = hInstance;wndclass.hIcon = LoadImage (0, IDI_APPLICATION, IMAGE_ICON, 0, 0, 0); wndclass.hCursor = LoadImage (0, IDC_ARROW, IMAGE_CURSOR, 0, 0, 0);wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);wndclass.lpszMenuName = NULL ;wndclass.lpszClassName = szAppName;wndclass.hIconSm = 0;
-
18
LoadImage
The LoadImage function loads an icon, cursor, animated cursor, or bitmap.
HANDLE LoadImage (
HINSTANCE hinst, // handle to instanceLPCTSTR lpszName, // name or identifier of the imageUINT uType, // image typeint cxDesired, // desired widthint cyDesired, // desired heightUINT fuLoad // load options
);
註冊視窗類別
RegisterClassEx (&wndclass);
完成視窗類別結構的設定後,接下來要註冊這個視
窗類別:
-
19
建立視窗物件
hwnd = CreateWindowEx (0, // extended window styleszAppName, // window class name"The Hello Program", // window captionWS_OVERLAPPEDWINDOW, // window styleCW_USEDEFAULT, // initial x positionCW_USEDEFAULT, // initial y positionCW_USEDEFAULT, // initial x sizeCW_USEDEFAULT, // initial y sizeNULL, // parent window handleNULL, // window menu handlehInstance, // program instance handleNULL) ; // creation parameters
CreateWindowEx
The CreateWindowEx function creates an overlapped, pop-up, or child window with an extended window style; otherwise, this function is identical to the CreateWindow function.
HWND CreateWindowEx (DWORD dwExStyle, // extended window styleLPCTSTR lpClassName, // registered class nameLPCTSTR lpWindowName, // window nameDWORD dwStyle, // window styleint x, // horizontal position of windowint y, // vertical position of windowint nWidth, // window widthint nHeight, // window heightHWND hWndParent, // handle to parent or owner windowHMENU hMenu, // menu handle or child identifierHINSTANCE hInstance, // handle to application instanceLPVOID lpParam // window-creation data
);
-
20
顯示視窗
建立視窗物件之後,我們用下面兩個函式把視窗顯示
在螢幕上:
ShowWindow (hwnd, iCmdShow) ;UpdateWindow (hwnd) ;
ShowWindow 把視窗 hwnd 放到螢幕上, iCmdShow是 WinMain 接收到的視窗顯示方式的參數。
UpdateWindow 送 WM_PAINT 訊息到視窗訊息處理函式,藉此來繪製視窗的內部區域。
訊息迴圈
while (GetMessage (&msg, NULL, 0, 0)){
TranslateMessage (&msg) ;DispatchMessage (&msg) ;
}
顯示視窗之後,程式就進入下列的訊息迴圈:
GetMessage 取出下一個訊息的資料並擺入參數mgs 中。若事件是 WM_QUIT 的話,函式傳回 0,而終止迴圈。
TranslateMessage 把鍵盤的按鍵碼轉換成字元碼。
DispatchMessage 呼叫視窗的訊息處理函式。
-
21
typedef struct tagMSG {HWND hwnd; // window handleUINT message; // 訊息型態WPARAM wParam; // 訊息資料(依訊息型態而定)LPARAM lParam; // 訊息資料(依訊息型態而定)DWORD time; // 訊息發生的時間POINT pt; // 發生訊息時游標在螢幕上的位置
} MSG;
變數 msg 的資料型態為底下的 MSG 結構:
typedef struct tagPOINT {LONG x; // x 座標LONG y; // y 座標
} POINT, *PPOINT;
訊息處理函式
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{ switch (message){case WM_CREATE:
// do some initialization when window is createdreturn 0 ;
case WM_DESTROY:PostQuitMessage (0); return 0;
// handle other messages}return DefWindowProc (hwnd, message, wParam, lParam) ;
}
-
22
視窗的訊息處理函式必須宣告成以下的格式:
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(你可改用其他函式名稱,不一定要用 WndProc 這名稱)你不需要在程式中呼叫 WndProc 函式,因為 DispatchMessage
函式會自動呼叫它,而且傳入適當的參數值。
在 WndProc 中,我們用 switch 敘述,依據訊息的型態(message),分別做適當的處理。在 case 敘述中,我們用Windows 的訊息常數。這些常數都以 WM_ 開頭(WM 代表Windows Message)。訊息常數是定義在 winuser.h 中(windows.h 會自動加入這個檔)。
WM_CREATE
呼叫 CreateWindowEx 函式會產生這個訊息。你可以在這裏加入視窗初始化的程式碼,然後 return 0。若初始化失敗,則可以 return (–1) 來終止視窗。通常這是程式所收到
的第一個訊息。
WinMain (…){
…CreateWindowEx(…);…
}
WndProc (…){
switch (message) {case WM_CREATE:
// winodw initializationreturn 0;
…}
}
-
23
WM_DESTROY
這個訊息發生在視窗終止時(例如使用者關閉視窗)。通
常我們在這裏呼叫 PostQuitMessage (0) 送出 WM_QUIT
訊息來終止事件迴圈。
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
WndProc (…){
switch (message) {case WM_DESTROY:
PostQuitMessage(0);return 0;
…}
}
寫一般簡單的 Windows 程式時,你的主要工作就是在 WndProc
中加入適當的程式碼來處理種種的訊息。
return DefWindowProc (hwnd, message, wParam, lParam) ;
Windows 有上百個不同的訊息型態。你可以把不需要特別處理的訊息交給 DefWindowProc 函式來處理。所以 WndProc 最
後一行的敘述如下:
-
24
#include
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)
{char szAppName[] = "HelloWin";HWND hwnd ;MSG msg ;WNDCLASSEX wndclass ;
wndclass.cbSize = sizeof(WNDCLASSEX) ;wndclass.style = CS_HREDRAW | CS_VREDRAW ;wndclass.lpfnWndProc = WndProc ;wndclass.cbClsExtra = 0 ;wndclass.cbWndExtra = 0 ;wndclass.hInstance = hInstance ;wndclass.hIcon = LoadImage (0, IDI_APPLICATION, IMAGE_ICON, 0, 0, 0);
視窗程式的骨架:
wndclass.hCursor = LoadImage (0, IDC_ARROW, IMAGE_CURSOR, 0, 0, 0);wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;wndclass.lpszMenuName = NULL ;wndclass.lpszClassName = szAppName ;wndclass.hIconSm = 0 ;
RegisterClassEx (&wndclass);
hwnd = CreateWindowEx (0,szAppName, // window class name“The Hello Program", // window captionWS_OVERLAPPEDWINDOW, // window styleCW_USEDEFAULT, // initial x positionCW_USEDEFAULT, // initial y positionCW_USEDEFAULT, // initial x sizeCW_USEDEFAULT, // initial y sizeNULL, // parent window handleNULL, // window menu handlehInstance, // program instance handleNULL) ; // creation parameters
-
25
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){
switch (message){case WM_CREATE:
return 0 ; case WM_DESTROY:
PostQuitMessage (0) ;return 0 ;
}return DefWindowProc (hwnd, message, wParam, lParam) ;
}
top related