[翻譯]-WinCE 程序設計 (3rd 版)--1.6 Hello3

Hello3
回顧的夠多了,是時候做一個完整的Windows 應用程序--Hello3了。雖然Hello3的整個程序文件以及書中全部例子都可以在附書光盤裏找到,但我還是建議,對於初期的例子,您應當避免簡單的從CD上裝載工程文件,而是應該手工輸入整個例子。通過這種略微有些枯燥的工作,你會體會到標準Win32程序與Windows CE程序之間在開發過程的不同以及在程序上的細微差別。清單1-3給出了Hello3的全部源代碼。

清單1-3:程序Hello3
Hello3.cpp
//======================================================================
// Hello3 - A simple application for Windows CE
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h>                 // For all that Windows stuff
  
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
  
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc;
    HWND hWnd;
    MSG msg;
  
    // Register application main window class.
    wc.style = 0;                             // Window style
    wc.lpfnWndProc = MainWndProc;             // Callback function
    wc.cbClsExtra = 0;                        // Extra class data
    wc.cbWndExtra = 0;                        // Extra window data
    wc.hInstance = hInstance;                 // Owner handle
    wc.hIcon = NULL,                          // Application icon
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor
    wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
    wc.lpszMenuName =  NULL;                  // Menu name
    wc.lpszClassName = TEXT("MyClass");       // Window class name
  
    if (RegisterClass (&wc) == 0) return -1;
  
    // Create main window.
    hWnd = CreateWindowEx(WS_EX_NODRAG,       // Ex style flags
                          TEXT("MyClass"),    // Window class
                          TEXT("Hello"),      // Window title
                          // Style flags
                          WS_VISIBLE | WS_CAPTION | WS_SYSMENU,
                          CW_USEDEFAULT,      // x position
                          CW_USEDEFAULT,      // y position
                          CW_USEDEFAULT,      // Initial width
                          CW_USEDEFAULT,      // Initial height
                          NULL,               // Parent
                          NULL,               // Menu, must be null
                          hInstance,          // Application instance
                          NULL);              // Pointer to create
                                              // parameters
    if (!IsWindow (hWnd)) return -2;  // Fail code if not created.
  
    // Standard show and update calls
    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);
  
    // Application message loop
    while (GetMessage (&msg, NULL, 0, 0)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
    // Instance cleanup
    return msg.wParam;
}
//======================================================================
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
                              LPARAM lParam) {
    PAINTSTRUCT ps;
    RECT rect;
    HDC hdc;
  
    switch (wMsg) {
    case WM_PAINT:
        // Get the size of the client rectangle
        GetClientRect (hWnd, &rect);
  
        hdc = BeginPaint (hWnd, &ps);
        DrawText (hdc, TEXT ("Hello Windows CE!"), -1, &rect,
                  DT_CENTER | DT_VCENTER | DT_SINGLELINE);
  
        EndPaint (hWnd, &ps);
        return 0;
  
    case WM_DESTROY:
        PostQuitMessage (0);
        break;
    }
    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}


Hello3展示了Windows程序的各個方面,從註冊窗口類到創建窗口及窗口過程。和頭兩個例子一樣,Hello3有着相同的入口點--WinMain。但是因爲Hello3創建了自己的窗口,所以它必須爲主窗口註冊一個窗口類,創建窗口並且提供一個消息循環來爲窗口處理消息。

註冊窗口類
在WinMain中,Hello3爲主窗口註冊了窗口類。註冊一個窗口類只是簡單的填充一個描述窗口類的有些大的結構並調用RegisterClass函數。RegisterClass和WNDCLASS結構定義如下:

ATOM RegisterClass (const WNDCLASS *lpWndClass);
  
typedef struct _WNDCLASS {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HANDLE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
} WNDCLASS;

