Windows Mobile 6.5 手势 API:简介
本文简要介绍 Windows Mobile 6.5 Professional Edition 中提供的新手势 API。我们将创建一个简单的智能设备应用程序,该应用程序能够读取用户的手势,并将有关这些手势的信息报告到屏幕上。

引言
本文介绍了一个适用于 Windows Mobile 6.5 Professional Edition 的简单应用程序,这是一个熟悉 Windows Mobile 6.5 中新推出的手势 API 的有用练习。
背景
本文读者应基本了解 Win32 GUI 开发的基础知识,并对 C/C++ 有入门到中等程度的熟悉。如果您不知道什么是 HWND、窗口过程或 MSG,那么我强烈建议您阅读 MSDN 上“通用示例应用程序”下的 Windows API,或者最好能获取一本 Charles Petzold 的《Windows 编程》,并充分理解标记为“第一部分:基础”的部分。此外,您还需要安装 Visual Studio 2005 或 2008 Professional Edition,以及 Windows Mobile 6.0 和 6.5 SDK(Professional Edition),以便构建和执行代码。还假定您熟悉 Visual Studio 环境(创建项目、导航、添加文件等)。
Using the Code
首先,我们需要设置我们的环境。我们将构建一个简单的应用程序,并需要包含 Windows Mobile SDK 中的一些文件。打开 Visual Studio(我使用的是 Visual Studio 2008 Professional,但 2005 Professional 应该也可以)并创建一个新项目。选择 Visual C++ 作为语言类型,然后选择“智能设备”项目类型和“Win32 智能设备项目”模板。

给您的项目起一个名字,例如“GesturesTest
”或任何您喜欢的名字。
单击“确定”,Win32 智能设备项目向导将启动。在“概述”页面上单击“下一步”。在“平台”页面上,我们需要选择要用于开发应用程序的正确 SDK。手势 API 仅在 Windows Mobile 6.5 Professional 中可用,这需要 Windows Mobile 6 Professional SDK(请注意,Windows Mobile 6.5 Professional SDK 必须安装在您的计算机上,但在选择我们的平台时,我们必须使用 Windows Mobile 6 Professional SDK)。单击“下一步”继续。

最后,在“应用程序设置”页面上,对于“应用程序类型”,选择“Windows 应用程序”。另外,为了使此项目保持简单,请勾选“空项目”复选框,并确保 ATL 复选框未勾选。单击“完成”以完成智能设备项目向导。

现在我们有了一个现成的项目,需要添加一个代码文件以便开始编写我们的应用程序。右键单击“源文件”,然后向项目中添加一个新 .cpp 文件。我们还需要向项目中添加一些关键的库引用。右键单击项目图标并选择“属性”。在“配置属性”、“C/C++”、“常规”下,将 Windows Mobile 6 Professional SDK 的“Include”目录添加到“附加包含目录”属性。此默认路径通常是 "%Program Files%\Windows Mobile 6 SDK\PocketPC\Include\Armv4i"。

现在,在“链接器”下,我们需要添加必要的 lib 文件。首先,在“链接器”、“常规”、“附加库目录”下,我们需要添加 Windows Mobile 6 SDK Lib 文件目录。此默认路径是 "%Program Files%\Windows Mobile 6 SDK\PocketPC\Lib\Armv4i"。

最后,我们需要显式地将两个 .lib 文件添加到项目中。在“链接器”、“输入”、“附加依赖项”下,添加对 AygShell.lib 和 TouchGestureCore.lib 的引用。AygShell.lib 包含我们将在此应用程序中调用的一些 GUI API 代码。TouchGestureCore.lib 包含执行手势 API 所需的代码。

