[翻譯]-WinCE 程序設計 (3rd 版)--3.2 鼠標和觸摸屏

鼠標和觸摸屏
和桌面PC不同,Windows CE設備並不總是有鼠標的。作爲替代,許多Windows CE設備都有觸摸屏和手寫筆。但對有鼠標的Windows CE系統來說,編程接口和桌面系統是一樣的。

鼠標消息
鼠標光標無論在什麼時候移過屏幕,光標下的最頂層窗口都會收到一個WM_MOUSEMOVE消息。如果用戶點鼠標左鍵或者右鍵,窗口會收到WM_LBUTTONDOWN或WM_RBUTTONDOWN消息;而當用戶釋放按鍵時,窗口則會收到WM_LBUTTONUP或WM_RBUTTONUP消息。如果用戶按下並釋放鼠標滾輪,窗口會收到WM_MBUTTONDOWN及WM_MBUTTONUP消息。

對所有這些消息,wParam和lParam都具有相同的值。wParam包含一個標誌位集合,用來指出當前鍵盤上Ctrl或Shift鍵是否被按下。同Windows 的其它版本一樣,在這些消息裏沒有提供Alt鍵的狀態。要想獲得消息發送時Alt鍵的狀態,可以使用GetKeyState函數。

lParam包含兩個16位的值,用來指出點擊點在屏幕上的位置。低16位是相對窗口客戶區左上角的x(水平)座標位置,高16位是y(垂直)座標位置。

如果用戶雙擊,也就是在預定義的時間內在屏幕同一位置點兩次,Windows會向被雙擊的窗口發送WM_LBUTTONDBLCLK消息,不過只有當窗口類註冊了CS_DBLCLKS風格時纔會這麼做。可以在用RegisterClass註冊窗口類時設置類風格。

您可以通過對比發送給窗口的消息來區分單擊和雙擊。當雙擊發生時,窗口首先收到來自最初單擊的WM_LBUTTONDOWN和WM_LBUTTONUP消息。

接下來一個WM_LBUTTONDBLCLK消息會在WM_LBUTTONUP後發出。一個技巧是,禁止用任何方式響應WM_LBUTTONDOWN消息,因爲這會阻止隨後的WM_LBUTTONDBLCLK消息。通常來說這沒什麼問題,因爲單擊通常是選擇一個對象,而雙擊則是啓動一個對象的默認行爲。

如果用戶滾動鼠標輪,窗口會收到WM_MOUSEWHEEL消息。對於該消息,lParam的內容和其它鼠標消息的內容一樣,都是鼠標光標的水平和垂直位置。wParam的低字位也是同樣的位標誌,指出當前被按下的鍵。但wParam的高字位則包含的是滾輪的距離,用常量WHEEL_DELTA的倍數來表示滾動的距離。如果該值爲正,表示滾輪是朝遠離用戶方向滾動;如果該值爲負,表示滾輪是朝用戶方向滾動。

使用觸摸屏
觸摸屏和手寫筆這一組合對Windows平臺來說相對是比較新的,但幸運的是,要把它們集成到Windows CE應用程序裏相對還是比較容易的。處理手寫筆的最好方法就是把它看成是一個單鍵鼠標。手寫筆產生的鼠標消息,同其它版本的Windows以及有鼠標的Windows CE裏鼠標提供的消息相同。鼠標和手寫筆的不同在於這兩種輸入設備的物理實體的不同。

和鼠標不同,手寫筆沒有光標來指示其當前位置。因此,手寫筆不能像鼠標光標那樣在屏幕的一個點上盤旋。當用戶將光標移過一個窗口而不按鼠標鍵的話,光標就會盤旋。這個概念不適用於手寫筆編程,因爲當手寫筆不和屏幕接觸的時候,觸摸屏是檢測不到手寫筆的位置的。

手寫筆和鼠標之間的差異帶來的另一個後果是:沒有鼠標光標,那麼應用程序不能通過改變盤旋光標的外貌來給用戶提供反饋。基於觸摸屏的Windows CE系統爲這種典型的Windows反饋方式提供了光標設置功能。提示用戶必須等待系統完成處理的沙漏光標,在Windows CE下得到支持,應用程序可以通過SetCursor函數來顯示繁忙的沙漏,這和其它版本的Windows裏的應用程序使用的方式一樣。