給WNDCLASS結構各個域賦的值爲Hello3主窗口的所有實例定義了行爲表現。style域爲窗口設置了類的風格。在Windows CE中,類風格被限制爲:
CS_GLOBALCLASS  表示類是全局的。這個標誌只是出於兼容性才提供的,因爲Windows CE中所有窗口類都是進程級全局類。
CS_HREDRAW      告訴系統如果窗口改變了水平大小,就強制重畫窗口。
CS_VREDRAW      告訴系統如果窗口改變了垂直大小,就強制重畫窗口。
CS_NOCLOSE      如果[關閉]按鈕出現在標題欄上,則使其失效。
CS_PARENTDC 讓窗口使用父窗口的設備環境變量
CS_DBLCLKS      允許[雙擊]通知(Windows CE下敲擊兩次爲雙擊)傳遞給父窗口

lpfnWndProc分配的是窗口的窗口過程的地址。因爲該域定義爲指向窗口過程的指針,所以在源代碼中,必須在域被設置之前,定義該過程的聲明。否則,編譯器類型檢查時會警告該行。


cbClsExra允許程序員爲類結構增加額外的空間來存儲只有應用程序才知道的類特定數據。cbWndExtra更加便於使用,這個域爲Windows內部結構增加空間,該結構負責維護窗口每個實例的狀態。不在窗口結構本身裏存儲大量的數據,應用程序應該存儲一個指向應用程序特定結構的指針,該結構包含窗口每個實例的數據。在Windows CE裏,cbClsExtra 和cbWndExtra域必須時4字節的倍數。


hInstance域設置爲程序的實例句柄,該句柄指明擁有窗口的進程。hIcon域設置爲窗口默認圖標的句柄,但在Windows CE中並不支持該域,所以該域應該設置爲NULL。(在Windows CE中,會在類的第一個窗口被創建後設置類的圖標。對於Hello3,沒有圖標提供,並且與其它Windows版本不同,Windows CE中沒有任何預定義圖標用於裝載。)

除非應用程序是爲帶鼠標的Windows CE系統設計的,否則hCursor域應該設置爲NULL。幸運的是,如果系統不支持光標,調用LoadCursor (IDC_ARROW) 函數會返回NULL。

hbrBackground域規定Windows CE如何畫窗口背景。Windows用這個域中指定的刷子brush(一個小的預定義的像素數組)來畫窗口背景。Windows CE提供許多預定義的刷子,你可以用GetStockObject函數來裝載。如果hbrBackground域是NULL,窗口必須處理WM_ERASEBKGND消息,重畫窗口背景。

lpszMenuName域必須設置爲NULL,因爲Windows CE不直接支持有菜單的窗口。在Windows CE中,菜單由主窗口創建的命令工具條、命令帶或菜單條控件提供。

lpszClassName設置爲程序員定義的字符串,用於爲Windows指明類的名字。Hello3用的是“MyClass”做爲類名。

整個WNDCLASS類被填充後,RegisterClass函數被調用,並用指向WNDCLASS結構的指針作爲唯一的參數。如果函數成功,一個標記窗口類的值被返,如果失敗,函數返回0。

創建窗口
一旦窗口類註冊成功,就可以創建主窗口了。所有Windows程序員在他們的我Windows編程生涯裏都會學習到CreateWindow和CreateWindowEx函數調用。CreateWindowEx 的原型如下:
HWND CreateWindowEx (DWORD dwExStyle, LPCTSTR lpClassName,
                     LPCTSTR lpWindowName, DWORD dwStyle,
                     int x, int y, int nWidth, int nHeight,
                     HWND hWndParent, HMENU hMenu,
                     HINSTANCE hInstance, LPVOID lpParam);

雖然參數數量讓人畏懼,但是一旦你瞭解了這些參數,會發現它們很有邏輯性。第1個參數是擴展風格標誌位。Windows CE能夠支持的擴展風格標誌如下:
WS_EX_TOPMOST  窗口置頂
WS_EX_WINDOWEDGE  窗口有凸起的邊框
WS_EX_CLIENTEDGE  窗口有凹陷的邊框
WS_EX_STATICEDGE  靜態窗口具有3D外觀
WS_EX_OVERLAPPEDWINDOW  是WS_EX_WINDOWEDGE 和WS_EX_CLIENTEDGE兩個風格的組合
WS_EX_CAPTIONOKBUTTON  在標題欄上有OK按鈕
WS_EX_CONTEXTHELP      在標題欄上有幫助按鈕
WS_EX_NOACTIVATE       點擊窗口時,窗口不成爲活動窗口
WS_EX_NOANIMATION      當窗口創建的時候,頂層窗口沒有彈出矩形,在任務條上也沒有按鈕。
WS_EX_NODRAG        防止窗口被移動

