一、体系概述
1.Windows Touch Input 和 Gestures消息
Windows Touch消息特性 通过在执行期间的监听和解释来使能。下面的示例展示了Windows7 上消息是怎么从硬件产生并发给应用程序的。
在最左边的那列,touch-sensitive 设备接收用户输入,驱动程序沟通硬件和操作系统,接下来,操作系统产生WM_TOUCH或者WM_GESTURE消息,进而发送给应用程序句柄,然后应用程序通过消息中封装的信息更新UI。
默认情况下应用程序受到的是gesture消息,除非应用程序中通过RegisterTouchWindow函数注册touch输入消息。这样,WM_TOUCH消息就会被发送到这个应用程序窗口。Touch和Gesture消息是“贪婪的”,一旦一个touch消息或者Gesture在应用程序上产生了,所有的消息都会被发送到给窗口中,除非gesture或touch完成。
Windows解释WM_GESTURE消息然后SEND或POST合适的映射到gesture的消息,为了不会造成内存泄漏,确保你没处理的WM_GESTURE消息被DefWindowProc处理。
2.操作和惯性
Windows touch程序员必须能够解释多种来源的手势消息,Microsoft提供操作API去处理这些计算。当您连接到输入数据的操纵处理器,你可以获得用户在对象上做的动作信息,下图显示的一种你可以使用的操作方法。
左上角,用户touch屏幕,屏幕创建touch消息,这些消息包含x和y位置,这个位置被用来决定这个Object in Focus,Object in Focus包含一个操作处理器,接下来,带有TOUCHEVENTF_UP标志的WM_TOUCH消息,选中用户的Object in Focus,引用操作处理器,然后把消息发送到操作处理器,然后和本次触摸关联的WM_TOUCH消息被发送到引用处理器直到TOUCHEVENTF_UP标志被收到,被选择的焦点对象被解引用。上图中的右下角,一个实现了_IManipulationEvents接口的处理事件接收器被用来处理事件,当touch消息被创建时它被加载,这个事件接收器。在windows Touch应用程序中,聚合简单的物理原理使得对象很自然得表现stop,而不是不再被触摸时马上stop,Microsoft提供了惯性API去计算来表现这些简单的物理现象,这样也省去了你要建立这些物理特性的努力,下图显示了如何使用惯性。
你可以注意到惯性和操作之间的相似性,两者唯一的不同是在惯性的情况下,消息被投递到惯性处理器而不是操作处理器,惯性处理器产生事件,touch消息被用来标志一个object in focus 这个object in focus 包含惯性处理器和操作处理器,接下来的WM_TOUCH消息被发送到操作处理器,这个操作处理器来完成应用程序UI的更新,操作完成后,来自于这个操作的向量被用来建立一个惯性处理器,IInertiaProcessor接口的Process和ProcessTime (上图)方法被调用使用一个时间或者其他单独线程的循环知道这些调用表示处理器完成了处理。当这些调用被执行时,操作事件被产生,被基于_IManipulationEvents接口的事件接收器处理。事件接收器来表现这些操作事件所要表现的界面更新。
3.选择正确的途径来响应windows Touch
这个部分解释了你可以使用的处理Windows Touch的不同途径。
你可以通过使用Windows Touch特性来增强你的应用程序,在你做之前你应该明确你想要怎么编写你的应用程序。下面场景就是使用Windows Touch 的典型场景。
l 你想要你的应用程序表现的和在旧版本的windows中一样,但是希望windows touch 消息的行为不变
l 你想要程序中的自定义的对象支持rotation, translation, panning, or zoom 操作。
l 你想要你的应用程序解释windows Touch手势或者解释多点触摸。
l 你有个使用RealTimeStylus对象的应用程序,你想要使它支持windows touch 能力。
第一种情况:
在windows7中,有Windows Touch 特性的情况下,应用程序默认的情况下会产生Touch消息。例如pan 手势会触发WM_*SCROLL消息,除了pan支持,Windows7中的默认WM_GESTURE句柄,支持边界反馈, zoom, and press and tap,边界反馈一直是使能的也是传统的支持,开发者想要这个基本的功能的话不需要直接用Windwos Touch API
注意:Custom Scroll bar句柄必须支持SM_THUMBPOSITION 因为WM_VSCROLL需要这个支持,也必须支持SB_LINELEFT和SB_LINERIGHT 因为WM_HSCROLL需要这个支持。
第二种情况:
如果你想要支持有限的touch,但是windows7默认提供的不足以满足你的程序的需要,你可以使用 gestures 来增强你的程序的功能。通过使用gestures,你可以通过处理WM_GESTURE消息来解释gestures command在Windows Touch Gestures中可以找到更多的相关信息。如果你的应用程序只需要支持 高质量的旋转,zooming,单指panning。gestures是最合适不过的了。除了解释gesture消息,你可以选择性地支持边界反馈,如果你想要了解更多的边界反馈的东西,请阅读BoundaryFeedBack 那部分,在Windows7中,命令提示符和IE利用了边界反馈和gestures。
第三种情况:
如果你想要更具体的控制手势而不是像WM_GESTURE消息那样,或者你想要去解释多个对象的多次手势,你应该使用这个manipulation processor(操作处理器),操纵处理器基本上是一个超手势,使用操作处理器需要你实现一个(event sink)事件接收器来处理原始的触摸数据。
如果除了解释手势,你还想要简单的物理现象,你应该把inertia processor(惯性处理器)和操作处理器关联起来,惯性处理器和操作处理器协同工作,通过采集来自于操作处理器的操作完成时的速度值。
如果你的应用程序想要解释多点触摸消息,你应该创建一个WM_TOUCH的句柄。
windows Touch input部分的内容解释了你如何去解析一个WM_TOUCH消息。
Detecting and Tracking Multiple Touch Points部分的内容演示了如何去创建一个解析多点触摸的例子程序。
Manipulations and Inertia部分解释了如何去使能windows touch 的最灵活的方式。
第四种情况:
如果你想要使能Tablet PC 平台的多点触摸,你必须实现一个自定义的RealTimeStylus plug-in 来解析windows Touch数据。Microsoft介绍了ITablet3和IRealTimeStylus3接口去使能来自于RealTimeStylus plug-in的多点触摸数据。这些接口很简单地扩展了RealTimeStylus plug-in来支持多点触摸。
为了检查是否支持多点触摸,调用IsMultiTouch.为了检查支持的触摸数量,调用GetMaximumCursors,为了使能或者无效多点触摸,调用MultiTouchEnabled。
注意:如果你在RealTimeStylus中不使能多点触摸,你会收到普通的gesture消息,例如pan 或者zoom
二、Troubleshooting Applications疑难解答
一般疑难问题
1、问题:我使用OS是windows server 2008 ,windows touch 特性不工作?
原因:没有使能Desktop Experience。
解决方案:打开Server Manager administrative tool: 单击Start,指向Adiministrative tools,然后单击Server Manager,单击左侧的Features,单击Features 在 Features部分,选择Desktop Experience 单击Next,单击Install。
2、问题:无论我怎么在应用程序上快速地移动我的手指,一个arrow出现,我的手势或操作都不能正确的注册。
原因:当你不需要的时候,使能flicks。
解决方案:当你想要fllicks无效的时候,你应该无效它,看Legacy support for panning with scrollbars这部分获得更多的信息去无效pen flicks。
3、问题:我不能辨别鼠标的单击和windows touch 输入
原因:windows产生鼠标消息为了支持用户clicks在屏幕上的时候
解决方案:可以调用GetMessageExtraInfo来获取消息来源,看下面的代码
#define MOUSEEVENTF_FROMTOUCH 0xFF515700
if ((GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) {
// Click was generated by wisptis / Windows Touch
}else{
// Click was generated by the mouse.
}
4、问题:我怎么才能运行Surface Applications 在windows7上?
原因:windows touch 和Microsoft surface是不兼容的
解决方案:你既需要target windos7平台,也需要target Microsoft Surface平台
Manipulations 和惯性疑难问题
1、问题:我的应用程序不知道什么原因无响应了,当我初始化我的对象接口的时候得到了访问违规的提示
原因:没有为IManipulationProcessor和IInertiaProcessor接口调用CoInitialize
解决方案:这个原因可能是因为没有为windows touch的com对象调用com初始化函数,当你从使用手势的工程转换成使用manipulations or inertia接口的工程时可能会出现这种情况。
2、问题:我的对象不能正常地旋转,单指旋转不工作。
原因:你在这个对象上设置的枢轴不合适。
解决方案:你没有正确地设置枢轴点,设置PivotPointX 和PivotPointY属性作为旋转的中心点,设置PivotRadius属性为对象的半径。
Windows Touch Input的疑难问题
1、问题:我处理WM_TOUCH消息后,没有得到boundary feedback(边界反馈)
原因:不处理WM_TOUCH消息
解决方案:你可能consuming一个windows touch消息但是没有把它交给DefWindowProc处理,这个会导致不希望的行为,看一下Getting Started with Windows Touch Messages获取更多的信息关于如何合适地处理WM_TOUCH消息。
2、问题:我包含了windows.h文件,为什么报错说WM_TOUCH没定义?
原因:targetver.h中的windows的版本不正确
解决方法:你没有设置正确的windows 版本,下面的代码教你为windows touch设置正确的windows 版本(在targetver.h头文件中)
#ifndef WINVER // Specify that the minimum required platform is Windows 7.
#define WINVER 0x0601
#endif
3、问题:我的touch输入的x和y坐标的值不对,有时比期望的大,有时又是负值?
原因:你可能需要把你的touch点转成像素表示,或者你需要转成你的屏幕坐标
解决方法:确保你调用了TOUCH_COORD_TO_PIXEL 和ScreenToClient
POINT ptInput;
if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))){
for (int i=0; i < static_cast<INT>(cInputs); i++){
TOUCHINPUT ti = pInputs[i];
if (ti.dwID != 0){
// Do something with your touch input handle.
ptInput.x = TOUCH_COORD_TO_PIXEL(ti.x);
ptInput.y = TOUCH_COORD_TO_PIXEL(ti.y);
ScreenToClient(hWnd, &ptInput);
points[ti.dwID][0] = ptInput.x;
points[ti.dwID][1] = ptInput.y;
}
}
}
4、问题:我收不到WM_TOUCH消息,但是收到了WM_GESTURE消息
原因:没有调用RegisterTouchWindow消息
解决方案:WM_TOUCH和WM_GESTURE消息是互斥的,调用RegisterTouchWindow后就会收到WM_TOUCH消息,否则收到WM_GESTURE消息。
5、问题:在我touch的时候到我的程序收到消息中间有一小段延迟?
原因:平台排斥的关系
解决方案:如果调用RegisterTouchWindow的时候TWF_WANTPALM被设置了,就会使能平台排斥反映,这个就会导致软件探测输入是来自于手指,笔还是用户的平台从而产生大约100ms的延迟,通过调用RegisterTouchWindow的时候设置TWF_WANTPLAM标志就可以无效平台排斥反映。
Windows Touch Gestures疑难问题
1、问题:处理完WM_GESTURE消息后,没有boundary feedback
原因:截获了 WM_GESTURE消息儿没处理它
解决:你可能截获了一个WM_GESTURE消息但是没有处理它也没有交给DefWindowProc处理,这个就可能导致不被期望的行为,看Getting Started with Windows Gestures获得更多的信息去弄明白怎么合适地处理WM_GESTURE消息。
2、问题:我没有看到我所期望的所有的gestures消息,例如,我看到了GID_PAN但是没有GID_ROTATE消息
原因:有些手势,默认的情况下是没有使能的
解决:当收到WM_GESTURENOTIFY消息时需要调用SetGestureConfig来设置,下面的代码演示了如何实现WM_GESTURENOTIFY消息。
// The message map.
BEGIN_MESSAGE_MAP()
ON_WM_CREATE()
... ... ...
ON_MESSAGE(WM_GESTURENOTIFY, OnWindowsGestureNotify)
END_MESSAGE_MAP()
LRESULT CTestWndApp::OnWindowsGestureNotify(
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
BOOL& bHandled
){
GESTURECONFIG gc;
gc.dwID = GID_ROTATE; // The gesture identifier.
gc.dwWant = GC_ROTATE; // The gesture command you are enabling for GID_ROTATE.
gc.dwBlock = 0; // Don't block anything.
UINT uiGcs = 1; // The number of gestures being set.
BOOL bResult = SetGestureConfig(g_hMainWnd, 0, uiGcs, &gc, sizeof(GESTURECONFIG));
if(!bResult) {
// Something went wrong, report the error using your preferred logging.
}
return 0;
}
3、问题:自定义的scroll bar不能scrolling 当我用pan gesture的时候
原因:没有处理WM_*SCROLL消息
解决:在你的自定义scrollbar中没有处理全部的WM_*SCROLL消息,推荐的行为是,当你处理WM_GESTURE消息而不是通过以往的支持保持自定义scrollbar的功能,更详细的信息看Legacy Support for Panning with Scroll bars
4、问题:我的gestures有延迟
原因:flicks可能会导致延迟
解决:看Legacy Support for Panning with Scrollbars 来明白如何去disable flicks
三、Windows Touch Input
1.Getting Started with windows touch messages
下面的步骤解释了怎么处理windows touch 消息
l 测试输入设备的能力
l 注册接收touch消息的窗口
l 处理touch消息
第一步:测试设备能力
检测硬件是否支持触摸
int value = GetSystemMetrics( SM_DIGITIZER )
if ( value & NID_MULTI_INPUT )
{
// 设备支持触摸操作
}
Name |
Value |
Description |
TABLE_CONFIG_NONE |
0x00000000 |
输入设备没有触摸能力 |
NID_INTEGRATED_TOUCH |
0x00000001 |
集成的input digitizer |
NID_EXTERNAL_TOUCH |
0x00000002 |
扩展的touch digitizer |
NID_INTEGRATED_PEN |
0x00000004 |
集成的pen digitizer |
NID_EXTERNAL_PEN |
0x00000008 |
扩展的pen digitizer |
NID_MULTI_INPUT |
0x00000040 |
支持多点触摸 |
NID_READY |
0x00000080 |
输入设备准备好输入了 |
检查NID_* 值是一种很有效的检查用户的设备支持touch pen or no-table input的能力。例如,如果你有一个动态的UI,想要动态地配置一些东西,你可以检查NID_INTEGRATED_TOUCH,NID_MULTITOUCH,在用户第一次运行你的应用程序的时候获得最小的上表中的值。
对于SM_GETSYSTEMMETRICS 有一些固有的限制,不支持即插即用,当用这个函数的时候应该注意这一点。
第二步:注册touch窗口
应用程序必须先注册才能收到多点触摸消息,通过函数RegisterTouchWindow函数,退出时必须卸载,通过函数UnRegisterTouchWindow函数。
WM_TOUCH消息是“贪婪的”第一个消息被一个窗口接收到以后,在另一个窗口获得焦点前,所有的touch消息都会被发送到该窗口。
默认的情况下,应用程序会收到WM_GESTURE消息而不是WM_TOUCH消息,如果调用了RegisterTouchWindow函数,就会阻止WM_GESTURE消息而收到WM_TOUCH消息。
第三步:处理touch消息
下面列举一个Win32程序
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
RegisterTouchWindow(hWnd, 0);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_TOUCH:
......
// you can either pass this to DefWindowProc or can handle it yourself
DefWindowProc(hWnd, message, wParam, lParam);
break;
}
}
退出时调用UnRegisterTouchWindow函数。
下面的例子是一个MFC的例子
// Class implementations within a dialog
LRESULT TestDlg::OnTouch( WPARAM wParam, LPARAM lParam ){
//Insert handler code here to do something with the message or uncomment the following line to test
//MessageBox(L"touch!", L"touch!", MB_OK);
return 0;
}
// The message map
BEGIN_MESSAGE_MAP()
ON_WM_CREATE()
... ... ...
ON_MESSAGE(WM_TOUCH, OnTouch)
END_MESSAGE_MAP()
BOOL TestDlg::OnInitDialog()
{
CDialog::OnInitDialog();
RegisterTouchWindow(m_hWnd, 0);
... ... ...
}
当你开始收到touch消息后,你可以把这个消息转换成touch input结构体来表现更有意思的操作,下面的代码显示了如何转换这些信息。
LRESULT OnTouchHandled(HWND hWnd, WPARAM wParam, LPARAM lParam ){
BOOL bHandled = FALSE;
UINT cInputs = LOWORD(wParam);
PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
if (pInputs){
if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))){
for (UINT i=0; i < cInputs; i++){
TOUCHINPUT ti = pInputs[i];
//do something with each touch input entry
}
}else{
/* handle the error here */
}
delete [] pInputs;
}else{
/* handle the error here, probably out of memory */
}
if (bHandled){
// if you handled the message, close the touch input handle and return
CloseTouchInputHandle((HTOUCHINPUT)lParam);
return 0;
}else{
// if you didn't handle the message, let DefWindowProc handle it
return DefWindowProc(hWnd, WM_TOUCH, wParam, lParam);
}
}
2.Detecting and Tracking Multiple Touch Points
下面的步骤解释了如何跟踪windows touch中的touch points
l 创建一个应用程序使能windows touch
l 加入一个WM_TOUCH的处理
l 描画touch点
创建程序后,在targetver.h头文件中修改成如下的样子,提供操作系统版本支持
#ifndef WINVER // Specifies that the minimum required platform is Windows 7.
#define WINVER 0x0601 // Change this to the appropriate value to target other versions of Windows.
#endif
#ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows 7.
#define _WIN32_WINNT 0x0601 // Change this to the appropriate value to target other versions of Windows.
#endif
#ifndef _WIN32_WINDOWS // Specifies that the minimum required platform is Windows 98.
#define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later.
#endif
#ifndef _WIN32_IE // Specifies that the minimum required platform is Internet Explorer 7.0.
#define _WIN32_IE 0x0700 // Change this to the appropriate value to target other versions of IE.
#endif
加入下面的代码到你的工程中
#include <windows.h> // included for Windows Touch
#include <windowsx.h> // included for point conversion
#define MAXPOINTS 10
// You will use this array to track touch points
int points[MAXPOINTS][2];
// You will use this array to switch the color / track ids
int idLookup[MAXPOINTS];
// You can make the touch points larger
// by changing this radius value
static int radius = 50;
// There should be at least as many colors
// as there can be touch points so that you
// can have different colors for each point
COLORREF colors[] = { RGB(153,255,51),
RGB(153,0,0),
RGB(0,153,0),
RGB(255,255,0),
RGB(255,51,204),
RGB(0,0,0),
RGB(0,153,0),
RGB(153, 255, 255),
RGB(153,153,255),
RGB(0,51,153)
};
第二步:
在WndProc中声明一些变量
int wmId, wmEvent, i, x, y;
UINT cInputs;
PTOUCHINPUT pInputs;
POINT ptInput;
在初始化函数中注册touch 窗口
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd) {
return FALSE;
}
// register the window for touch instead of gestures
RegisterTouchWindow(hWnd, 0);
// the following code initializes the points
for (int i=0; i< MAXPOINTS; i++){
points[i][0] = -1;
points[i][1] = -1;
idLookup[i] = -1;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
第三步:处理touch消息
case WM_TOUCH:
cInputs = LOWORD(wParam);
pInputs = new TOUCHINPUT[cInputs];
if (pInputs){
if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))){
for (int i=0; i < static_cast<INT>(cInputs); i++){
TOUCHINPUT ti = pInputs[i];
index = GetContactIndex(ti.dwID);
if (ti.dwID != 0 && index < MAXPOINTS){
// Do something with your touch input handle
ptInput.x = TOUCH_COORD_TO_PIXEL(ti.x);
ptInput.y = TOUCH_COORD_TO_PIXEL(ti.y);
ScreenToClient(hWnd, &ptInput);
if (ti.dwFlags & TOUCHEVENTF_UP){
points[index][0] = -1;
points[index][1] = -1;
}else{
points[index][0] = ptInput.x;
points[index][1] = ptInput.y;
}
}
}
}
// If you handled the message and don't want anything else done with it, you can close it
CloseTouchInputHandle((HTOUCHINPUT)lParam);
delete [] pInputs;
}else{
// Handle the error here
}
注意:为了使用ScreenToClient函数,你的应用程序必须支持high DPI
touch时存储在点数组中的点都是设备独立的
// This function is used to return an index given an ID
int GetContactIndex(int dwID){
for (int i=0; i < MAXPOINTS; i++){
if (idLookup[i] == -1){
idLookup[i] = dwID;
return i;
}else{
if (idLookup[i] == dwID){
return i;
}
}
}
// Out of contacts
return -1;
}
描画这些点
// For double buffering
static HDC memDC = 0;
static HBITMAP hMemBmp = 0;
HBITMAP hOldBmp = 0;
// For drawing / fills
PAINTSTRUCT ps;
HDC hdc;
// For tracking dwId to points
int index;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
RECT client;
GetClientRect(hWnd, &client);
// start double buffering
if (!memDC){
memDC = CreateCompatibleDC(hdc);
}
hMemBmp = CreateCompatibleBitmap(hdc, client.right, client.bottom);
hOldBmp = (HBITMAP)SelectObject(memDC, hMemBmp);
FillRect(memDC, &client, CreateSolidBrush(RGB(255,255,255)));
//Draw Touched Points
for (i=0; i < MAXPOINTS; i++){
SelectObject( memDC, CreateSolidBrush(colors[i]));
x = points[i][0];
y = points[i][1];
if (x >0 && y>0){
Ellipse(memDC, x - radius, y - radius, x+ radius, y + radius);
}
}
BitBlt(hdc, 0,0, client.right, client.bottom, memDC, 0,0, SRCCOPY);
EndPaint(hWnd, &ps);
break;
四、Windows Touch Gestures
1.windows Touch Gestures Overview预览
Gestures Overview
windows7使能了好几种形式的gestures支持单点和多点,见下图
解析手势时,当接触点彼此远时用多点解析会更有效。
Legacy Support
在以往的支持中,默认的手势处理会把一些手势映射成在windows的以前版本中使用的windows消息,下面概要的说明了这一点
Gesture |
Description |
Messages Generated |
Pan |
这个手势映射成使用scroll 滚动 |
|
Press and Hold |
这个手势被映射成单击鼠标 |
|
Zoom |
这个手势触发了一些消息,这些消息持有CTRL键,spinning鼠标的滚轮 |
解析windows touch Gestures
在win32程序中,用户通过在WndProc函数中处理WM_GESTURE消息来对windows touch gestures进行解析。处理这个消息过程中,可以获得一个GESTUREINFO结构体描述了Gestures消息,这个结构体有关于gesture的各种信息。
这个GESTUREINFO结构体能够被获得,通过传递句柄给GetGestureInfo函数。
下面的表示了储存在dwFlags的各种手势的状态。
Name |
Value |
Description |
GF_BEGIN |
0x00000001 |
手势开始 |
GF_INERTIA |
0x00000002 |
手势有惯性效果 |
GF_END |
0x00000004 |
手势结束 |
大多数的应用程序应该忽略GID_BEGIN和GID_END消息而把它们交给DefWindowProc处理,如果这两个消息被第三方应用程序 的话,应用程序的行为是未定义的。
下表列出了支持的手势命令
GestureID |
Value(dwID) |
Description |
GID_BEGIN |
1 |
标志一个手势开始 |
GID_END |
2 |
标志一个手势结束 |
GID_ZOOM |
3 |
代表zoom in, zoom out或者zoom start 第一个GID_ZOOM消息开始一个Zoom但是不会引起任何Zooming。第二个GID_ZOOM消息才触发zoom |
GID_PAN |
4 |
代表pan start或pan move,第一个GID_PAN代表pan start 第二GID_PAN发来时,应用程序才开始paning |
GID_ROTATE |
5 |
代表rotate move 或 rotate start 第一个GID_ROTATE代表rotate move 或rotate start但是不会rotate,第二个消息才会触发和第一个消息相关的rotate |
GID_TWOFINGERTAP |
6 |
代表 two-finger 手势 |
GID_PRESSANDTAP |
7 |
代表 press and tap 手势 |
注意 GID_PAN手势内建了惯性,在pan手势的最后,额外的pan手势会被OS自动创建。
GESTUREINFO结构体成员ptsLocation和ullArguments具体指定了一个点和与手势相关的额外信息。
Gesture ID |
ullArgument |
pstLocation |
GID_ZOOM |
两个点的距离 |
Zoom的中心 |
GID_PAN |
两个点的距离 |
当前pan位置 |
GID_ROTATE |
如果GID_BEGIN被置位的话,代表旋转角否则代表自从旋转开始时的角度改变量,用GID_ROTATE_ANGLE_FROM_ARGUMENT GID_ROTATE_ANGEL_TO_ARGUMENT获得和改变角度值 |
代表旋转中心 |
GID_TWOFINGERTAP |
两个手指的距离 |
两个手指中点 |
GID_PRESS_AND_TAP |
两个手指之间的delta |
第一个手指的位置 |
2.Getting Start with Windows Touch Gestures开始
下面描述了使用Windows touch gestures消息的一般步骤
l 建立一个接受gestures消息的窗口
l 处理gestures 消息
l 解析gestures消息
建立一个接收gestures消息的窗口
默认的情况下你会收到WM_GESTURE消息
如果你调用RegisterTouchWindow就会阻止接收WM_GESTURE消息,如果你没有收到WM_GESTURE消息,确保你没有调用RegisterTouchWindow消息。
下面的例子展示了一个简单的InitInstance的实现
#include <windows.h>
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
处理Gesture消息
和处理touch消息类似,你可以用很多种方式处理Gesture消息,如果你用win32,你可以在WndProc中加入case WM_GESTURE分支,如果你创建MFC的应用程序,你可以在消息映射中加入WM_GESTURE消息的映射,然后实现自己的处理函数。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_GESTURE:
// Insert handler code here to interpret the gesture.
return DecodeGesture(hWnd, message, wParam, lParam);
解析Gesture消息
GetGestureInfo函数被用来解析一个gesture消息到GESTUREINFO结构体中,这个体包含了关于这个gesture消息的位置类型等信息,下面的代码展示了你如何去解析一个gesture消息。
LRESULT DecodeGesture(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
// Create a structure to populate and retrieve the extra message info.
GESTUREINFO gi;
ZeroMemory(&gi, sizeof(GESTUREINFO));
gi.cbSize = sizeof(GESTUREINFO);
BOOL bResult = GetGestureInfo((HGESTUREINFO)lParam, &gi);
BOOL bHandled = FALSE;
if (bResult){
// now interpret the gesture
switch (gi.dwID){
case GID_ZOOM:
// Code for zooming goes here
bHandled = TRUE;
break;
case GID_PAN:
// Code for panning goes here
bHandled = TRUE;
break;
case GID_ROTATE:
// Code for rotation goes here
bHandled = TRUE;
break;
case GID_TWOFINGERTAP:
// Code for two-finger tap goes here
bHandled = TRUE;
break;
case GID_PRESSANDTAP:
// Code for roll over goes here
bHandled = TRUE;
break;
default:
// A gesture was not recognized
break;
}
}else{
DWORD dwErr = GetLastError();
if (dwErr > 0){
//MessageBoxW(hWnd, L"Error!", L"Could not retrieve a GESTUREINFO structure.", MB_OK);
}
}
if (bHandled){
return 0;
}else{
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
3.Legacy Support for Panning with Scrollbars
这个部分描述了Windows应用程序上,scorllbar上的panning对传统动作的支持
在windows7中,panning手势会产生WM_*SCROLL消息去使能传统的手势支持,由于你的应用程序可能不支持全部的WM_*SCROLL消息,panning也许不会正确地工作,这个主题就描述了你必须采取的步骤去确保panning操作会按照你的想法工作。
下面的部分解释了如何去使能传统的panning操作
l 创建一个带scrollbar的应用程序
l 无效flicks
l 自定义panning响应
首先创建一个带有scrollbar的应用程序,创建一个win32应用程序,然后在InitInstance中使能scrollbar,代码如下
hWnd = CreateWindow( szWindowClass, szTitle,
WS_OVERLAPPEDWINDOW | WS_VSCROLL, // style
200, 200, 550, 300, NULL, NULL, hInstance, NULL
);
把下面的代码放到你的应用程序的WndProc的开头
TEXTMETRIC tm;
SCROLLINFO si;
// These variables are required to display text.
static int xClient; // width of client area
static int yClient; // height of client area
static int xClientMax; // maximum width of client area
static int xChar; // horizontal scrolling unit
static int yChar; // vertical scrolling unit
static int xUpper; // average width of uppercase letters
static int xPos; // current horizontal scrolling position
static int yPos; // current vertical scrolling position
int i; // loop counter
int x, y; // horizontal and vertical coordinates
int FirstLine; // first line in the invalidated area
int LastLine; // last line in the invalidated area
HRESULT hr;
int abcLength = 0; // length of an abc[] item
int lines = 0;
// Create an array of lines to display.
static const int LINES=28;
static LPCWSTR abc[] = {
L"anteater", L"bear", L"cougar",
L"dingo", L"elephant", L"falcon",
L"gazelle", L"hyena", L"iguana",
L"jackal", L"kangaroo", L"llama",
L"moose", L"newt", L"octopus",
L"penguin", L"quail", L"rat",
L"squid", L"tortoise", L"urus",
L"vole", L"walrus", L"xylophone",
L"yak", L"zebra",
L"This line contains words, but no character. Go figure.",
L""
};
接下来,实现应用程序的逻辑部分,计算所有的text metrics,如下面的代码
case WM_CREATE :
// Get the handle to the client area's device context.
hdc = GetDC (hWnd);
// Extract font dimensions from the text metrics.
GetTextMetrics (hdc, &tm);
xChar = tm.tmAveCharWidth;
xUpper = (tm.tmPitchAndFamily & 1 ? 3 : 2) * xChar/2;
yChar = tm.tmHeight + tm.tmExternalLeading;
// Free the device context.
ReleaseDC (hWnd, hdc);
// Set an arbitrary maximum width for client area.
// (xClientMax is the sum of the widths of 48 average
// lowercase letters and 12 uppercase letters.)
xClientMax = 48 * xChar + 12 * xUpper;
return 0;
接下来实现当窗口resized的时候重新计算text block
case WM_SIZE:
// Retrieve the dimensions of the client area.
yClient = HIWORD (lParam);
xClient = LOWORD (lParam);
// Set the vertical scrolling range and page size
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = LINES - 1;
si.nPage = yClient / yChar;
SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
// Set the horizontal scrolling range and page size.
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = 2 + xClientMax / xChar;
si.nPage = xClient / xChar;
SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
return 0;
接下来实现vscroll的消息
case WM_VSCROLL:
// Get all the vertical scroll bar information
si.cbSize = sizeof (si);
si.fMask = SIF_ALL;
GetScrollInfo (hWnd, SB_VERT, &si);
// Save the position for comparison later on
yPos = si.nPos;
switch (LOWORD (wParam))
{
// user clicked the HOME keyboard key
case SB_TOP:
si.nPos = si.nMin;
break;
// user clicked the END keyboard key
case SB_BOTTOM:
si.nPos = si.nMax;
break;
// user clicked the top arrow
case SB_LINEUP:
si.nPos -= 1;
break;
// user clicked the bottom arrow
case SB_LINEDOWN:
si.nPos += 1;
break;
// user clicked the scroll bar shaft above the scroll box
case SB_PAGEUP:
si.nPos -= si.nPage;
break;
// user clicked the scroll bar shaft below the scroll box
case SB_PAGEDOWN:
si.nPos += si.nPage;
break;
// user dragged the scroll box
case SB_THUMBTRACK:
si.nPos = si.nTrackPos;
break;
// user positioned the scroll box
// This message is the one used by Windows Touch
case SB_THUMBPOSITION:
si.nPos = HIWORD(wParam);
break;
default:
break;
}
// Set the position and then retrieve it. Due to adjustments
// by Windows it may not be the same as the value set.
si.fMask = SIF_POS;
SetScrollInfo (hWnd, SB_VERT, &si, TRUE);
GetScrollInfo (hWnd, SB_VERT, &si);
// If the position has changed, scroll window and update it
if (si.nPos != yPos)
{
ScrollWindow(hWnd, 0, yChar * (yPos - si.nPos), NULL, NULL);
UpdateWindow (hWnd);
}
break;
接下来更新代码来重画窗口
case WM_PAINT:
// Prepare the window for painting
hdc = BeginPaint (hWnd, &ps);
// Get vertical scroll bar position
si.cbSize = sizeof (si);
si.fMask = SIF_POS;
GetScrollInfo (hWnd, SB_VERT, &si);
yPos = si.nPos;
// Get horizontal scroll bar position
GetScrollInfo (hWnd, SB_HORZ, &si);
xPos = si.nPos;
// Find painting limits
FirstLine = max (0, yPos + ps.rcPaint.top / yChar);
LastLine = min (LINES - 1, yPos + ps.rcPaint.bottom / yChar);
for (i = FirstLine; i <= LastLine; i++)
{
x = xChar * (1 - xPos);
y = yChar * (i - yPos);
// Note that "55" in the following depends on the
// maximum size of an abc[] item.
//
abcLength = wcslen(abc[i]);
hr = S_OK;
if ((FAILED(hr)))
{
MessageBox(hWnd, L"err", L"err", NULL);
}else{
TextOut(hdc, x, y, abc[i], abcLength);
}
}
// Indicate that painting is finished
EndPaint (hWnd, &ps);
return 0;
无效Flicks
要想改变panning的动作,必须关闭flicks,包含tpcshrd.h头文件,然后如下
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){
const DWORD_PTR dwHwndTabletProperty =
TABLET_DISABLE_PRESSANDHOLD | // disables press and hold (right-click) gesture
TABLET_DISABLE_PENTAPFEEDBACK | // disables UI feedback on pen up (waves)
TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen button down (circle)
TABLET_DISABLE_FLICKS; // disables pen flicks (back, forward, drag down, drag up)
SetProp(hWnd, MICROSOFT_TABLETPENSERVICE_PROPERTY, reinterpret_cast<HANDLE>(dwHwndTabletProperty));
自定义panning Experience
你可以实现一个不同于windows7默认的panning操作,为了达到这个目的,你必须处理WM_GESTURE消息,更多的信息请看下一节。
4.Improving the Single Finger Panning Experience
如果你建立一个接收windows touch的应用程序,它会自动地提供基本的panning支持,然而,你可以通过处理WM_GESTURE消息来为single-finger panning提供增强的支持。
l 创建一个带有scrollbar和无效flicks的应用程序
l 加入gesture pan支持
l 使能bounce(反弹)
第一步见上一小节
第二步:
为了支持gestrue pan 你必须在wndproc中处理它,这个手势消息可以获得是水平的还是垂直的delta,这个量被用来更新scrollbar,进而更新用户界面
首先更新Windows
的版本,在targetver.h文件中
#ifndef WINVER // Specifies that the minimum required platform is Windows Vista.
#define WINVER 0x0601 // Change this to the appropriate value to target other versions of Windows.
#endif
#ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista.
#define _WIN32_WINNT 0x0601 // Change this to the appropriate value to target other versions of Windows.
#endif
接下来,加入UXTheme.h文件并且加入uxtheme.lib库文件的引用
#include <uxtheme.h>
然后在wndproc中加入如下的变量
// The following are used for the custom panning handler
BOOL bResult = FALSE;
static int scale = 8; // altering the scale value will change how fast the page scrolls
static int lastY = 0; // used for panning calculations (initial / previous vertical position)
static int lastX = 0; // used for panning calculations (initial / previous horizontal position)
GESTUREINFO gi;
然后加入WM_GESTURE消息的处理分支,使scrollbar可以通过手势更新
case WM_GESTURE:
// Get all the vertial scroll bar information
si.cbSize = sizeof (si);
si.fMask = SIF_ALL;
GetScrollInfo (hWnd, SB_VERT, &si);
yPos = si.nPos;
ZeroMemory(&gi, sizeof(GESTUREINFO));
gi.cbSize = sizeof(GESTUREINFO);
bResult = GetGestureInfo((HGESTUREINFO)lParam, &gi);
if (bResult){
// now interpret the gesture
switch (gi.dwID){
case GID_BEGIN:
lastY = gi.ptsLocation.y;
CloseGestureInfoHandle((HGESTUREINFO)lParam);
break;
// A CUSTOM PAN HANDLER
// COMMENT THIS CASE OUT TO ENABLE DEFAULT HANDLER BEHAVIOR
case GID_PAN:
si.nPos -= (gi.ptsLocation.y - lastY) / scale;
si.fMask = SIF_POS;
SetScrollInfo (hWnd, SB_VERT, &si, TRUE);
GetScrollInfo (hWnd, SB_VERT, &si);
yOverpan -= lastY - gi.ptsLocation.y;
lastY = gi.ptsLocation.y;
if (gi.dwFlags & GF_BEGIN){
BeginPanningFeedback(hWnd);
yOverpan = 0;
} else if (gi.dwFlags & GF_END) {
EndPanningFeedback(hWnd, TRUE);
yOverpan = 0;
}
if (si.nPos == si.nMin || si.nPos >= (si.nMax - si.nPage)){
// we reached the bottom / top, pan
UpdatePanningFeedback(hWnd, 0, yOverpan, gi.dwFlags & GF_INERTIA);
}
ScrollWindow(hWnd, 0, yChar * (yPos - si.nPos), NULL, NULL);
UpdateWindow (hWnd);
return DefWindowProc(hWnd, message, lParam, wParam);
case GID_ZOOM:
// Add Zoom handler
return DefWindowProc(hWnd, message, lParam, wParam);
default:
// You have encountered an unknown gesture
return DefWindowProc(hWnd, message, lParam, wParam);
}
}else{
DWORD dwErr = GetLastError();
if (dwErr > 0){
// something is wrong
// 87 indicates that you are probably using a bad
// value for the gi.cbSize
}
}
return DefWindowProc (hWnd, message, wParam, lParam);
现在你就可以在你的窗口上pan了,你可以看到scroll可以带有惯性的滚动。
Boundary Feedback in WndProc
BoundaryFeedBack 是一种当用户在可panning区域中panning时的一种反馈,当到达某个界限时被应用程序触发的,在上一个例子的WM_GESTURE消息的处理部分,在case WM_GESTURE中的结束条件si.nPos == si.yPos 被用来探测是否到达了可panning区域的边界,下面的变量被用来跟踪值和测试errors
// The following are used for panning feedback (Window Bounce)
static int animCount = 0;
static DWORD dwErr = 0;
static BOOL isOverpan = FALSE;
static long xOverpan = 0;
static long yOverpan = 0;
pan手势被用来触发边界反馈,下面的代码演示了WM_GESTURE中的GID_PAN分支
case GID_PAN:
si.nPos -= (gi.ptsLocation.y - lastY) / scale;
si.fMask = SIF_POS;
SetScrollInfo (hWnd, SB_VERT, &si, TRUE);
GetScrollInfo (hWnd, SB_VERT, &si);
yOverpan -= lastY - gi.ptsLocation.y;
lastY = gi.ptsLocation.y;
if (gi.dwFlags & GF_BEGIN){
BeginPanningFeedback(hWnd);
yOverpan = 0;
} else if (gi.dwFlags & GF_END) {
EndPanningFeedback(hWnd, TRUE);
yOverpan = 0;
}
if (si.nPos == si.nMin){
// we reached the top, pan upwards in y direction
UpdatePanningFeedback(hWnd, 0, yOverpan, gi.dwFlags & GF_INERTIA);
}else if (si.nPos >= (si.nMax - si.nPage)){
// we reached the bottom, pan downwards in y direction
UpdatePanningFeedback(hWnd, 0, yOverpan, gi.dwFlags & GF_INERTIA);
}
ScrollWindow(hWnd, 0, yChar * (yPos - si.nPos), NULL, NULL);
UpdateWindow (hWnd);
return DefWindowProc(hWnd, message, lParam, wParam);
现在,当用户在scrollbar区域的底部pan时,你的程序就会有边界反馈了。
五、Manipulations and Inertia
Manipulations:
1.Adding Manipulation Support in Unmanaged Code
这部分主要介绍如何在非托管的代码中通过一个事件sink(_IManipulationEvents接口)添加manipulation支持
下图展示了manipulation的结构图:
从WM_TOUCH消息接收到的Touch数据(除了触点ID外)被传递到IManipulationProcessor。建立在消息队列的基础上,IManipulationProcessor接口会计算将要被执行的变化以及变化相关的值。然后,IManipulationProcessor会产生事件sink _IManipulationEvents的句柄。这个事件sink会使用这些数据去执行对象的转换操作
添加Manipulation支持需要执行以下步骤:
以下介绍所有步骤的代码:
Note 不能同时使用Manipulation和手势,因为手势和Touch是互斥使用的
使用一个_IManipualtionEvents接口的event sink
在实例化自定义的event sink时,必须创建一个类来实现_IManipualtionEvents的接口。由IManipulationProcessor接口产生的事件会回调自定义的event sink。下面例子是一个自定义的继承自_ IManipulationEvents接口的event sink类。
// Manipulation Header Files
#include <comdef.h>
#include <manipulations.h>
#include <ocidl.h>
class CManipulationEventSink : _IManipulationEvents
{
public:
CManipulationEventSink(IManipulationProcessor *manip, HWND hWnd);
int GetStartedEventCount();
int GetDeltaEventCount();
int GetCompletedEventCount();
double CManipulationEventSink::GetX();
double CManipulationEventSink::GetY();
~CManipulationEventSink();
//////////////////////////////
// IManipulationEvents methods
//////////////////////////////
virtual HRESULT STDMETHODCALLTYPE ManipulationStarted(
/* [in] */ FLOAT x,
/* [in] */ FLOAT y);
virtual HRESULT STDMETHODCALLTYPE ManipulationDelta(
/* [in] */ FLOAT x,
/* [in] */ FLOAT y,
/* [in] */ FLOAT translationDeltaX,
/* [in] */ FLOAT translationDeltaY,
/* [in] */ FLOAT scaleDelta,
/* [in] */ FLOAT expansionDelta,
/* [in] */ FLOAT rotationDelta,
/* [in] */ FLOAT cumulativeTranslationX,
/* [in] */ FLOAT cumulativeTranslationY,
/* [in] */ FLOAT cumulativeScale,
/* [in] */ FLOAT cumulativeExpansion,
/* [in] */ FLOAT cumulativeRotation);
virtual HRESULT STDMETHODCALLTYPE ManipulationCompleted(
/* [in] */ FLOAT x,
/* [in] */ FLOAT y,
/* [in] */ FLOAT cumulativeTranslationX,
/* [in] */ FLOAT cumulativeTranslationY,
/* [in] */ FLOAT cumulativeScale,
/* [in] */ FLOAT cumulativeExpansion,
/* [in] */ FLOAT cumulativeRotation);
////////////////////////////////////////////////////////////
// IUnknown methods
////////////////////////////////////////////////////////////
STDMETHOD_(ULONG, AddRef)(void);
STDMETHOD_(ULONG, Release)(void);
STDMETHOD(QueryInterface)(REFIID riid, LPVOID *ppvObj);
private:
double m_fX;
double m_fY;
int m_cRefCount;
int m_cStartedEventCount;
int m_cDeltaEventCount;
int m_cCompletedEventCount;
IManipulationProcessor* m_pManip;
IConnectionPointContainer* m_pConPointContainer;
IConnectionPoint* m_pConnPoint;
HWND m_hWnd;
};
给出了头文件,必须实现事件接口,使类按照你想要的方式让Manipulation处理器去执行。下面的代码是一个最简单的_IManipulationEvents接口event sink。
#include "stdafx.h"
#include "cmanipulationeventsink.h"
CManipulationEventSink::CManipulationEventSink(IManipulationProcessor *manip, HWND hWnd)
{
m_hWnd = hWnd;
//Set initial ref count to 1.
m_cRefCount = 1;
m_pManip = manip;
m_pManip->put_PivotRadius(-1);
m_cStartedEventCount = 0;
m_cDeltaEventCount = 0;
m_cCompletedEventCount = 0;
HRESULT hr = S_OK;
//Get the container with the connection points.
IConnectionPointContainer* spConnectionContainer;
hr = manip->QueryInterface(
IID_IConnectionPointContainer,
(LPVOID*) &spConnectionContainer
);
//hr = manip->QueryInterface(&spConnectionContainer);
if (spConnectionContainer == NULL){
// something went wrong, try to gracefully quit
}
//Get a connection point.
hr = spConnectionContainer->FindConnectionPoint(__uuidof(_IManipulationEvents), &m_pConnPoint);
if (m_pConnPoint == NULL){
// something went wrong, try to gracefully quit
}
DWORD dwCookie;
//Advise.
hr = m_pConnPoint->Advise(this, &dwCookie);
}
int CManipulationEventSink::GetStartedEventCount()
{
return m_cStartedEventCount;
}
int CManipulationEventSink::GetDeltaEventCount()
{
return m_cDeltaEventCount;
}
int CManipulationEventSink::GetCompletedEventCount()
{
return m_cCompletedEventCount;
}
double CManipulationEventSink::GetX()
{
return m_fX;
}
double CManipulationEventSink::GetY()
{
return m_fY;
}
CManipulationEventSink::~CManipulationEventSink()
{
//Cleanup.
}
///////////////////////////////////
//Implement IManipulationEvents
///////////////////////////////////
HRESULT STDMETHODCALLTYPE CManipulationEventSink::ManipulationStarted(
/* [in] */ FLOAT x,
/* [in] */ FLOAT y)
{
m_cStartedEventCount ++;
return S_OK;
}
HRESULT STDMETHODCALLTYPE CManipulationEventSink::ManipulationDelta(
/* [in] */ FLOAT x,
/* [in] */ FLOAT y,
/* [in] */ FLOAT translationDeltaX,
/* [in] */ FLOAT translationDeltaY,
/* [in] */ FLOAT scaleDelta,
/* [in] */ FLOAT expansionDelta,
/* [in] */ FLOAT rotationDelta,
/* [in] */ FLOAT cumulativeTranslationX,
/* [in] */ FLOAT cumulativeTranslationY,
/* [in] */ FLOAT cumulativeScale,
/* [in] */ FLOAT cumulativeExpansion,
/* [in] */ FLOAT cumulativeRotation)
{
m_cDeltaEventCount ++;
RECT rect;
GetWindowRect(m_hWnd, &rect);
int oldWidth = rect.right-rect.left;
int oldHeight = rect.bottom-rect.top;
// scale and translate the window size / position
MoveWindow(m_hWnd, // the window to move
static_cast<int>(rect.left + (translationDeltaX / 100.0f)), // the x position
static_cast<int>(rect.top + (translationDeltaY/100.0f)), // the y position
static_cast<int>(oldWidth * scaleDelta), // width
static_cast<int>(oldHeight * scaleDelta), // height
TRUE); // redraw
return S_OK;
}
HRESULT STDMETHODCALLTYPE CManipulationEventSink::ManipulationCompleted(
/* [in] */ FLOAT x,
/* [in] */ FLOAT y,
/* [in] */ FLOAT cumulativeTranslationX,
/* [in] */ FLOAT cumulativeTranslationY,
/* [in] */ FLOAT cumulativeScale,
/* [in] */ FLOAT cumulativeExpansion,
/* [in] */ FLOAT cumulativeRotation)
{
m_cCompletedEventCount ++;
m_fX = x;
m_fY = y;
// place your code handler here to do any operations based on the manipulation
return S_OK;
}
/////////////////////////////////
//Implement IUnknown
/////////////////////////////////
ULONG CManipulationEventSink::AddRef(void)
{
return ++m_cRefCount;
}
ULONG CManipulationEventSink::Release(void)
{
m_cRefCount --;
if(0 == m_cRefCount) {
delete this;
return 0;
}
return m_cRefCount;
}
HRESULT CManipulationEventSink::QueryInterface(REFIID riid, LPVOID *ppvObj)
{
if (IID__IManipulationEvents == riid) {
*ppvObj = (_IManipulationEvents *)(this); AddRef(); return S_OK;
} else if (IID_IUnknown == riid) {
*ppvObj = (IUnknown *)(this); AddRef(); return S_OK;
} else {
return E_NOINTERFACE;
}
}
特别注意类中的ManipulationStarted、ManipulationDelta和ManipulationCompleted方法的执行。他们是在接口中最合适你基于Manipulation所执行操作的地方,这些操作的信息在事件中被分发。也要注意在构造函数的第二个参数,他是在事件处理中被使用的对象。在上面的例子中,应用程序的hwnd被传递到构造函数中,以致它可以被定位和调整大小。
实例化一个IManipulationProcessor接口
要使用Manipulation必须实例化一个IManipulationProcessor接口,首先你必须为Manipulation类提供支持。
以下代码给出如何完成这些操作
//Include windows.h for touch events
#include "windows.h"
// Manipulation implementation file
#include <manipulations_i.c>
// Smart Pointer to a global reference of a manipulation processor, event sink
IManipulationProcessor* g_pIManipProc;
当你定义了Manipulation的处理器变量并且包含Manipulation的头文件后你将需要实例化IManipulationProcessor接口。这是一个COM对象,所以你必须调用CoCreateInstance方法,然后实例化IManipulationProcessor 的引用。下面代码告诉如何实现这个借口:
HRESULT hr = CoInitialize(0);
hr = CoCreateInstance(CLSID_ManipulationProcessor,
NULL,
CLSCTX_INPROC_SERVER,
IID_IUnknown,
(VOID**)(&g_pIManipProc)
);
实例化Event Sink并建立Touch事件
在代码中包含自定义的sink类,然后添加一个Manipulation事件sink类的变量。如下代码,包含了类的头文件并且设置一个全局变量来存储这个event sink。
//Include your definition of the event sink, CManipulationEventSink.h in this case
#include "CManipulationEventSink.h"
// Set up a variable to point to the manipulation event sink implementation class
CManipulationEventSink* g_pManipulationEventSink;
当你定义了变量并包含了事件sink类的定义后,就可以使用Manipulation处理器来构造这个类对象。下面代码显示这个类的初始化在OnInitDialog中。
g_pManipulationEventSink = new CManipulationEventSink(g_pIManipProc, hWnd);
RegisterTouchWindow(hWnd, 0);
注意 实例化event sink的方式依赖于你想通过Manipulation数据做什么。大多数情况下,你会创建一个和当前例子不同的构造函数的Manipulation处理器事件sink。
向Manipulation处理器发送Touch事件数据
现在已经有了Manipulation处理器和事件sink,然后需要提供Touch数据给Manipulation处理器去引发Manipulation事件。
首先,你应该创建一些代码去解析WM_TOUCH消息,然后把他们发送给IManipulationProcessor接口去产生事件。
LRESULT OnTouch(HWND hWnd, WPARAM wParam, LPARAM lParam )
{
UINT cInputs = LOWORD(wParam);
PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
BOOL bHandled = FALSE;
if (NULL != pInputs) {
if (GetTouchInputInfo((HTOUCHINPUT)lParam,
cInputs,
pInputs,
sizeof(TOUCHINPUT))) {
for (UINT i=0; i<cInputs; i++){
if (pInputs[i].dwFlags & TOUCHEVENTF_DOWN){
g_pIManipProc->ProcessDown(pInputs[i].dwID, static_cast<FLOAT>(pInputs[i].x), static_cast<FLOAT>(pInputs[i].y));
bHandled = TRUE;
}
if (pInputs[i].dwFlags & TOUCHEVENTF_UP){
g_pIManipProc->ProcessUp(pInputs[i].dwID, static_cast<FLOAT>(pInputs[i].x), static_cast<FLOAT>(pInputs[i].y));
bHandled = TRUE;
}
if (pInputs[i].dwFlags & TOUCHEVENTF_MOVE){
g_pIManipProc->ProcessMove(pInputs[i].dwID, static_cast<FLOAT>(pInputs[i].x), static_cast<FLOAT>(pInputs[i].y));
bHandled = TRUE;
}
}
} else {
// GetLastError() and error handling
}
delete [] pInputs;
} else {
// error handling, presumably out of memory
}
if (bHandled){
// if you don't want to pass to DefWindowProc, close the touch input handle
if (!CloseTouchInputHandle((HTOUCHINPUT)lParam)) {
// error handling
}
return 0;
}else{
return DefWindowProc(hWnd, WM_TOUCH, wParam, lParam);
}
}
现在你有一个应用的方法去解析WM_TOUCH消息,必须从WndProc方法传递WM_TOUCH消息给这个应用函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_TOUCH:
return OnTouch(hWnd, wParam, lParam);
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
2.高级Manipulation
(1)介绍应用程序的高级Manipulation:
作为可用性的目的。你可能在应用程序中添加复杂的处理,以便对象可以以一个好的粒度被熟练控制。一下描述了高级控制。
一下两图说明了两种扩大方式:
在例A中,这个对象围绕着他的中心点扩张,例B中,对象围绕着控制(Manipulation)的中间点扩张。
例A围绕中心点旋转,例B围绕控制的中心点旋转。
高级转换
在例A中,对象移动时不带旋转。例B中,在对象移动过程中伴随旋转,旋转依赖于对象的接触点。如果设置单指旋转,则可以使用复合的移动。
(2)高级放大
在例A中,对象围绕着他的中心点在扩大。在例B中,对象围绕着控制点的中心被扩大。如果想这样实现,必须在扩大的过程中移动对象。移动对象的距离相当于对象的中心点到手势的中心点的距离。也就是说是以手势的中心点去扩大对象然后移动对象使他仍然在初始状态的中心点。下面的代码展示了用于围绕一个中心点被扩大的方法。
if(m_fFactor != 1.0f)
{
// We represent our vectors as an array.
// x: vx[0], y: vx[1]
FLOAT v1[2];
v1[0] = this->get_CenterX() - fOffset[0];
v1[1] = this->get_CenterY() - fOffset[1];
FLOAT v2[2];
v2[0] = v1[0] * m_fFactor;
v2[1] = v1[1] * m_fFactor;
m_fdX += v2[0] - v1[0];
m_fdY += v2[1] - v1[1];
}
(3)高级旋转
在例A中,对象围绕着他的中心点被控制旋转。在例B中,对象围绕着控制的中点被旋转。要想支持围绕除了对象中心点的移动,必须执行旋转和移动的复合操作。一下代码展示如何被计算和执行的。
RotateVector(FLOAT *vector, FLOAT *tVector, FLOAT fAngle) {
FLOAT fAngleRads = fAngle / 180.0f * 3.14159f;
FLOAT fSin = sin(fAngleRads);
FLOAT fCos = cos(fAngleRads);
FLOAT fNewX = (vector[0]*fCos) - (vector[1]*fSin);
FLOAT fNewY = (vector[0]*fSin) + (vector[1]*fCos);
tVector[0] = fNewX;
tVector[1] = fNewY;
}
(4)高级移动
在例A中,对象移动不伴随旋转。在例B中,对象在移动过程中被旋转,而旋转依赖于这个对象被触摸的点。如果可以使用单指旋转,才可以使用复合移动。下图说明了复合移动中的单指旋转的一些组成部分。
当对象被移动,半径会被重新计算并且轴心被移动。
The following code shows one way that this could be done in an implementation of ManipulationDelta that enables complex translation.
以下代码展示了可以使用ManipulationDelta操作来完成复合移动
//Apply transformation based on rotationDelta (in radians)
FLOAT rads = 180.0f / 3.14159f;
m_dObj->Rotate(rotationDelta*rads, x, y);
// Apply translation based on scaleDelta
m_dObj->Scale(scaleDelta);
// Apply translation based on translationDelta
m_dObj->Translate(translationDeltaX, translationDeltaY);
// Set values for one finger rotations
FLOAT fPivotRadius = (FLOAT)(m_dObj->get_Width() + m_dObj->get_Height())/8.0f;
FLOAT fPivotPtX = m_dObj->get_CenterX();
FLOAT fPivotPtY = m_dObj->get_CenterY();
m_manip->put_PivotPointX(fPivotPtX);
m_manip->put_PivotPointY(fPivotPtY);
m_manip->put_PivotRadius(fPivotRadius);
注意 对象的移动发生在轴心点和半径被计算之前,如果使用移动伴随放大使用这种方式是正确的。
3. 单指旋转
介绍如何使用单个手指进行旋转。
在例A中,对象围绕着手势的中心点去旋转。例B中,对象围绕它的边缘通过一个手指移动来旋转。Manipulation处理器通过轴心点和轴半径的值来实现。下面展示单指移动的各种成分。
在设置PivotPointX、PivotPointY和PivotRadius值后,在随后发送的消息中将包含旋转。轴的半径越大,就越会旋转。以下代码显示如何在Manipulation处理器中设置这些值。
HRESULT STDMETHODCALLTYPE CManipulationEventSink::ManipulationDelta(
/* [in] */ FLOAT x,
/* [in] */ FLOAT y)
{
m_cStartedEventCount ++;
// Set the pivot point to the object's center and then set the radius
// to the distance from the center to the
m_pManip->put_PivotPointX(m_objectRef->xPos);
m_pManip->put_PivotPointY(m_objectRef->yPos);
float fPivotRadius = (FLOAT)(sqrt(pow(m_dObj->get_Width()/2, 2) + pow(m_dObj->get_Height()/2, 2)))*0.4f;
m_pManip->put_PivotRadius(fPivotRadius);
return S_OK;
}
在前面的例子中,到物体边缘的距离(缩放至百分之四十)作为轴半径使用。因为要考虑到对象尺寸的变化,计算在每个对象增长的时候有效。对象进行缩放,轴半径也增长。除了对象中心的x、y值,其余的都被传递到Manipulation处理器去围绕轴点旋转这个对象
注意 PivotRadius不应该在0.0到1.0之间。