[转]Windows 7 中的多点触控功能

      开发文献 2010-1-22 9:51:00
转自:MSDN  点击查看原文
 
Windows 7 中的多点触控功能
Yochay Kiriaty

本文内容:
  • 多点触控编程模型
  • 手势
  • 原始触控消息
本文使用了以下技术:
Windows 7
 
本文基于 Windows 7 的预发布版本。详细信息可能会有所更改。
Windows 7 多点触控简介
Windows 7 多点触控平台编程模型
使用手势
处理 Windows 原始触控消息
摘要
 
本文是有关 Windows 7 的系列文章的第三篇文章。该系列文章主要介绍了新的用户体验,开发人员可以深入研究这些体验,使其应用程序在 Windows 7 上发挥强大作用。第 1 部分介绍了库。第 2 部分介绍了任务栏 API。第 3 部分介绍 Windows 7 中的多点触控功能。请立即下载 Windows 7 Release Candidate,以帮助您充分理解本文。
Windows 7 多点触控简介
在 Windows 7 中,我们通过触控功能丰富了 Windows 体验,从而使触控成为继鼠标和键盘之后又一种与 PC 进行交互的绝佳方式。近年来,各种多点触控设备相继问世,创造了极佳的用户体验。因此,Windows 理所当然地在 Windows 7 中引入了这种多点触控支持,并将其作为一项核心功能。
通过 Windows 7 多点触控平台,您可以随心所欲地直接与计算机进行交互。例如,您可以直接从 Windows 资源管理器中访问和缓慢浏览图片,也可以通过点按操作快速浏览这些图片。有一点您必须清楚的是,我们并未创建特殊的 Windows 7 多点触控外壳程序,也未提供只能在多点触控设备上使用的特殊 Windows 资源管理器。Windows 7 任务栏跳转列表就是一个最简单的示例。使用鼠标右键单击任务栏上的任一图标时,您将看到对应的跳转列表。例如,右键单击 Windows Live Messenger 图标可以显示 Live Messenger 的跳转列表。但如何使用多点触控实现右键单击的功能呢?只需用手指触摸 Live Messenger 图标并将其拖出,如图 1 所示。
fig01.gif
图 1:在 Live Messenger 的跳转列表上使用多点触控
执行该拖动手势可以显示 Live Messenger 的跳转列表。如图 2 所示,通过触控触发的跳转列表显示了与标准右键单击跳转列表相同的内容。右侧的图像显示了使用触控触发的 Live Messenger 跳转列表。多点触控启用的跳转列表中每个列表项之间的间距大于左图(默认的右键单击跳转列表)中每个列表项之间的间距。
fig01.gif
图 2:跳转列表的多点触控视图和标准视图
Windows Live Messenger 只是一个示例,它可以证明 Windows 7 没有专为触控方案创建一组新的 UI,而是将其结合到现有基础结构中。任务栏也只是一个示例,它可以证明 Windows 7 提供的多点触控功能可以带来诸多优化体验,例如 XPS 查看器、Windows 照片查看器和 IE8。