參數dwExStyle是CreateWindowEx和CreateWindow之間唯一有差別的地方。實際上,如果你在Windows CE頭文件裏看CreateWindow的聲明,會發現CreateWindow只是簡單的將dwExStyle設置爲0並調用CreateWindowEx而已。

第2個參數是實例化窗口所使用的窗口類的名字。在Hello3裏,類名是MyClass,也就是用RegisterClass註冊的類的名字。

第3個參數用做窗口文本。在Windows其它版本里,它是用做標準窗口標題欄裏的文字。在H/PC裏,主窗口很少有標題欄,這個文字只用在任務條按鈕上。在Pocket PC裏,這個文字出現在顯示屏頂部導航條裏。文字使用TEXT宏,確保字符串在Windows CE下可以轉換成Unicode。

風格標誌規定了窗口的初始風格。風格標誌即用於系統裏所有相關窗口的普通風格,也用於特定類的風格,比如按鈕類或者列表框類的風格。在這種情況下,我們需要指定的就是用WS_VISIBLE標誌來指明窗口在創建時可視。經驗豐富的Win32程序員應該查閱關於CreateWindow的文檔,因爲許多窗口風格標誌在Windows CE下並不支持。

接下來的四個參數指定了窗口初始位置和大小。因爲Wimdows CE下大部分應用程序都是全屏窗口,所以每一個大小和位置域都使用CW_USEDEFAULT標誌作爲默認值。在Windows CE當前版本里,默認值創建的窗口,其大小覆蓋整個屏幕。注意不要爲Windows CE設備假設任何特殊的尺寸,因爲不同的實現有不同的屏幕尺寸。

下一個域填寫爲父窗口的句柄。因爲是頂層窗口,所以父窗口域設置爲NULL。菜單域也設置爲NULL,因爲Windows CE不支持頂層窗口菜單。

hInstance參數就是傳遞給程序的實例句柄。窗口創建中實例句柄總會用的上,實例句柄是在程序開始時就被存儲的。最後的參數是一個指針,在WM_CREATE消息期間,用於在CreateWindow調用時傳遞數據給窗口過程。本例中,沒有額外數據需要傳遞,所以該參數設置爲NULL。


如果創建窗口成功,CreateWindow返回剛創建的窗口的句柄,如果函數調用期間有錯誤發生,則返回0。在通過錯誤檢查語句--if語句之後,該窗口句柄用在接下來的兩個語句裏(ShowWindow 和UpdateWindow裏)。ShowWindow函數修改窗口狀態,使其同傳給WinMain的參數nCmdShow中給的狀態一致。UpdateWindow函數則強制Windows發送WM_PAINT消息給剛創建的窗口。

消息循環
主窗口創建後,WinMain進入消息循環,這是每個Windows 應用程序的心臟。Hello3的消息循環定義如下:
while (GetMessage (&msg, NULL, 0, 0)) {
    TranslateMessage (&msg);
    DispatchMessage (&msg);
}

該循環很簡單:調用GetMessage函數,從應用程序消息隊列中獲取下一個消息。如果沒有消息可用,則調用進入等待期,阻塞應用程序線程直到消息可用。當消息可用,該函數返回包含在MSG結構的消息數據。MSG結構自身包含幾個域,有的用於識別消息,有的提供特定消息參數,有的識別在消息被髮送之前,被筆觸摸過的最後屏幕位置點。該位置信息不同於標準Win32消息位置數據,在XP下,返回的位置是當前鼠標位置而不是最後點擊(或者tapped,在Windows CE裏)的位置。

TranslateMessage 把適當的鍵盤信息轉換成字符信息。(後面會討論其它信息過濾器,比如IsDialogMsg。)DispatchMessage 接下來告訴Windows把消息發給應用程序適當的窗口。