手寫筆消息
當用戶用手寫筆在屏幕上壓觸時,壓觸點下的頂層窗口如果此前沒有輸入焦點的話就會收到焦點,隨後收到WM_LBUTTONDOWN消息。當用戶擡起手寫筆時,窗口會收到WM_LBUTTONUP消息。在手寫筆按下的同時在同一個窗口內移動它,窗口就會收到WM_MOUSEMOVE消息。

電子墨水
對手持設備來說最典型的應用是捕捉屏幕上用戶在的筆跡並存儲下來。這個過程不是手寫識別,只是簡單的墨跡存儲。在開始階段,完成這個功能的最好方法應該是存儲由WM_MOUSEMOVE消息傳入的手寫筆的各個點。但有個問題,就是有時候這些小型CE設備不能快速的發送消息,導致不能獲得滿意的分辨率。因此在Windows CE下,增加了一個函數來幫助程序員追蹤手寫筆。
BOOL GetMouseMovePoints (PPOINT pptBuf, UINT nBufPoints, UINT *pnPointsRetrieved);
GetMouseMovePoints返回沒有產生WM_MOUSEMOVE消息的手寫筆點數。函數參數包括點數組、數組大小和一個指向整數的指針,用來接收返回給應用程序的點數。一旦接收完,這些附加的點可以用來填充上一個WM_MOUSEMOVE消息和當前WM_MOUSEMOVE消息之間的空白。

GetMouseMovePoints產生一條曲線。它是按觸摸板的分辨率返回點的,而不是按屏幕的。觸摸板的分辨率通常設置爲屏幕分辨率的4倍,所以您需要把GetMouseMovePoints返回的座標除以4來轉換成屏幕座標。額外的分辨率是用在手寫識別之類的程序中的。


在簡短的示例程序PenTrac中,演示了GetMouseMovePoints帶來的不同之處。圖3-4顯示了PenTrac窗口。注意觀察窗口上的兩條點線。上面的線是僅僅使用來自WM_MOUSEMOVE的點繪製的。底下的線則是包括了用GetMouseMovePoints查詢到的點,其中黑色點是來自WM_MOUSEMOVE,而紅色(淺色)點是來自GetMouseMovePoints。
圖3-4(略):顯示了兩條線的PenTrac窗口
清單3-2給出了PenTrac的源代碼。該程序爲每個接收到的WM_MOUSEMOVE或WM_LBUTTONDOWN消息在屏幕上繪製一個點。如果在鼠標移動期間Shift鍵被按下,PenTrac會調用GetMouseMovePoints並把獲得的這些點顯示成紅色,用於和來自鼠標消息的點進行區分。

爲了加強GetMouseMovePoints的效果,PenTrac做了一些手腳。在處理WM_MOUSEMOVE和WM_LBUTTONDOWN消息的DoMouseMain例程裏,調用了sleep函數來消耗掉一些毫秒時間。這個延遲模擬了那些沒有時間處理及時處理每個鼠標移動消息的響應緩慢的應用程序。

清單3-2:PenTrac程序
PenTrac.h
//======================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
// Returns number of elements.
#define dim(x) (sizeof(x) / sizeof(x[0]))
  
//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT {                             // Structure associates
    UINT Code;                                  // messages
                                                // with a function.
    LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD {                              // Structure associates
    UINT Code;                                  // menu IDs with a
    LRESULT (*Fxn)(HWND, WORD, HWND, WORD);     // function.
};
  
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
  
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
  
// Message handlers
LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoMouseMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);

PenTrac.cpp
//======================================================================
// PenTrac - Tracks stylus movement
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h>                 // For all that Windows stuff
#include "pentrac.h"                 // Program-specific stuff
  
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("PenTrac");
HINSTANCE hInst;                     // Program instance handle
  
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
    WM_LBUTTONDOWN, DoMouseMain,
    WM_MOUSEMOVE, DoMouseMain,
    WM_DESTROY, DoDestroyMain,
};
  
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {
    MSG msg;
    int rc = 0;
    HWND hwndMain;
  
    // Initialize this instance.
    hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
    if (hwndMain == 0)
        return 0x10;
  
    // Application message loop
    while (GetMessage (&msg, NULL, 0, 0)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
    // Instance cleanup
    return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitApp - Application initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc;
    HWND hWnd;
  
#if defined(WIN32_PLATFORM_PSPC)
    // If Pocket PC, allow only one instance of the application
    hWnd = FindWindow (szAppName, NULL);
    if (hWnd) {
        SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));   
        return 0;
    }