Windows 7 多点触控平台编程模型
为了提供适用于各种应用程序的全面 Windows 触控解决方案,Windows 触控平台提供了不同级别的支持。您可以通过多种方案来使用 Windows 触控平台功能增强应用程序。在采用某种特定方法之前,应该考虑一下您究竟希望如何让应用程序支持触控。
旧版支持 假定您已经有一个广泛安装使用的现有应用程序。您可能会问自己,在启用多点触控的 Windows 7 计算机上运行该应用程序时,用户使用多点触控的体验将是怎样的呢?令人庆幸的是,对于那些无法识别触控且不支持多点触控的应用程序,Windows 7 多点触控平台提供了现成的免费支持。具体来说,它为一些基本手势提供了现成的免费支持。换而言之,您可以在应用程序中使用一些基本手势,并获得理想的效果。基本手势包括在 Windows Vista 阶段引入的单指或双指平移手势、双指缩放手势以及光标手势。
添加基本多点触控支持 在本部分,我们的讨论重点是添加直接手势支持以及其他行为和用户界面更改,以使应用程序除了支持简单手势以外还可以更好地支持触控。
在本文开头,我们已经了解一个触控优化的任务栏跳转列表示例。通过使用 getMessageExtraInfo 方法,任务栏可以跟踪输入消息的来源,并确定该消息是否为触控消息,然后做出相应的响应。
此外,您可以使用手势来增强应用程序,提供更好的多点触控支持。直接响应手势的应用程序可以完全控制它们在用户触摸触控设备时的行为。例如,Windows 7 附带了 Windows 照片查看器。在照片查看器应用程序中,查看器接收有关缩放手势起始位置的特定信息。也就是说,缩放手势包含有关缩放手势中心点(特定的 X 和 Y 坐标)的信息,这使得照片查看器可以根据手势中心位置进行缩放。Windows 照片查看器应用程序还使用平移和旋转手势来提供绝佳的图像查看体验,而且相对简单一些。
利用手势,您还可以替代默认的平移行为。例如,默认的触控滚动适合在以文本为中心并通常垂直滚动的窗口(例如网页或文档)中使用,而水平拖动则可以进行文本选择,而不是滚动。在大多数应用程序中,这种触控方式可以正常工作。但如果您的应用程序实际上需要支持水平滚动,该怎么办?此外,对于某些应用程序,默认滚动可能显得不够灵活,即速度过快或过慢。有了手势支持,您可以替代默认的平移行为,并根据应用程序的需要对其进行优化。
多点触控优化体验 最好的情况是从头开始设计应用程序以支持多点触控。这些应用程序基于 Windows Messages 触控消息 WM_TOUCH 构建。此消息为应用程序提供原始触控数据,您可以利用这些消息并处理多个接触点。我们之前提到的大多数手势都是双指手势,您可以利用 WM_TOUCH 消息同时接收基础触敏式硬件所支持的多个接触点。
Windows 7 多点触控平台还提供操作和延时处理器,帮助您解释触控消息。可以将操作视作一个黑匣子,用于以输入形式接收用户触摸的对象,以及所有相关的触控消息。结果是一个二维仿射转换矩阵,表示通过手指移动产生的转换。例如,如果您要编写一个照片编辑应用程序,则可以使用一个或多个手指同时捕捉两张照片,以对照片进行旋转、调整大小和转换操作,而操作过程将提供您需要在对象上反映的变化。
延时为应用程序提供了一个非常基本的物理模型,并为您提供了一种简单的方法来继续平稳转换对象(即使在您将手指从触敏式设备拿开以后也能平稳转换),并创建简单的转换效果,而不是立即停止对象。

使用手势
默认情况下,每当用户触摸触敏式 Windows 7 设备时,Windows 7 多点触控平台都会向您的应用程序发送手势消息 WM_GESTURE。这是现成的免费行为,如果您希望停止接收此类消息,则需要选择退出。
手势被视为单指或双指触控输入,可以转换为用户执行的某种预定义操作(手势)。一旦检测到手势(操作系统为您进行所有检测),操作系统就会向应用程序发送手势消息。此消息包含解码和进行操作所需的全部信息。Windows 7 支持下列手势:
  • 缩放
  • 单指和双指平移
  • 旋转
  • 双指点击
  • 按下并点击
处理 WM_Gesture 消息 要使用手势,必须处理发送到应用程序的 WM_GESTURE 消息。如果您是 Win32 程序员,可以在应用程序的 WndProc 函数中检查 WM_GESTURE 消息。
WM_GESTURE 是用于所有手势的通用消息。因此,要确定需要处理的手势,您首先需要对手势消息进行解码。有关手势的信息可在 lParam 参数中找到,您需要使用一个特殊函数 GetGestureInfo 来解码手势消息,如下面的代码片段中所示。
GESTUREINFO gi;
ZeroMemory(&gi, sizeof(GESTUREINFO));
gi.cbSize = sizeof(gi);
BOOL bResult = GetGestureInfo((HGESTUREINFO)lParam, &gi);
 
获取 GESTUREINFO 结构后,可以检查 dwID 以确定执行了哪个手势。GESTUREINFO 结构包含几个其他重要成员:
  • cbSize - 结构大小(以字节为单位)
  • ptsLocation - 一个 POINTS 结构,其中包含与手势相关的坐标。这些坐标始终都相对于屏幕的原点
  • dwFlags - 手势的状态,例如开始、延时和结束
  • ullArguments - 一个 64 位无符号整数,其中包含手势的参数,组合为八字节。这是额外信息,对于每个手势类型都是唯一的
