鼠标和触摸屏
和桌面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
#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
#include
#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,但没有更完整的讨论操作系统的基础组件--窗口。现在是时候更进一部的学习窗口、子窗口和控件的时候了。