#endif
    // Save program instance handle in global variable.
    hInst = hInstance;
  
    // 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 = szAppName;             // Window class name
  
    if (RegisterClass (&wc) == 0) return 0;
  
    // Create main window.
    hWnd = CreateWindowEx (WS_EX_NODRAG, szAppName, TEXT ("PenTrac"),
                         WS_VISIBLE | WS_CAPTION | WS_SYSMENU,
                         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                         CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
    // Return fail code if window not created.
    if (!IsWindow (hWnd)) return 0;
  
    // Standard show and update calls
    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);
    return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
    return nDefRC;
}
//======================================================================
// Message handling procedures for MainWindow
//
  
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
                              LPARAM lParam) {
    INT i;
    //
    // Search message list to see if we need to handle this
    // message.  If in list, call procedure.
    //
    for (i = 0; i < dim(MainMessages); i++) {
        if (wMsg == MainMessages[i].Code)
            return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
    }
    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoMouseMain - Process WM_LBUTTONDOWN and WM_MOUSEMOVE messages
// for window.
//
LRESULT DoMouseMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                     LPARAM lParam) {
    POINT pt[64];
    POINT ptM;
    UINT i, uPoints = 0;
    HDC hdc;
  
    ptM.x = LOWORD (lParam);
    ptM.y = HIWORD (lParam);
    hdc = GetDC (hWnd);
    // If shift and mouse move, see if any lost points.
    if (wMsg == WM_MOUSEMOVE) {
        if (wParam & MK_SHIFT)
            GetMouseMovePoints (pt, 64, &uPoints);
  
        for (i = 0; i < uPoints; i++) {
            pt[i].x /= 4;  // Convert move pts to screen coords
            pt[i].y /= 4;
            // Covert screen coordinates to window coordinates
            MapWindowPoints (HWND_DESKTOP, hWnd, &pt[i], 1);
            SetPixel (hdc, pt[i].x,   pt[i].y, RGB (255, 0, 0));
            SetPixel (hdc, pt[i].x+1, pt[i].y, RGB (255, 0, 0));
            SetPixel (hdc, pt[i].x,   pt[i].y+1, RGB (255, 0, 0));
            SetPixel (hdc, pt[i].x+1, pt[i].y+1, RGB (255, 0, 0));
        }
    }
    // The original point is drawn last in case one of the points
    // returned by GetMouseMovePoints overlaps it. 
    SetPixel (hdc, ptM.x, ptM.y, RGB (0, 0, 0));
    SetPixel (hdc, ptM.x+1, ptM.y, RGB (0, 0, 0));
    SetPixel (hdc, ptM.x, ptM.y+1, RGB (0, 0, 0));
    SetPixel (hdc, ptM.x+1, ptM.y+1, RGB (0, 0, 0));
    ReleaseDC (hWnd, hdc);
  
    // Kill time to make believe we are busy.
    Sleep(25);
    return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                       LPARAM lParam) {
    PostQuitMessage (0);
    return 0;
}

輸入焦點和鼠標消息
對於如何以及何時把手寫筆產生的鼠標消息發送到不同的窗口,這個過程中涉及的相關規則是需要關注一下的。正如我前面提到的,當手寫筆壓觸到一個窗口上時,系統的輸入焦點將發生變化。但是,把手寫筆從一個窗口拖動到另一個窗口並不會使新窗口獲得輸入焦點。因爲是下壓時才設置焦點,而拖動手寫筆滑過窗口是不會設置的。當手寫筆拖動出窗口時,該窗口就停止接收WM_MOUSEMOVE消息了,但會繼續保持輸入焦點。因爲手寫筆的筆尖依然下壓,所以沒有其它窗口會接收WM_MOUSEMOVE消息了。這一點與保持鼠標按鍵按下並拖動出一個窗口很類似。

