25
1 Windows 視窗程式設計 1靜宜大學資訊管理學系 蔡奇偉 副教授 大綱 Windows 視窗系統的特性 Windows API MSDN 線上說明文件 匈牙利(Hungarian)命名法 一個最少行的 Windows 視窗程式 Windows 程式的事件處理模型 視窗程式的骨架

Windows 視窗程式設計 1 - cs.pu.edu.twtsay/course/gameprog/slides/gp2.pdf · 2 Windows 視窗系統的特性 圖形化的人機介面 圖形顯示器 視窗 滑鼠+ 鍵盤 Multiprocessing

  • 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) ;

    }