Upload
others
View
13
Download
0
Embed Size (px)
Citation preview
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) ;
}