獲取消息、轉換消息、分發消息這個過程會一直循環到GetMessage 收到WM_QUIT消息,這會使GetMessage返回0,這一點不同於其它消息。從while子句可以看出,GetMessage返回0將導致循環終止。

消息循環終止後,程序除了清理和退出外幾乎不做什麼。在Hello3裏,程序簡單的從WinMain中返回。WinMain的返回值成爲程序的返回碼。傳統上,最後一個消息WM_QUIT的wParam參數值包含有返回值。爲了響應應用程序對PostQuitMessage的調用,WM_QUIT消息被髮送出去,此時WM_QUIT的wParam參數值被填充。


窗口過程
發送或提交(send 或 post方式)到Hello3主窗口的消息被送到MainWndProc過程中。和所有窗口過程一樣,MainWndProc原型如下:
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
                              LPARAM lParam);

返回值類型LRESULT 實際上就是long型(在Windows裏long是一個32位值),寫成這種形式是爲源代碼和機器之間提供一箇中間級。雖然你可以輕易的從包含文件中確定Windows編程時使用的變量的真實類型,但當你試圖把代碼做跨平臺的轉換時會產生問題。雖然瞭解變量類型的大小對計算內存使用是有用的,但沒有什麼好的理由去使用(實際上有很多不使用的理由)windows.h文件中提供的類型定義。

CALLBACK 類型指明該函數是EXE的外部入口點,這是Windows直接調用該過程所必須的。在桌面系統裏,CALLBACK 指出參數是按類Pascal風格從右到左方式壓進程序棧的,這和標準C語言方式相反。爲外部入口點使用Pascal語言棧框架的原因可以追朔到Windows開發非常早的時期。使用固定大小、Pascal棧方式,意味着由被調用的過程來清理棧,而不是留給調用者來清理。這種方式可以有效的減少Windows及其附屬程序的大小,所以早期的微軟開發者認爲這是一個好的方式。在Windows CE裏,應用程序對所有函數都使用C棧框架,不管是否是外部調用。

傳給窗口過程的第一個參數是窗口句柄,當您需要定義具體的窗口實例的時候,這個句柄是很有用的。wMsg參數表示發給窗口的消息。這不是WinMain消息循環裏使用的MSG結構,而是一個包含消息值的unsigned整型。剩餘兩個參數,wParam 和lParam, 傳遞和具體消息有關的數據給窗口過程。它們的名字來自Win16時代,那時wParam是個16位值而lParam是32位值。同其它Win32操作系統一樣,在Windows CE裏,兩個都是32位的。

和傳統的窗口過程一樣,Hello3的窗口過程通過一個switch語句解析wMsg消息ID。該switch語句包含2個case語句,一個用來解析WM_PAINT消息,另一個用來解析WM_DESTROY消息。這個窗口過程大概是窗口過程所能簡化到及至的一個窗口過程了。

WM_PAINT
繪製窗口,處理WM_PAINT消息,這在任何Windows 程序中都是很重要的功能之一。窗口的外觀是在程序處理WM_PAINT消息的過程中完成的。除了用您在註冊窗口類時指定的刷子繪製默認背景外,Windows對處理該消息不提供任何幫助。Hello3中處理WM_PAINT消息如下:
case WM_PAINT:
    // Get the size of the client rectangle
    GetClientRect (hWnd, &rect);
  
    hdc = BeginPaint (hWnd, &ps);
    DrawText (hdc, TEXT ("Hello Windows CE!"), -1, &rect,
              DT_CENTER | DT_VCENTER | DT_SINGLELINE);
  
    EndPaint (hWnd, &ps);
    return 0;
在窗口繪製之前,程序必須確定窗口大小。在Windows程序裏,一個標準窗口被劃分爲兩個區域--非客戶區和客戶區。窗口標題欄和可變大小的邊框通常佔據了窗口的非客戶區,這個區域由Windows負責繪製。客戶區屬於窗口的內部區域,由應用程序負責繪製。應用程序通過調用GetClientRect 函數來確定客戶區的大小和位置。該函數返回一個RECT結構,包含左上角、右下角座標等描述客戶區矩形邊界的信息。分成客戶區和非客戶區的好處是,應用程序不必繪製那些窗口標準元素,例如標題欄。