掌握了这些知识后,我们可以继续操作并编写用于处理所有手势的完整 switch-case 方法,如图 3 所示。
void CMTTestDlg::DecodeGesture(WPARAM wParam, LPARAM lParam)
{
    GESTUREINFO gi; 
    ZeroMemory(&gi, sizeof(GESTUREINFO));
    GetGestureInfo((HGESTUREINFO)lParam, &gi);
    switch (gi.dwID){
        case GID_ZOOM:
            // Code for zooming goes here
            break;
        case GID_PAN:
            break;
        case GID_ROTATE:
            break;
        case GID_TWOFINGERTAP:
            break;
        case GID_PRESSANDTAP:
            break;
        default:
            // You have encountered an unknown gesture
            break;
    CloseGestureInfoHandle((HGESTUREINFO)lParam);
}

 
请注意,在函数的末尾,我们调用了 CloseGestureInfoHandle 函数,用于关闭与手势信息处理程序相关联的资源。如果处理 WM_GESTURE 消息,则您要确保使用此函数来关闭句柄。不这么做可能会导致内存泄漏。
处理手势消息具有一个固定流程,包括配置、解码手势消息以及根据应用程序的需要处理特定手势。正如您在以上代码中看到的那样,执行这个流程并不很难。
现在,让我们来详细了解缩放手势,通过这种手势,您也可以大致了解所有其他手势的工作原理。
使用缩放手势缩放对象
缩放手势通常被用户视为两个接触点之间的“挤压”运动,您可以将手指相互靠近以缩小内容显示,或者将手指分开以放大内容显示。使用缩放手势,您可以缩放对象的大小。图 4 说明了缩放手势的使用方式。
fig01.gif
图 4:缩放手势
现在,我们将了解需要在 GID_ZOOM switch 中实现什么代码才能达到所需的缩放效果。
手势信息结构包括 dwFlags 成员,该成员用于确定手势的状态,而且可以包括以下任何值:
  • GF_BEGIN - 指示手势即将开始,在第一个 WM_Gesture 消息中收到
  • GF_INERTIA - 指示手势已经触发了延时
  • GF_END - 指示手势已经完成
  • switch 的默认值 - 指示手势消息的剩余部分,通常称为变化量
我们将使用 GF_BEGIN 标志将接触点的初始开始坐标保存在变量中,并将其作为以下步骤的引用。我们将 ptsLocation 保存在 _ptFirst 变量中。对于缩放手势,ptsLocation 表示缩放的中心。
收到的以下缩放消息由 default case 进行处理。我们将坐标保存在 _ptSecond 变量中。接下来,我们将计算缩放中心点和缩放比例。最后,我们还将更新矩形(我们的图形对象)以反映缩放中心点和缩放比例。图 5 显示了这些参数。
case GID_ZOOM:
switch(gi.dwFlags)
{
case GF_BEGIN:
    _dwArguments = LODWORD(gi.ullArguments);
    _ptFirst.x = gi.ptsLocation.x;
    _ptFirst.y = gi.ptsLocation.y;
    ScreenToClient(hWnd,&_ptFirst);
    break;
default:
    // We read here the second point of the gesture. This is middle point between fingers. 
    _ptSecond.x = gi.ptsLocation.x;
    _ptSecond.y = gi.ptsLocation.y;
    ScreenToClient(hWnd,&_ptSecond);
    // We have to calculate zoom center point 
    ptZoomCenter.x = (_ptFirst.x + _ptSecond.x)/2;
    ptZoomCenter.y = (_ptFirst.y + _ptSecond.y)/2;           
    
    // The zoom factor is the ratio between the new and the old distance. 
    k = (double)(LODWORD(gi.ullArguments))/(double)(_dwArguments);
    // Now we process zooming in/out of the object
    ProcessZoom(k,ptZoomCenter.x,ptZoomCenter.y);
    InvalidateRect(hWnd,NULL,TRUE);

    // Now we have to store new information as a starting information for the next step
    _ptFirst = _ptSecond;
    _dwArguments = LODWORD(gi.ullArguments);
    break;
}
break;


 
在默认的 case 处理程序中,我们保存手势的位置,从两组点(表示当前接触点和前一个接触点)计算缩放中心位置,并将其存储在 ptZoomCenter 中。我们还通过计算两个点之间的比例来计算出缩放系数。调用 ProcessZoom 帮助函数可以更新新的坐标,以反映缩放系数和中心点。
处理其他 Windows 7 默认手势与以上所述的特定缩放手势处理非常相似。所有手势都遵循相同的流程,只是在每个使用案例场景中,每个手势的内部逻辑实现有所不同。接下来,我们将了解最佳模型,并深入探讨让您能够接收和处理原始触控事件的 API。

处理 Windows 原始触控消息
要开始接收原始触控消息 WM_TOUCH,首先需要请求操作系统开始向应用程序发送触控消息,并停止发送默认手势消息。若要执行此操作,则需要调用 RegisterTouchWindow(HWND hWnd, ULONG uFlags) 函数。调用此函数可将单个 hWnd 元素(通常是一个窗口)注册为启用触控的。
与手势相同,您在应用程序的 WndProc 函数中处理 WM_TOUCH 消息。单条 WM_TOUCH 消息可能包含多条不同的“接触点消息”,需要将它们解包为一个触控输入结构数组。标准做法是将 WM_TOUCH 消息解包为一个 TOUCHINPUT 结构数组,该数组中的每个结构表示来自单个接触点的数据。若要进行解包,需要调用 GetTouchInputInfo(HTOUCHINPUT hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) 函数,并向其传递 WM_TOUCH 消息的 lParam 以及一个新创建的接触点数组,如图 6 所示。
case WM_TOUCH:
{
    unsigned int numInputs = (unsigned int) wParam; 
    TOUCHINPUT* ti = new TOUCHINPUT[numInputs]; 
    if(GetTouchInputInfo((HTOUCHINPUT)lParam, numInputs, ti, sizeof(TOUCHINPUT)))
    {
        // Handle each contact point
        for(unsigned int i=0; i< numInputs; ++i)
        {
           /* handle ti[i]  */
        }
    }
    CloseTouchInputHandle((HTOUCHINPUT)lParam);
    delete [] ti;
}
break;
default:
    return DefWindowProc(hWnd, message, wParam, lParam);


 
在这里,您可以看到我们如何使用来自每个接触点的数据来填充 TOUCHPOINT ti 数组。接下来,我们将遍历接触点数组,将逻辑应用于每个接触点 handle ti[i] 注释。最后,我们需要通过调用 CloseTouchInputHandle(HTOUCHINPUT hTouchInput) 清除触控句柄,传递原始 WinProc 的 lParam。不这么做将会导致内存泄漏。
以上代码表示处理 WM_TOUCH 消息的第一个步骤。单个触控输入结构 TOUCHINPUT 包含了有关您需要使用的单个接触点的所有必需信息:
  • dwID - 接触点标识符,用于区分特定触控输入与其他输入
  • dwFlags - 一组位标志,用于指定接触点的状态
  • 接触点的 XY 坐标(基本上是每个接触点的位置)
  • dwTime - 事件的时间步长,以毫秒为单位
  • dwMask - 一组位标志,用于指定结构中的哪些可选字段包含有效值
请务必注意,X 和 Y 坐标的单位是实际屏幕坐标的像素的百分之一(即 centa-pixel)。对于可能需要高分辨率的其他应用程序而言,这种超高分辨率有利于达到高精度,实现更加精确的手写识别。但在大多数情况下,在开始使用这些坐标之前,请务必记住应将接触点的 X 和 Y 坐标除以一百,从而将接触点坐标转换为可用的屏幕坐标。
现在,您已经知道了如何处理触控消息,并且掌握了为上述 WM_TOUCH 处理程序添加真实逻辑所需的全部信息。接下来让我们利用这些知识来构建一个多点触控画图应用程序,也称为“草稿板”。
跟踪接触点 ID 若要创建“草稿板”应用程序,需要跟踪每个接触点的运动及其形成的轨迹,然后沿着该轨迹绘制线条。为了区分不同的接触点,确保真正正确地处理每个接触点,我们可以为每个接触点指定不同的颜色。
在将触控消息解包为触控输入结构数组 ti 之后,需要检查每个接触点状态,并针对每个接触点状态应用不同的逻辑。在“草稿板”示例中,新接触点由向下状态 TOUCHEVENTF_DOWN 进行标识。您注册新的接触点 ID,并为其指定一种颜色。删除接触点 TOUCHEVENTF_UP 之后,您完成最后的绘图,并注销接触点 ID。在向上和向下事件之间,您很可能会收到大量移动消息 TOUCHEVENTF_MOVE。收到每条移动消息时,您向现有线条添加一个新点,然后绘制新的线段。图 7 显示了“草稿板”应用程序支持多点触控所需的整个 WM_TOUCH 处理程序。
case WM_TOUCH:
{
    unsigned int numInputs = (unsigned int) wParam; 
    TOUCHINPUT* ti = new TOUCHINPUT[numInputs]; 
    if(GetTouchInputInfo((HTOUCHINPUT)lParam, numInputs, ti, sizeof(TOUCHINPUT)))
    {
        // For each contact, dispatch the message to the appropriate message handler.
        for(unsigned int i=0; i< numInputs; ++i)
        {
            if(ti[i].dwFlags & TOUCHEVENTF_DOWN)
            {
               OnTouchDownHandler(hWnd, ti[i]);
            }
            else if(ti[i].dwFlags & TOUCHEVENTF_MOVE)
            {
               OnTouchMoveHandler(hWnd, ti[i]);
            }
            else if(ti[i].dwFlags & TOUCHEVENTF_UP)
            {
               OnTouchUpHandler(hWnd, ti[i]);
            }
        }
    }
    CloseTouchInputHandle((HTOUCHINPUT)lParam);
    delete [] ti;
}
break;
 
 
跟踪各个接触点的关键是使用 dwID,在特定触控笔划的整个过程中,它都保持不变。在 OnTouchDownHandler 帮助函数中,您将此 ID 指定给 CStroke 对象,该对象基本上是一个代表线条的点数组。这个线条是您在触敏式设备上拖动手指时形成的轨迹。我们将不再介绍支持应用程序并在屏幕上实际绘制线条的完整代码示例。在前面的代码示例中,您基本上可以找到支持多点触控所需的全部操作。
图 8 中,您可以查看“草稿板”应用程序的输出。
fig01.gif
图 8:“草稿板”应用程序的输出
在默认的 case 处理程序中,我们保存手势的位置,从两组点(表示当前接触点和前一个接触点)计算缩放中心位置,并将其存储在 ptZoomCenter 中。我们还通过计算两个点之间的比例来计算出缩放系数。调用 ProcessZoom 帮助函数可以更新新的坐标,以反映缩放系数和中心点。
处理其他 Windows 7 默认手势与以上所述的特定缩放手势处理非常相似。所有手势都遵循相同的流程,只是在每个使用案例场景中,每个手势的内部逻辑实现有所不同。接下来,我们将了解最佳模型,并深入探讨让您能够接收和处理原始触控事件的 API。

处理 Windows 原始触控消息
要开始接收原始触控消息 WM_TOUCH,首先需要请求操作系统开始向应用程序发送触控消息,并停止发送默认手势消息。若要执行此操作,则需要调用 RegisterTouchWindow(HWND hWnd, ULONG uFlags) 函数。调用此函数可将单个 hWnd 元素(通常是一个窗口)注册为启用触控的。
与手势相同,您在应用程序的 WndProc 函数中处理 WM_TOUCH 消息。单条 WM_TOUCH 消息可能包含多条不同的“接触点消息”,需要将它们解包为一个触控输入结构数组。标准做法是将 WM_TOUCH 消息解包为一个 TOUCHINPUT 结构数组,该数组中的每个结构表示来自单个接触点的数据。若要进行解包,需要调用 GetTouchInputInfo(HTOUCHINPUT hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) 函数,并向其传递 WM_TOUCH 消息的 lParam 以及一个新创建的接触点数组,如图 6 所示。

摘要
Windows 7 多点触控平台是一个非常强大的开发平台。从实现默认手势支持到更高级的原始触控消息,它为您提供了强大的功能,而且实现起来相对要简单一些。
该平台还包括操作和延时处理器。操作在很多方面与手势相似,但却强大得多。使用操作可以简化对任何给定数量的对象的转换操作。您可以同时对某个特定对象执行特定手势组合,例如旋转、缩放和伸缩。操作处理器生成二维的“转换矩阵”,该矩阵用 X 和 Y 坐标来表示转换,还表示由于执行接触点移动,对象随着时间推移发生的大小变化和旋转。最后一个接触点拉起之后,您可能希望将简单的物理运动应用于对象,使其平滑停止,而不是突然停在点上。为了支持这种平滑运动,Windows 7 多点触控平台提供了延时 API。
这些 API 将是我们下一篇 MSDN 文章的主题。

Yochay Kiriaty 是一位专注于研究 Windows 7 的 Microsoft 技术专员。他拥有十年以上的软件开发经验。他编写并传授了一些学术性的计算机科学课程,而且还积极地为 Windows 博客撰稿。

 

标签集:TAGS:
回复Comments() 点击Count()
喜欢就顶一下

回复Comments

{commentauthor}
{commentauthor}
{commenttime}
{commentnum}
{commentcontent}
作者:
{commentrecontent}