要想在手寫筆即使被移動到窗口外時繼續接收手寫筆消息,應用程序只要用接收鼠標消息的窗口句柄做參數,調用HWND SetCapture (HWND hWnd)函數就可以了。該函數返回前一個捕捉鼠標消息的窗口的句炳,如果之前沒有捕捉過則返回NULL。要停止接收手寫筆輸入產生的鼠標消息,窗口可以調用BOOL ReleaseCapture(void)函數。任何時候都只有一個窗口可以捕捉手寫筆的輸入。要判斷手寫筆是否被捕捉了,可以調用HWND GetCapture(void)函數,它返回捕捉手寫筆的窗口的句柄,如果沒有窗口捕捉手寫筆的輸入,則返回0(不過要注意一個警告:返回0只是表示該線程沒有捕捉鼠標,並不表示其它線程或進程沒有捕捉鼠標)。捕捉手寫筆的窗口必須何調用該函數的的窗口在同樣的線程環境裏。這個限制意味着,如果手寫筆被另一個應用裏的窗口捕捉了,GetCapture依然返回0。

如果一個窗口捕捉了手寫筆而另一個窗口調用了GetCapture,那麼最初捕捉手寫筆的窗口會收到一個WM_CAPTURECHANGED消息。消息的lParam參數中包含了獲得手寫筆捕捉的窗口的句柄。您不應該試圖調用GetCapture來取會捕捉。通常,因爲手寫筆是共享資源,應用程序應該小心謹慎的捕捉手寫筆一段時間,並且應該能夠優雅地處理捕捉丟失的情況。另外一個有趣的事情是:正因爲窗口捕捉了鼠標,所以它不能阻止在另一個窗口上點擊來獲得輸入焦點。您可以使用其它方法來防止輸入焦點的更換,但幾乎在所有情況下,最好是讓用戶而不是程序來決定哪個頂層窗口應該擁有輸入焦點。

點擊右鍵
在Windows系統裏,當您在一個對象上單擊鼠標右鍵,通常地會調出上下文相關的、獨立的菜單,顯示針對該具體對象能做什麼的功能項集合。在有鼠標的系統中,Windows發送WM_RBUTTONDOWN和WM_RBUTTONUP消息,指出右鍵點擊了。但是當使用手寫筆的時候,不會有右鍵。不過Windows CE指導方針中允許您使用手寫筆模擬右鍵點擊。指導方針規定,如果用戶按下Alt鍵的同時用手寫筆點擊屏幕,程序會當成是右鍵鼠標被點擊,並顯示相關的上下文菜單。在WM_LBUTTONDOWN的wParam中沒有MK_ALT標誌,所以判斷Alt鍵是否被按的最好方法是用VK_MENU做參數調用GetKeyState,並測試返回值的相關位是否被設置了。在這種情況下,GetKeyState是最合適的,因爲返回的是鼠標消息從消息隊列裏取出時的鍵的狀態。

在沒有鍵盤的系統上,採取壓下並保持這一姿勢來模擬鼠標右鍵點擊。SHRecognizeGesture函數可以用在Pocket PC和具有適當Shell組件的嵌入式Windows CE系統中,用來檢查壓下並保持這一姿勢。函數原型如下:
WINSHELLAPI DWORD SHRecongnizeGesture(SHRGINFO * shrg);
唯一的參數是一SHRGINFO結構的地址,該結構定義如下:
typedef struct tagSHRGI{
DWORD cbSize;
HWND hwndClient;
POINT ptDown;
DWORD dwFlags;
}SHRGINFO,*PSHRGINFO;

cbSize需要用結構的大小來填充。hwndClient則需要設置爲調用該函數的窗口的句柄。ptDown結構需要用識別出姿勢時的點來填充。dwFlags則包含許多標誌。SHRG_RETURNCMD標誌表示如果用戶做出正確的下壓保持姿勢,則讓函數返回GN_CONTEXTMENU;否則就返回0。SHRG_NOTIFYPARENT標誌表示如果識別出正確姿勢的話,就給父窗口發送一個WM_NOTIFY消息。SHRG_LONDELAY消息要求在識別出姿勢之前,用戶需要保持點壓一段時間。