其它版本的Windows提供一系列WM_NCxxx消息,允許您的應用程序繪製非客戶區。在Windows CE裏,窗口很少有標題欄。因爲很少有非客戶區,所以Windows CE不發送非客戶端消息給窗口過程。

WM_PAINT消息裏執行的所有繪製工作都必須由兩個函數BeginPaint 和EndPaint包圍。BeginPaint 函數返回設備環境句柄HDC。設備環境是物理顯示設備(例如視頻顯示器或打印機)的邏輯代表。Windows程序從不直接修改顯示硬件。相反,Windows用設備環境將程序與具體硬件隔離開。

BeginPaint 填充一個PAINTSTRUCT結構,其結構如下:
typedef struct tagPAINTSTRUCT {
    HDC  hdc;
    BOOL fErase;
    RECT rcPaint;
    BOOL fRestore;
    BOOL fIncUpdate;
    BYTE rgbReserved[32];
} PAINTSTRUCT;

hdc就是BeginPaint函數返回的句柄。fErase指出窗口過程是否需要重畫窗口背景。rcPaint是RECT結構,定義了需要重畫的客戶區。Hello3忽略該域,並假設在每個WM_PAINT消息中,整個客戶區窗口都需要重畫。當性能是需要考慮的問題時,該域是很有用的,因爲有時僅僅需要重畫部分窗口即可。即使當程序嘗試重畫rcPaint矩形以外的區域時,Windows也會阻止這麼做的。該結構的其它域,fRestore, fIncUpdate, 和rgbReserved,屬於Windows內部使用,應用程序可以忽略掉它們。

Hello3中唯一的繪製工作是在窗口繪製一行文本。Hello3調用DrawText函數來完成該繪製。我將在第2章描述DrawText的細節,如果您看一下該函數,很容易會明白這個調用在窗口上繪製了一行字符串“Hello Windows CE”。在DrawText返回後,調用EndPaint來通知Windows程序已經完成了窗口更新。

EndPaint調用同時也使沒有被繪製的窗口其它區域有效。Windows保持一份無效窗口區域(也就是需要重畫的區域)列表和有效區域(也就是已經更新的區域)列表。不論您是否在窗口畫了什麼,通過成對的調用BeginPaint和EndPaint,會通知Windows由您來處理窗口的無效區域。實際上,您必須調用BeginPaint和EndPaint,或者通過其它方式使窗口無效區域變有效,否則Windows會不斷髮送WM_PAINT消息給窗口,直到無效區域變有效。

WM_DESTROY
Hello3中處理的另一個消息是WM_DESTROY。當窗口即將被銷燬時,該消息被送出。因爲該窗口是應用程序主窗口,當窗口被銷燬時應用程序將終止。處理WM_DESTROY消息的代碼調用PostQuitMessage消息來觸發該動作。PostQuitMessage函數將WM_QUIT消息放到到消息隊列裏。該函數的參數是返回碼的值,該值放在WM_QUIT消息的wParam參數裏傳回應用程序。

如前所述,消息循環看到WM_QUIT消息就會退出循環。WinMain 接着調用TermInstance,在Hello3裏,該函數什麼也不做,只是返回。WinMain 接着返回,並終止程序。

Hello3是典型的Windows程序。這種編程風格有時也成爲Petzold式Windows編程,這是爲了向Charles Petzold,這位Windows編程大師表示敬意。Charles所著的《Windows 程序設計》(Programming Microsoft Windows )當前是第5版,並且依然是學習Windows編程的最好的書。

我更喜歡爲我的Windows程序選擇略微不同的設計。從某種意義上講,這是一個將Windows程序功能組件化的方法,這使的更加容易將程序的一部分複製到另一個程序。在本章最終的例子裏,我介紹這種編程風格,並一起介紹一些Windows CE應用程序所必須的額外特性。

發佈了2 篇原創文章 · 獲贊 1 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章