由于我们正在构建一个本机 Windows 应用程序,因此我们将从 WinMain
函数开始。本文假定您熟悉使用 Win32 API 创建 Windows GUI 应用程序等主题,因此我们将很快地回顾这部分内容,以便进入本文的重点——手势 API。
首先,添加我们的宏定义和包含文件
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <gesture.h> // The header for the gestures API
#include <aygshell.h>
#define LABEL_WIDTH 240
#define LABEL_HEIGHT 125
现在添加我们的函数原型。
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI OnCreate(HWND hwnd, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI OnGesture(HWND hwnd, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI OnColorStatic(HWND hwnd, WPARAM wParam, LPARAM lParam);
VOID WINAPI GetGestureDirection(WORD wDirection, OUT PTSTR szDirection, IN DWORD dwSize);
LRESULT WINAPI OnSize(HWND hwnd, WPARAM wParam, LPARAM lParam);
我们还有一些将在本应用程序中使用的全局变量。
HINSTANCE g_hinst = NULL;
HWND g_hwndLabel = NULL;
现在是我们的 WinMain
函数
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nShowCmd)
{
static TCHAR szAppName[] = TEXT("GesturesTest");
HWND hwnd = NULL;
MSG msg;
WNDCLASS wndClass;
ZeroMemory(&wndClass, sizeof(WNDCLASS));
g_hinst = hInstance;
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WndProc;
wndClass.hInstance = hInstance;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = CreateSolidBrush(RGB(0, 0, 0));
wndClass.lpszClassName = szAppName;
hwnd = CreateWindow(szAppName, // Window Class Name
TEXT("Gestures Test"), // Window Caption
WS_VISIBLE, // Window Style
0, // Initial X position
0, // Initial Y position
CW_USEDEFAULT, // Initial Width
CW_USEDEFAULT, // Initial Height
NULL, // Parent Window Handle
NULL, // Menu Handle
g_hinst, // Program Instance Handle
NULL); // Creation Parameters
if (hwnd)
{
ShowWindow(hwnd, nShowCmd);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (hwnd)
{
CloseHandle(hwnd);
}
UnregisterClass(szAppName, hInstance);
return msg.wParam;
}
return GetLastError();
}
接下来我们需要创建我们的窗口过程。这是与原型匹配的函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
return OnCreate(hwnd, wParam, lParam);
case WM_GESTURE:
return OnGesture(hwnd, wParam, lParam);
case WM_SIZE:
return OnSize(hwnd, wParam, lParam);
case WM_CTLCOLORSTATIC:
return OnColorStatic(hwnd, wParam, lParam);
case WM_ACTIVATE:
if (WA_INACTIVE == (DWORD)wParam)
{
return SendMessage(hwnd, WM_DESTROY, 0,0);
}
return DefWindowProc(hwnd, msg, wParam, lParam);
case WM_DESTROY:
if (g_hwndLabel)
{
CloseHandle(g_hwndLabel);
}
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
}
这应该相当直接。我们捕获了本应用程序感兴趣的各种窗口消息。请注意 WM_GESTURE
的情况。当应用程序屏幕被触摸时,会生成 WM_GESTURE
消息并发送到消息循环。我们稍后将对此进行更多讨论。您可能还会想知道我们在 WM_ACTIVATE
情况下做了什么。Windows Mobile 应用程序的默认行为是在用户按下“关闭”按钮时保持活动但非活动状态。我们的小测试应用程序不需要这种行为,因此我们正在检查带有 WA_INACTIVE
标志的 WM_ACTIVATE
消息,并在收到此消息时简单地关闭我们的程序。
我们的下一个函数是 OnCreate
函数。这将处理应用程序所需的任何初始构造。在这种情况下,我们将构建一个标签来显示我们的手势数据,以及一个(空白的)菜单栏来替换拇指按钮。
LRESULT WINAPI OnCreate(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
if (!hwnd)
{
return ERROR_INVALID_PARAMETER;
}
RECT rect;
GetClientRect(hwnd, &rect);
int iLabelLeft = (rect.right >> 1) - (LABEL_WIDTH >> 1);
int iLabelTop = (rect.bottom >> 1) - (LABEL_HEIGHT >> 1);
// Create a Text Label and center it on screen
g_hwndLabel = CreateWindowEx(0,
TEXT("STATIC"),
0,
WS_CHILD | WS_VISIBLE | SS_CENTER,
iLabelLeft,
iLabelTop,
LABEL_WIDTH,
LABEL_HEIGHT,
hwnd,
NULL,
g_hinst,
NULL);
// Create an empty menu bar for our app
SHMENUBARINFO mbi;
ZeroMemory(&mbi, sizeof(SHMENUBARINFO));
mbi.cbSize = sizeof(SHMENUBARINFO);
mbi.hwndParent = hwnd;
SHCreateMenuBar(&mbi);
DrawMenuBar(hwnd);
// Set the font
LOGFONT lf;
ZeroMemory(&lf, sizeof(LOGFONT));
StringCchCopy(lf.lfFaceName, 32, TEXT("Courier New"));
HFONT hFont = CreateFontIndirect(&lf);
SendMessage(g_hwndLabel, WM_SETFONT, (WPARAM)hFont, 0);
CloseHandle(hFont);
return ERROR_SUCCESS;
}
现在让我们构建 OnColorStatic
函数。我们将使用它来设置我们标签的颜色。请注意,您可以使用任何您喜欢的颜色,但在我的应用程序中,我选择使用纯黑色背景和绿色文本。
LRESULT WINAPI OnColorStatic(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
static HBRUSH hBrush = CreateSolidBrush(RGBA(0,0,0,0));
HWND hwndStatic = (HWND)lParam;
HDC dc = (HDC)wParam;
SetTextColor(dc, RGB(100, 255, 100));
SetBkColor(dc, RGBA(0, 0, 0, 255));
SetBkMode(dc, TRANSPARENT);
return (LRESULT)hBrush;
}
我们包含 OnSize
函数是为了处理屏幕方向的变化。我们希望我们的标签在屏幕上保持垂直和水平居中。
LRESULT WINAPI OnSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
if (g_hwndLabel)
{
RECT rect;
GetClientRect(hwnd, &rect);
int iLabelLeft = (rect.right >> 1) - (LABEL_WIDTH >> 1);
int iLabelTop = (rect.bottom >> 1) - (LABEL_HEIGHT >> 1);
MoveWindow(g_hwndLabel,
iLabelLeft,
iLabelTop,
LABEL_WIDTH,
LABEL_HEIGHT,
TRUE);
}
return DefWindowProc(hwnd, WM_SIZE, wParam, lParam);
}
我们还剩下两个函数。首先让我们看其中比较简单的 GetGestureDirection
。此函数评估一个 DWORD
值是否等同于在 gesture.h 头文件中定义的 常量。如果 DWORD
等于预定义的定向常量之一,那么 OUT
参数将包含一个对应于该方向的 string
。如果 DWORD
无法与现有常量匹配,则 OUT
参数将读取“NONE
”(请注意,也有一个与此对应的定义常量)。
VOID WINAPI GetGestureDirection(WORD wDirection, OUT TCHAR* szDirection, IN DWORD dwSize)
{
if (dwSize <= 0)
{
dwSize = 16;
}
switch (wDirection)
{
case ARG_SCROLL_RIGHT:
StringCchCopy(szDirection, dwSize, TEXT("RIGHT"));
break;
case ARG_SCROLL_UP:
StringCchCopy(szDirection, dwSize, TEXT("UP"));
break;
case ARG_SCROLL_LEFT:
StringCchCopy(szDirection, dwSize, TEXT("LEFT"));
break;
case ARG_SCROLL_DOWN:
StringCchCopy(szDirection, dwSize, TEXT("DOWN"));
break;
case ARG_SCROLL_NONE:
default:
StringCchCopy(szDirection, dwSize, TEXT("NONE"));
break;
}
return;
}
剩下的就是编写 OnGesture
函数。这是我们函数中更复杂(但并非非常复杂)的一个,需要更多解释。它包含了当用户触摸屏幕时发生的所有操作。首先让我们看一下函数原型
LRESULT WINAPI OnGesture(HWND hwnd, WPARAM wParam, LPARAM lParam);
回想一下,当我们从窗口过程中收到 WM_GESTURE
消息时,我们正在调用 OnGesture
函数。根据 MSDN,当 WndProc
收到 WM_GESTURE
消息时,wParam
包含一个对应于手势 ID 的 DWORD
,而 lParam
包含一个指向 GESTUREINFO 结构的句柄(该结构也包含手势 ID)。GESTUREINFO
结构包含我们感兴趣的数据,例如手势的坐标、方向和速度。让我们来看一下函数
LRESULT WINAPI OnGesture(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
if (!hwnd || !wParam || !lParam)
{
return ERROR_INVALID_PARAMETER;
}
GESTUREINFO gi;
ZeroMemory(&gi, sizeof(GESTUREINFO));
gi.cbSize = sizeof(GESTUREINFO);
if (TKGetGestureInfo((HGESTUREINFO)lParam, &gi))
{
int iBufferSize = 128;
PTSTR szGestureText = new TCHAR[iBufferSize];
ZeroMemory(szGestureText, sizeof(TCHAR) * iBufferSize);
switch(wParam)
{
/* No action on GID_BEGIN or GID_END events. Otherwise
all we would ever see on the screen is GID_END :-)
*/
case GID_BEGIN:
case GID_END:
return DefWindowProc(hwnd, WM_GESTURE, wParam, lParam);
break;
/* A pan event occurs when a user drags their finger(or stylus) across
the screen.
*/
case GID_PAN:
{
/* According to MSDN, a GID_PAN event is
supposed to produce direction,
angle, and velocity data when the
GF_INERTIA flag is flipped on. I
have yet to be able to reproduce this behavior.
*/
if (gi.dwFlags & GF_INERTIA)
{
WORD wDirection =
GID_SCROLL_DIRECTION(gi.ullArguments);
WORD wAngle = GID_SCROLL_ANGLE(gi.ullArguments);
WORD wVelocity =
GID_SCROLL_VELOCITY(gi.ullArguments);
PTSTR szDirection = new TCHAR[16];
ZeroMemory(szDirection, sizeof(TCHAR) * 16);
GetGestureDirection(wDirection, szDirection, 16);
StringCchPrintf(szGestureText, iBufferSize,
TEXT("GID_PAN\nX: %d, Y:
%d\nDirection: %s\nAngle:
%d\nVelocity: %d"),
gi.ptsLocation.x, gi.ptsLocation.y,
szDirection, wAngle, wVelocity);
SetWindowText(g_hwndLabel, szGestureText);
delete[] szGestureText;
if (szDirection)
{
delete[] szDirection;
}
}
else
{
StringCchPrintf(szGestureText, iBufferSize,
TEXT("GID_PAN\nX: %d, Y: %d"),
gi.ptsLocation.x, gi.ptsLocation.y);
SetWindowText(g_hwndLabel, szGestureText);
delete[] szGestureText;
}
break;
}
/* A Scroll event occurs after a user quickly flicks
their finger (or stylus) across the screen.
This is distinguishable from a Pan because a Pan
-always- occurs when a finger or stylus is dragged,
while a Scroll -only- occurs after the user releases
their finger or stylus from the screen after a Pan event,
and only if that Pan event occurs fast enough to
register as a scroll.
You'll see what I mean if you build and run the code on a touch
enabled 6.5 device. The angle values are in a raw argument
form and can be converted to radians using the
GID_ROTATE_ANGLE_FROM_ARGUMENT macro.
I elected not to do that here.
*/
case GID_SCROLL:
{
WORD wDirection = GID_SCROLL_DIRECTION(gi.ullArguments);
WORD wAngle = GID_SCROLL_ANGLE(gi.ullArguments);
WORD wVelocity = GID_SCROLL_VELOCITY(gi.ullArguments);
PTSTR szDirection = new TCHAR[16];
ZeroMemory(szDirection, sizeof(TCHAR) * 16);
GetGestureDirection(wDirection, szDirection, 16);
StringCchPrintf(szGestureText, iBufferSize,
TEXT("GID_SCROLL\nX: %d, Y: %d\nDirection:
%s\nAngle: %d\nVelocity: %d"),
gi.ptsLocation.x, gi.ptsLocation.y, szDirection,
wAngle, wVelocity);
SetWindowText(g_hwndLabel, szGestureText);
delete[] szGestureText;
if (szDirection)
{
delete[] szDirection;
}
break;
}
/* A Hold event occurs when the user touches the screen and
does not move their finger (or the stylus) and also does not
release the screen. It usually takes about two seconds for a touch
event to register as a GID_HOLD message on my phone.
MSDN states that the amount of time required is defined arbitrarily
at some lower level, possibly by the OEM.
Therefore the amount of time it
takes for a GID_HOLD message to appear may vary by device.
*/
case GID_HOLD:
StringCchPrintf(szGestureText, iBufferSize,
TEXT("GID_HOLD\nX: %d, Y: %d"),
gi.ptsLocation.x, gi.ptsLocation.y);
SetWindowText(g_hwndLabel, szGestureText);
delete[] szGestureText;
break;
/* A Select event occurs when the user touches the screen.
It is comparable to left clicking a mouse.
*/
case GID_SELECT:
StringCchPrintf(szGestureText, iBufferSize,
TEXT("GID_SELECT\nX: %d, Y: %d"),
gi.ptsLocation.x, gi.ptsLocation.y);
SetWindowText(g_hwndLabel, szGestureText);
delete[] szGestureText;
break;
/* A Double Select event occurs when the user double taps the screen.
That is, quickly taps the screen twice.
It is comparable to double clicking the left
button of a mouse.
*/
case GID_DOUBLESELECT:
StringCchPrintf(szGestureText, iBufferSize,
TEXT("GID_DOUBLESELECT\nX: %d, Y: %d"),
gi.ptsLocation.x, gi.ptsLocation.y);
SetWindowText(g_hwndLabel, szGestureText);
delete[] szGestureText;
break;
default:
break;
}
return ERROR_SUCCESS;
}
else
{
return ERROR_INVALID_PARAMETER;
}
}
关注点
我在初步掌握 WM_GESTURE
消息的使用时,大量地参考了 MSDN。一个令人沮丧的地方是,MSDN 文档没有提及 TouchGestureCore.lib 文件作为项目依赖项的必要性,甚至没有提到它的存在。幸运的是,它的命名并不那么晦涩难懂,以至于我找不到它。
另外值得注意的是,在玩了一段时间的应用程序后,很容易发现 Windows Mobile 6.5 中的手势 API 支持多点触控(至少在我的 HTC Imagio 上是这样),因此很奇怪我还没有看到任何利用此功能的应用程序。
历史
- 2009年10月29日:初始版本