TicTac1示例程序
爲了演示手寫筆編程,我寫了一個tic-tac-toe小遊戲。圖3-5顯示了TicTac1窗口。清單3-3顯示了程序的源代碼。這個程序並不提供人機遊戲,也不判斷遊戲結束,只是簡單繪製邊界並記錄X和O的位置。儘管如此,該程序已經演示了手寫筆的基本交互功能。
圖3-5(略):TicTac1窗口
清單3-3:TicTac1程序
TicTac1.h
//======================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT {                             // Structure associates
    UINT Code;                                  // messages
                                                // with a function.
    LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD {                              // Structure associates
    UINT Code;                                  // menu IDs with a
    LRESULT (*Fxn)(HWND, WORD, HWND, WORD);     // function.
};
  
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
  
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
  
// Message handlers
 LRESULT DoSizeMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoLButtonDownMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoLButtonUpMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);
  
// Game function prototypes
void DrawXO (HDC hdc, HPEN hPen, RECT *prect, INT nCell, INT nType);
void DrawBoard (HDC hdc, RECT *prect);

TicTac1.cpp
//======================================================================
// TicTac1 - Simple tic-tac-toe game
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//
//======================================================================
#include <windows.h>                 // For all that Windows stuff
#include <commctrl.h>                // Command bar includes
#include "tictac1.h"                 // Program-specific stuff
  
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("TicTac1");
HINSTANCE hInst;                     // Program instance handle
  
// State data for game
RECT rectBoard = {0, 0, 0, 0};       // Used to place game board.
RECT rectPrompt;                     // Used to place prompt.
BYTE bBoard[9];                      // Keeps track of X's and O's.
BYTE bTurn = 0;                      // Keeps track of the turn.
  
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
    WM_SIZE, DoSizeMain,
    WM_PAINT, DoPaintMain,
    WM_LBUTTONUP, DoLButtonUpMain,
    WM_DESTROY, DoDestroyMain,
};
  
//======================================================================
//
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {
    MSG msg;
    HWND hwndMain;
  
    // Initialize this instance.
    hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
    if (hwndMain == 0)
        return 0x10;
    // Application message loop
    while (GetMessage (&msg, NULL, 0, 0)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
    // Instance cleanup
    return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc;
    HWND hWnd;
  
    // Save program instance handle in global variable.
    hInst = hInstance;
  
#if defined(WIN32_PLATFORM_PSPC)
    // If Pocket PC, allow only one instance of the application.
    hWnd = FindWindow (szAppName, NULL);
    if (hWnd) {
        SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));   
        return 0;
    }
#endif
    // 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 = szAppName;             // Window class name
  
    if (RegisterClass (&wc) == 0) return 0;
  
    // Create main window.
    hWnd = CreateWindowEx (WS_EX_NODRAG, szAppName, TEXT ("TicTac1"),
                         WS_VISIBLE | WS_CAPTION | WS_SYSMENU,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         NULL, NULL, hInstance, NULL);        
    // Return fail code if window not created.
    if (!IsWindow (hWnd)) return 0;
    // Standard show and update calls
    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);
    return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
 int TermInstance (HINSTANCE hInstance, int nDefRC) {
  
    return nDefRC;
}
//======================================================================
// Message handling procedures for MainWindow
//
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
                              LPARAM lParam) {
    INT i;
    //
    // Search message list to see if we need to handle this
    // message. If in list, call procedure.
    //
    for (i = 0; i < dim(MainMessages); i++) {
        if (wMsg == MainMessages[i].Code)
            return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
    }
    return DefWindowProc(hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoSizeMain - Process WM_SIZE message for window.
//
LRESULT DoSizeMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                    LPARAM lParam) {
    RECT rect;
    INT i;
    // Adjust the size of the client rect to take into account
    // the command bar height.
    GetClientRect (hWnd, &rect);
  
    // Initialize the board rectangle if not yet initialized.
    if (rectBoard.right == 0) {
  
        // Initialize the board.
        for (i = 0; i < dim(bBoard); i++)
            bBoard[i] = 0;
    }
    // Define the playing board rect.
    rectBoard = rect;
    rectPrompt = rect;
    // Layout depends on portrait or landscape screen.
    if (rect.right - rect.left > rect.bottom - rect.top) {
        rectBoard.left += 20;
        rectBoard.top += 10;
        rectBoard.bottom -= 10;
        rectBoard.right = rectBoard.bottom - rectBoard.top + 10;
  
        rectPrompt.left = rectBoard.right + 10;
  
    } else {
        rectBoard.left += 20;
        rectBoard.right -= 20;
        rectBoard.top += 10;
        rectBoard.bottom = rectBoard.right - rectBoard.left + 10;
  
        rectPrompt.top = rectBoard.bottom + 10;
    }
    return 0;
}
  
//----------------------------------------------------------------------
// DoPaintMain - Process WM_PAINT message for window.
//
LRESULT DoPaintMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                     LPARAM lParam) {
    PAINTSTRUCT ps;
    RECT rect;
    HFONT hFont, hOldFont;
    HDC hdc;
  
    GetClientRect (hWnd, &rect);
  
    hdc = BeginPaint (hWnd, &ps);
  
    // Draw the board.
    DrawBoard (hdc, &rectBoard);
  
    // Write the prompt to the screen.
    hFont = (HFONT)GetStockObject (SYSTEM_FONT);
    hOldFont = (HFONT)SelectObject (hdc, hFont);
    if (bTurn == 0)
        DrawText (hdc, TEXT (" X's turn"), -1, &rectPrompt,
                  DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    else
        DrawText (hdc, TEXT (" O's turn"), -1, &rectPrompt,
                  DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    SelectObject (hdc, hOldFont);
    EndPaint (hWnd, &ps);
    return 0;
}
//----------------------------------------------------------------------
// DoLButtonUpMain - Process WM_LBUTTONUP message for window.
//
LRESULT DoLButtonUpMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                         LPARAM lParam) {
    POINT pt;
    INT cx, cy, nCell = 0;
  
    pt.x = LOWORD (lParam);
    pt.y = HIWORD (lParam);
    // See if pen on board.  If so, determine which cell.
    if (PtInRect (&rectBoard, pt)){
        // Normalize point to upper left corner of board.
        pt.x -= rectBoard.left;
        pt.y -= rectBoard.top;
  
        // Compute size of each cell.
        cx = (rectBoard.right - rectBoard.left)/3;
        cy = (rectBoard.bottom - rectBoard.top)/3;
  
        // Find column.
        nCell = (pt.x / cx);
        // Find row.
        nCell += (pt.y / cy) * 3;
  
        // If cell empty, fill it with mark.
        if (bBoard[nCell] == 0) {
            if (bTurn) {
                bBoard[nCell] = 2;
                bTurn = 0;
            } else {
                bBoard[nCell] = 1;
                bTurn = 1;
            }
            InvalidateRect (hWnd, NULL, FALSE);
        } else {
            // Inform the user of the filled cell.
            MessageBeep (0);
            return 0;
        }
    }
    return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
                       LPARAM lParam) {
    PostQuitMessage (0);
    return 0;
}
//======================================================================
// Game-specific routines
//
//----------------------------------------------------------------------
// DrawXO - Draw a single X or O in a square.
//
void DrawXO (HDC hdc, HPEN hPen, RECT *prect, INT nCell, INT nType) {
    POINT pt[2];
    INT cx, cy;
    RECT rect;
  
    cx = (prect->right - prect->left)/3;
    cy = (prect->bottom - prect->top)/3;
  
    // Compute the dimensions of the target cell.
    rect.left = (cx * (nCell % 3) + prect->left) + 10;
    rect.right = rect.right =  rect.left + cx - 20;
    rect.top = cy * (nCell / 3) + prect->top + 10;
    rect.bottom = rect.top + cy - 20;
  
    // Draw an X ?
    if (nType == 1) {
        pt[0].x = rect.left;
        pt[0].y = rect.top;
        pt[1].x = rect.right;
        pt[1].y = rect.bottom;
        Polyline (hdc, pt, 2);
  
        pt[0].x = rect.right;
        pt[1].x = rect.left;
        Polyline (hdc, pt, 2);
    // How about an O ?
    } else if (nType == 2) {
        Ellipse (hdc, rect.left, rect.top, rect.right, rect.bottom);
    }
    return;
}
//----------------------------------------------------------------------
// DrawBoard - Draw the tic-tac-toe board.
//  VK_MENU
void DrawBoard (HDC hdc, RECT *prect) {
    HPEN hPen, hOldPen;
    POINT pt[2];
    LOGPEN lp;
    INT i, cx, cy;
  
    // Create a nice thick pen.
    lp.lopnStyle = PS_SOLID;
    lp.lopnWidth.x = 5;
    lp.lopnWidth.y = 5;
    lp.lopnColor = RGB (0, 0, 0);
    hPen = CreatePenIndirect (&lp);
  
    hOldPen = (HPEN)SelectObject (hdc, hPen);
  
    cx = (prect->right - prect->left)/3;
    cy = (prect->bottom - prect->top)/3;
  
    // Draw lines down.
    pt[0].x = cx + prect->left;
    pt[1].x = cx + prect->left;
    pt[0].y = prect->top;
    pt[1].y = prect->bottom;
    Polyline (hdc, pt, 2);
    pt[0].x += cx;
    pt[1].x += cx;
    Polyline (hdc, pt, 2);
  
    // Draw lines across.
    pt[0].x = prect->left;
    pt[1].x = prect->right;
    pt[0].y = cy + prect->top;
    pt[1].y = cy + prect->top;
    Polyline (hdc, pt, 2);
  
    pt[0].y += cy;
    pt[1].y += cy;
    Polyline (hdc, pt, 2);
  
    // Fill in X's and O's.
    for (i = 0; i < dim (bBoard); i++)
        DrawXO (hdc, hPen, &rectBoard, i, bBoard[i]);
  
    SelectObject (hdc, hOldPen);
    DeleteObject (hPen);
    return;
}
TicTac的行爲主要集中在3個方面:DrawBoard,DrawXO和DoLbuttonUpMain。頭兩個執行繪製遊戲棋盤的工作。判斷在棋盤上點擊位置的是DoLButtonUpMain。正如名字所暗示的,該函數是用來響應WM_LBUTTONUP消息的。首先調用PtInRect來判斷點擊是否在遊戲棋盤上。PtInRect原型如下:BOOL PtInRect(const RECT *lprc, POINT pt);程序知道點擊位置,因爲是包含在消息的lParam裏的。程序啓動時在DoSizeMain中計算了棋盤邊框矩形。一旦發現在棋盤上進行了點擊,程序會用橫縱格數分別除以點擊點在棋盤上的座標,來確定棋盤裏相關單元的位置。

前面提到過是在DoSizeMain中計算棋盤邊框矩形的,調用該例程是爲了響應WM_SIZE消息。可能會奇怪Windows CE爲什麼會支持對其它版本Windows來說是很普通的WM_SIZE消息,實際上之所以支持這個消息,是因爲窗口尺寸變化頻繁:首先是在窗口創建的時候,之後是每次最小化和恢復的時候。您可能會想,另一個可能確定窗口尺寸的地方是在WM_CREATE消息裏。lParam參數指向一個CREATESTRUCT結構,包含了窗口初始大小和位置。用這些數據的問題在於此處的大小是整個窗口的大小,而不是我們需要的客戶區的大小。在Windows CE下,大多數窗口沒有標題欄和邊框,但也有一些兩個都有並且很多都有滾動條,所以用這些數據會帶來麻煩。對TicTac1程序,我們有一個簡單有效的使用手寫筆的程序,雖然還不完整。爲了重開一局遊戲,需要退出重起TicTac1。也不能悔棋或者讓0先行。我們需要一個方法來發送這些命令給程序。當然,用按鍵是可以完成這個目標的。另一種解決方案是在屏幕上創建熱點(hot spots)來提供必要的輸入。很明顯本例需要一些額外的功能來使其完整。我已經儘可能的討論了Windows,但沒有更完整的討論操作系統的基礎組件--窗口。現在是時候更進一部的學習窗口、子窗口和控件的時候了。

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